From 69c5c270ad145a5f860a29fe0c11d2464a550e33 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 6 Feb 2014 21:10:00 +0100 Subject: [PATCH 01/37] first steps to indienotes --- css/screen.css | 18 +++++++++++++++++- js/App.js | 6 ++++-- js/Pond.js | 4 +++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/css/screen.css b/css/screen.css index 7c14659..d1b2fe4 100644 --- a/css/screen.css +++ b/css/screen.css @@ -188,6 +188,7 @@ canvas { #list li { position: relative; + min-height: 3em; } .red #list li { border-bottom: 1px solid #c0392b; } @@ -199,7 +200,7 @@ canvas { content: ""; position: absolute; right: 7px; - top: 0.1em; + top: 0; font-weight: 100; font-size: 3em; font-family: "Entypo"; @@ -248,6 +249,7 @@ canvas { font-weight: normal; margin: 0; padding: 0; + display: none; } #full .wrapper { @@ -278,6 +280,20 @@ canvas { padding: 0; } +#full article header p:nth-child(1) { + float: left; +} + +#full article header p:nth-child(3) { + float: right; +} + +#full .article { + clear: both; + padding-top: 1em; + font-size: 1.3em; +} + #full footer.bar { margin: auto 0 0 0; position: relative; diff --git a/js/App.js b/js/App.js index c874b84..bbcc0dd 100644 --- a/js/App.js +++ b/js/App.js @@ -182,8 +182,10 @@ App.prototype.populateList = function() { html_str += ""; html_str += ""; html_str += "

" + article.feed_title + "

"; - html_str += "

" + article.title + "

"; - if(article.excerpt) html_str += "

" + article.excerpt + "

"; + var content = article.content.stripHTML(); + if(content.replace(/^\s+|\s+$/g,'').length == 0) content = article.title; + html_str += "

" + content + "

"; + //if(article.excerpt) html_str += "

" + article.excerpt + "

"; html_str += "
"; } diff --git a/js/Pond.js b/js/Pond.js index 501494c..cdea2b0 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -76,7 +76,7 @@ Pond.prototype.reload = function(callback) { Pond.prototype.getUnreadFeeds = function(callback, skip) { var options = { - status: "unread", + status: "all", limit: 100 }; @@ -134,6 +134,8 @@ Pond.prototype.normalizeArticle = function(article) { var timestamp = new Date(article.published_at).getTime() / 1000; + console.log(article.read) + return { id: article.id, guid_hash: article.url + article.id, From 5a0950a8ee59d049a6a40e76055631cd1701afc6 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 11:01:11 +0100 Subject: [PATCH 02/37] login only with pond now --- css/screen.css | 7 ++++++- index.html | 7 ------- js/App.js | 26 +++++++++++++++++++++----- js/Login.js | 20 +++----------------- js/Pond.js | 10 ++++------ js/application.js | 5 +++++ 6 files changed, 39 insertions(+), 36 deletions(-) diff --git a/css/screen.css b/css/screen.css index d1b2fe4..6fbf34a 100644 --- a/css/screen.css +++ b/css/screen.css @@ -165,6 +165,7 @@ canvas { list-style-type: none; margin: 0; padding: 0; + word-wrap: break-word; } #list p { @@ -284,10 +285,14 @@ canvas { float: left; } -#full article header p:nth-child(3) { +#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; diff --git a/index.html b/index.html index feaa4f4..9686112 100644 --- a/index.html +++ b/index.html @@ -64,14 +64,7 @@ -

- - - -

- - diff --git a/js/App.js b/js/App.js index bbcc0dd..b577d17 100644 --- a/js/App.js +++ b/js/App.js @@ -7,6 +7,16 @@ function App() { if(!color) color = "red"; this.setColor(color); this.fontChange(); + + var _this = this; + + window.onkeydown = function(e) { + if(e.keyCode == 39) { + _this.showNext(); + } else if(e.keyCode == 37) { + _this.showPrevious(); + } + } }; App.prototype.authenticate = function() { @@ -15,10 +25,13 @@ App.prototype.authenticate = function() { App.prototype.after_login = function(backend) { + /* var request = window.navigator.mozApps.getSelf(); request.onsuccess = function() { - $("#version").innerHTML = request.result.manifest.version; - } + if(request.result) { + $("#version").innerHTML = request.result.manifest.version; + } + }*/ var _this = this; @@ -276,8 +289,8 @@ App.prototype.showFull = function(article, slide_back) { $(page_id + " .date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000)).toLocaleString(); - var title = $(page_id + " .title"); - title.innerHTML = article.title; + var title = $(page_id + " .link"); + title.innerHTML = article.link; title.href = article.link; $(page_id + " .feed_title").innerHTML = article.feed_title; @@ -286,7 +299,10 @@ App.prototype.showFull = function(article, slide_back) { if(article.author && article.author.length > 0) $(page_id + " .author").innerHTML = "– " + article.author; - $(page_id + " .article").innerHTML = article.content; + + var content = article.content + if(content.replace(/^\s+|\s+$/g,'').length == 0) content = article.title; + $(page_id + " .article").innerHTML = content.urlify(); $$(page_id + " .article a").forEach(function(o, i) { o.target = "_blank"; }); diff --git a/js/Login.js b/js/Login.js index 72a9490..bc05ba8 100644 --- a/js/Login.js +++ b/js/Login.js @@ -17,19 +17,6 @@ Login.prototype.is_logged_in = function() { Login.prototype.log_in = function() { 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)); }; @@ -38,11 +25,10 @@ Login.prototype.authenticate = function(e) { e.preventDefault(); e.stopPropagation(); - var backend = "TinyTinyRSS"; - if($("#login form").backend[1].checked) backend = "OwnCloud"; - else if($("#login form").backend[2].checked) backend = "Pond"; + var backend = "Pond"; - var server_url = $("#url").value; + var server_url = window.location.href.split("#")[0].replace(/\/FeedMonkey\//, ''); + console.log(server_url) var user = $("#un").value; var password = $("#pw").value; diff --git a/js/Pond.js b/js/Pond.js index cdea2b0..5991620 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -46,7 +46,7 @@ Pond.prototype.doOperation = function(method, operation, new_options, callback) body = a.join("&"); } - var xhr = new XMLHttpRequest({mozSystem: true}); + var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 200) { @@ -76,7 +76,7 @@ Pond.prototype.reload = function(callback) { Pond.prototype.getUnreadFeeds = function(callback, skip) { var options = { - status: "all", + status: "unread", limit: 100 }; @@ -134,8 +134,6 @@ Pond.prototype.normalizeArticle = function(article) { var timestamp = new Date(article.published_at).getTime() / 1000; - console.log(article.read) - return { id: article.id, guid_hash: article.url + article.id, @@ -195,7 +193,7 @@ Pond.prototype.setArticleUnstarred = function(articles, callback) { } Pond.prototype.logOut = function() { - this.doOperation("auth/sessions/" + this.session_token ); + this.doOperation("DELETE", "auth/sessions/" + this.session_token ); localStorage.feeds = null; } @@ -209,7 +207,7 @@ Pond.login = function(server_url, user, password, callback) { var password_hash = md5(user + ':' + password) var options = "username=" + user.toLowerCase() + "&" + "password=" + password_hash; - var xhr = new XMLHttpRequest({mozSystem: true}); + var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 201) { diff --git a/js/application.js b/js/application.js index 5e38312..e20c39b 100644 --- a/js/application.js +++ b/js/application.js @@ -63,5 +63,10 @@ String.prototype.capitalize = function() { return this.charAt(0).toUpperCase() + this.slice(1); } +String.prototype.urlify = function() { + var exp = /^\>(\b(http):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + return this.replace(exp,"$1"); +} + if(!window.app) window.app = new App(); From 5b252e56b3d2485673c024a21d03da9940f9cb05 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 11:02:08 +0100 Subject: [PATCH 03/37] removed owncloud and ttrss --- js/OwnCloud.js | 254 ---------------------------------------------- js/TinyTinyRSS.js | 183 --------------------------------- 2 files changed, 437 deletions(-) delete mode 100644 js/OwnCloud.js delete mode 100644 js/TinyTinyRSS.js diff --git a/js/OwnCloud.js b/js/OwnCloud.js deleted file mode 100644 index 15776f1..0000000 --- a/js/OwnCloud.js +++ /dev/null @@ -1,254 +0,0 @@ -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.withCredentials = true; - xhr.setRequestHeader('Authorization', 'Basic ' + this.session_id); - var body = JSON.stringify(options); - xhr.send(body); -} - -OwnCloud.prototype.reload = function(callback) { - var _this = this; - this.getFeeds(function() { _this.getUnreadFeeds(callback); }); -}; - -OwnCloud.prototype.getUnreadFeeds = function(callback, skip) { - if(skip) { - skip = skip[skip.length - 1].id; - } - - var options = { - batchSize: 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(options.items); - 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 { - 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(); -} diff --git a/js/TinyTinyRSS.js b/js/TinyTinyRSS.js deleted file mode 100644 index 4479f49..0000000 --- a/js/TinyTinyRSS.js +++ /dev/null @@ -1,183 +0,0 @@ -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"].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) { - this.getUnreadFeeds(callback, []); -}; - -TinyTinyRSS.prototype.getUnreadFeeds = function(callback, skip) { - skip = skip.length; - var options = { - show_excerpt: false, - view_mode: "unread", - show_content: true, - feed_id: -4, - 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.append = function(key, array) { - - var tmp = localStorage[key]; - - if (typeof tmp !== "undefined") tmp = JSON.parse(tmp); - else tmp = []; - - tmp.concat(options.items); - 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 { - alert("error: " + xhr.status + " " + xhr.statusText) - } - } - } - xhr.open("POST", url, true); - xhr.send(JSON.stringify(options)); -} From 4dba0ea07847000584f778b9f5516b0d3934eff9 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 11:02:50 +0100 Subject: [PATCH 04/37] removed owncloud and ttrss --- index.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.html b/index.html index 9686112..d851058 100644 --- a/index.html +++ b/index.html @@ -10,8 +10,6 @@ - - From 92ad94c1d8a173772b66ebaaefef26985c9a4966 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 11:15:21 +0100 Subject: [PATCH 05/37] fixed css a bit and logout --- css/screen.css | 4 +++- js/App.js | 3 ++- js/Pond.js | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/css/screen.css b/css/screen.css index d1b2fe4..3c995bf 100644 --- a/css/screen.css +++ b/css/screen.css @@ -291,12 +291,14 @@ canvas { #full .article { clear: both; padding-top: 1em; + padding-bottom: 2.8em; font-size: 1.3em; } #full footer.bar { + bottom: 0; margin: auto 0 0 0; - position: relative; + position: fixed; height: 3.8em; } diff --git a/js/App.js b/js/App.js index bbcc0dd..6354c4e 100644 --- a/js/App.js +++ b/js/App.js @@ -14,11 +14,12 @@ App.prototype.authenticate = function() { }; App.prototype.after_login = function(backend) { - + /* var request = window.navigator.mozApps.getSelf(); request.onsuccess = function() { $("#version").innerHTML = request.result.manifest.version; } + */ var _this = this; diff --git a/js/Pond.js b/js/Pond.js index cdea2b0..8765ce2 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -195,7 +195,7 @@ Pond.prototype.setArticleUnstarred = function(articles, callback) { } Pond.prototype.logOut = function() { - this.doOperation("auth/sessions/" + this.session_token ); + this.doOperation("DELETE", "auth/sessions/" + this.session_token ); localStorage.feeds = null; } From 01cb207b6562aa82faeb042bdc58101da6bc1335 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 11:35:58 +0100 Subject: [PATCH 06/37] some reverts --- css/screen.css | 10 ++-------- index.html | 9 +++++++++ js/App.js | 24 ++++-------------------- js/Pond.js | 8 +++++--- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/css/screen.css b/css/screen.css index b5116bd..46bcd45 100644 --- a/css/screen.css +++ b/css/screen.css @@ -165,7 +165,6 @@ canvas { list-style-type: none; margin: 0; padding: 0; - word-wrap: break-word; } #list p { @@ -285,23 +284,18 @@ canvas { float: left; } -#full article header p:nth-child(2) { - float: right; -} - #full article header p:nth-child(3) { - clear: both; + float: right; } #full .article { clear: both; padding-top: 1em; - padding-bottom: 2.8em; + padding-bottom: 3em; font-size: 1.3em; } #full footer.bar { - bottom: 0; margin: auto 0 0 0; position: fixed; height: 3.8em; diff --git a/index.html b/index.html index d851058..feaa4f4 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,8 @@ + + @@ -62,7 +64,14 @@

+

+ + + +

+ + diff --git a/js/App.js b/js/App.js index 5b51703..be5ebe6 100644 --- a/js/App.js +++ b/js/App.js @@ -7,16 +7,6 @@ function App() { if(!color) color = "red"; this.setColor(color); this.fontChange(); - - var _this = this; - - window.onkeydown = function(e) { - if(e.keyCode == 39) { - _this.showNext(); - } else if(e.keyCode == 37) { - _this.showPrevious(); - } - } }; App.prototype.authenticate = function() { @@ -27,12 +17,9 @@ App.prototype.after_login = function(backend) { /* var request = window.navigator.mozApps.getSelf(); request.onsuccess = function() { - if(request.result) { - $("#version").innerHTML = request.result.manifest.version; - } + $("#version").innerHTML = request.result.manifest.version; }*/ - var _this = this; window.onhashchange = function(e) { @@ -289,8 +276,8 @@ App.prototype.showFull = function(article, slide_back) { $(page_id + " .date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000)).toLocaleString(); - var title = $(page_id + " .link"); - title.innerHTML = article.link; + var title = $(page_id + " .title"); + title.innerHTML = article.title; title.href = article.link; $(page_id + " .feed_title").innerHTML = article.feed_title; @@ -299,10 +286,7 @@ App.prototype.showFull = function(article, slide_back) { if(article.author && article.author.length > 0) $(page_id + " .author").innerHTML = "– " + article.author; - - var content = article.content - if(content.replace(/^\s+|\s+$/g,'').length == 0) content = article.title; - $(page_id + " .article").innerHTML = content.urlify(); + $(page_id + " .article").innerHTML = article.content; $$(page_id + " .article a").forEach(function(o, i) { o.target = "_blank"; }); diff --git a/js/Pond.js b/js/Pond.js index 5991620..8765ce2 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -46,7 +46,7 @@ Pond.prototype.doOperation = function(method, operation, new_options, callback) body = a.join("&"); } - var xhr = new XMLHttpRequest(); + var xhr = new XMLHttpRequest({mozSystem: true}); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 200) { @@ -76,7 +76,7 @@ Pond.prototype.reload = function(callback) { Pond.prototype.getUnreadFeeds = function(callback, skip) { var options = { - status: "unread", + status: "all", limit: 100 }; @@ -134,6 +134,8 @@ Pond.prototype.normalizeArticle = function(article) { var timestamp = new Date(article.published_at).getTime() / 1000; + console.log(article.read) + return { id: article.id, guid_hash: article.url + article.id, @@ -207,7 +209,7 @@ Pond.login = function(server_url, user, password, callback) { var password_hash = md5(user + ':' + password) var options = "username=" + user.toLowerCase() + "&" + "password=" + password_hash; - var xhr = new XMLHttpRequest(); + var xhr = new XMLHttpRequest({mozSystem: true}); xhr.onreadystatechange = function() { if(xhr.readyState == 4) { if(xhr.status == 201) { From 2d5e1f00e7d51f2c019c4811736c8c75b7721f39 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 11:48:49 +0100 Subject: [PATCH 07/37] fixes after revert --- css/screen.css | 7 ++++++- index.html | 2 +- js/App.js | 20 ++++++++++++++++---- js/Pond.js | 4 +--- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/css/screen.css b/css/screen.css index 46bcd45..cd9a860 100644 --- a/css/screen.css +++ b/css/screen.css @@ -165,6 +165,7 @@ canvas { list-style-type: none; margin: 0; padding: 0; + word-wrap: break-word; } #list p { @@ -284,10 +285,14 @@ canvas { float: left; } -#full article header p:nth-child(3) { +#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; diff --git a/index.html b/index.html index feaa4f4..ff6889e 100644 --- a/index.html +++ b/index.html @@ -94,8 +94,8 @@

-

+

diff --git a/js/App.js b/js/App.js index be5ebe6..593ba29 100644 --- a/js/App.js +++ b/js/App.js @@ -7,6 +7,16 @@ function App() { if(!color) color = "red"; this.setColor(color); this.fontChange(); + + var _this = this; + + window.onkeydown = function(e) { + if(e.keyCode == 39) { + _this.showNext(); + } else if(e.keyCode == 37) { + _this.showPrevious(); + } + } }; App.prototype.authenticate = function() { @@ -276,9 +286,9 @@ App.prototype.showFull = function(article, slide_back) { $(page_id + " .date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000)).toLocaleString(); - var title = $(page_id + " .title"); - title.innerHTML = article.title; - title.href = article.link; + var link = $(page_id + " .link"); + link.innerHTML = article.link; + link.href = article.link; $(page_id + " .feed_title").innerHTML = article.feed_title; @@ -286,7 +296,9 @@ App.prototype.showFull = function(article, slide_back) { if(article.author && article.author.length > 0) $(page_id + " .author").innerHTML = "– " + article.author; - $(page_id + " .article").innerHTML = article.content; + var content = article.content; + if(content.replace(/^\s+|\s+$/g,'').length == 0) content = article.title; + $(page_id + " .article").innerHTML = content; $$(page_id + " .article a").forEach(function(o, i) { o.target = "_blank"; }); diff --git a/js/Pond.js b/js/Pond.js index 8765ce2..1c424a6 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -76,7 +76,7 @@ Pond.prototype.reload = function(callback) { Pond.prototype.getUnreadFeeds = function(callback, skip) { var options = { - status: "all", + status: "unread", limit: 100 }; @@ -134,8 +134,6 @@ Pond.prototype.normalizeArticle = function(article) { var timestamp = new Date(article.published_at).getTime() / 1000; - console.log(article.read) - return { id: article.id, guid_hash: article.url + article.id, From a70306da82626b9f8cdc2c632fbd06c8a7610f00 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 11:55:25 +0100 Subject: [PATCH 08/37] added keyboard shortcuts --- js/App.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/js/App.js b/js/App.js index 593ba29..d2cb11e 100644 --- a/js/App.js +++ b/js/App.js @@ -15,6 +15,10 @@ function App() { _this.showNext(); } else if(e.keyCode == 37) { _this.showPrevious(); + } else if(e.keyCode == 13) { + _this.openInBrowser(); + } else if(e.keyCode == 82) { + _this.reload(); } } }; @@ -298,7 +302,7 @@ App.prototype.showFull = function(article, slide_back) { var content = article.content; if(content.replace(/^\s+|\s+$/g,'').length == 0) content = article.title; - $(page_id + " .article").innerHTML = content; + $(page_id + " .article").innerHTML = content.urlify(); $$(page_id + " .article a").forEach(function(o, i) { o.target = "_blank"; }); @@ -339,6 +343,10 @@ App.prototype.showPrevious = function() { } }; +App.prototype.openInBrowser = function() { + $("#full .link").click(); +}; + App.prototype.setCurrentRead = function() { var article = this.unread_articles[this.currentIndex]; if(!article) return; // happens if we're not on a full article site From 348725476bfbb052cf01ac8629d231c359a6f71d Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 21 Feb 2014 12:05:10 +0100 Subject: [PATCH 09/37] fixed urlify --- js/application.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/application.js b/js/application.js index e20c39b..ebacb08 100644 --- a/js/application.js +++ b/js/application.js @@ -64,8 +64,8 @@ String.prototype.capitalize = function() { } String.prototype.urlify = function() { - var exp = /^\>(\b(http):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; - return this.replace(exp,"$1"); + var exp = /[^\>](https?:\/\/[^\s\<]*)/ig; + return this.replace(exp," $1"); } if(!window.app) window.app = new App(); From bc1c99354f037294201a669b86b97a8f57fc75b6 Mon Sep 17 00:00:00 2001 From: Alvarord Date: Tue, 29 Apr 2014 04:56:43 +0200 Subject: [PATCH 10/37] Adding identificator to show it in the interface, developing branch. --- manifest.webapp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.webapp b/manifest.webapp index 2149404..c4926f4 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -19,5 +19,5 @@ } }, "installs_allowed_from": ["*"], - "version": "0.3.0" -} \ No newline at end of file + "version": "0.3.0_iss30" +} From cfad4d4f4d4bd8fe547a7d78b997513eea0cb4e8 Mon Sep 17 00:00:00 2001 From: AlvaroRD Date: Tue, 6 May 2014 00:12:59 +0200 Subject: [PATCH 11/37] 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 --- js/App.js | 59 ++++++++++++++++++++++++++++++++++++++++++----- js/OwnCloud.js | 8 +++---- js/Pond.js | 10 ++++---- js/TinyTinyRSS.js | 7 +++--- 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/js/App.js b/js/App.js index c874b84..37fb558 100644 --- a/js/App.js +++ b/js/App.js @@ -7,6 +7,13 @@ function App() { if(!color) color = "red"; this.setColor(color); this.fontChange(); + + var numArticles = localStorage.numArticles; + if(!numArticles) numArticles = 50; + this.numArticles(numArticles); + var maxDownload = localStorage.maxDownload; + if(!maxDownload) maxDownload = 500000; + this.maxDownload(maxDownload); }; App.prototype.authenticate = function() { @@ -99,6 +106,14 @@ App.prototype.after_login = function(backend) { } else { this.backend = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id); } + + var numArticles = localStorage.numArticles; + if(!numArticles) numArticles = 50; + this.numArticles(numArticles); + var maxDownload = localStorage.maxDownload; + if(!maxDownload) maxDownload = 500000; + this.maxDownload(maxDownload); + this.reload(); }; @@ -137,7 +152,8 @@ App.prototype.setColor = function(color) { App.prototype.reload = function() { this.unread_articles = []; $("#all-read").innerHTML = "❌"; - 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) { @@ -155,11 +171,25 @@ App.prototype.gotUnreadFeeds = function(new_articles) { this.unread_articles = this.unread_articles.concat(new_articles); if(new_articles.length > 0) { - this.backend.getUnreadFeeds(this.gotUnreadFeeds.bind(this), this.unread_articles); - } else { - localStorage.unread_articles = JSON.stringify(this.unread_articles); + try { + //To check if when it fails it is the same + localStorage.unread_articles = JSON.stringify(this.unread_articles); + //alert("Size probando:"+probando.length) + 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(); - } + } else { + alert("Downloaded: " + this.unread_articles.length + " articles. Reached: " + localStorage.unread_articles.length + " bytes"); + } } }; @@ -445,5 +475,22 @@ App.prototype.fontChange = function(size) { 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; + } }; diff --git a/js/OwnCloud.js b/js/OwnCloud.js index 15776f1..25519eb 100644 --- a/js/OwnCloud.js +++ b/js/OwnCloud.js @@ -65,18 +65,18 @@ OwnCloud.prototype.doOperation = function(method, operation, new_options, callba xhr.send(body); } -OwnCloud.prototype.reload = function(callback) { +OwnCloud.prototype.reload = function(callback,limit) { var _this = this; - this.getFeeds(function() { _this.getUnreadFeeds(callback); }); + this.getFeeds(function() { _this.getUnreadFeeds(callback,0,limit); }); }; -OwnCloud.prototype.getUnreadFeeds = function(callback, skip) { +OwnCloud.prototype.getUnreadFeeds = function(callback, skip, limit) { if(skip) { skip = skip[skip.length - 1].id; } var options = { - batchSize: 700, + batchSize: limit || 700, offset: skip || 0, type: 3, id: 0, diff --git a/js/Pond.js b/js/Pond.js index 501494c..47cc5a5 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -69,15 +69,15 @@ Pond.prototype.doOperation = function(method, operation, new_options, callback) }; -Pond.prototype.reload = function(callback) { +Pond.prototype.reload = function(callback,limit) { 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 = { status: "unread", - limit: 100 + limit: limit || 100 }; if(skip && skip.length > 0) { @@ -222,4 +222,4 @@ Pond.login = function(server_url, user, password, callback) { xhr.setRequestHeader("Content-Length", options.length); xhr.setRequestHeader("Connection", "close"); xhr.send(options); -} \ No newline at end of file +} diff --git a/js/TinyTinyRSS.js b/js/TinyTinyRSS.js index 4479f49..1fd2813 100644 --- a/js/TinyTinyRSS.js +++ b/js/TinyTinyRSS.js @@ -54,17 +54,18 @@ TinyTinyRSS.prototype.doOperation = function(operation, new_options, callback) { xhr.send(JSON.stringify(options)); } -TinyTinyRSS.prototype.reload = function(callback) { - this.getUnreadFeeds(callback, []); +TinyTinyRSS.prototype.reload = function(callback,limit) { + this.getUnreadFeeds(callback, 0, limit); }; -TinyTinyRSS.prototype.getUnreadFeeds = function(callback, skip) { +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 }; From d99928908eec9949ba34c30aeb4c924096291d5e Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 19:43:42 +0200 Subject: [PATCH 12/37] added method to logOut doOperation --- js/Pond.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/Pond.js b/js/Pond.js index 501494c..94e5485 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -106,6 +106,8 @@ Pond.prototype.getUnreadFeeds = function(callback, skip) { Pond.prototype.getFeeds = function(callback) { var _this = this; this.doOperation("GET", "subscriptions", {}, function(feeds) { + + if(!feeds) return; _this.feeds = {}; for (var i = 0; i < feeds.length; i++) { @@ -193,7 +195,7 @@ Pond.prototype.setArticleUnstarred = function(articles, callback) { } Pond.prototype.logOut = function() { - this.doOperation("auth/sessions/" + this.session_token ); + this.doOperation("DELETE", "auth/sessions/" + this.session_token ); localStorage.feeds = null; } From 28ce177e178f906fe61c424a83fb41fa3110dbb8 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 20:11:54 +0200 Subject: [PATCH 13/37] replaced jetser with hammertime which hopefully fixes #32 --- index.html | 4 +- js/App.js | 12 +- js/hammer.js | 2163 ++++++++++++++++++++++++++++++++++++++++++++++++++ js/jester.js | 599 -------------- 4 files changed, 2172 insertions(+), 606 deletions(-) create mode 100644 js/hammer.js delete mode 100644 js/jester.js diff --git a/index.html b/index.html index feaa4f4..0a79f6a 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + FeedMonkey @@ -15,7 +15,7 @@ - + diff --git a/js/App.js b/js/App.js index c874b84..abcd735 100644 --- a/js/App.js +++ b/js/App.js @@ -84,11 +84,13 @@ App.prototype.after_login = function(backend) { } // set up swiping - jester($("#full")).flick(function(touches, direction) { - if(direction == "left") _this.showNext(); - else _this.showPrevious(); - }); - + var options = { + dragLockToAxis: true, + dragBlockHorizontal: true + }; + var hammertime = new Hammer($("#full"), options); + hammertime.on("swipeleft", function(ev){ _this.showNext() }); + hammertime.on("swiperight", function(ev){ _this.showPrevious(); }); this.changeToPage("#list"); diff --git a/js/hammer.js b/js/hammer.js new file mode 100644 index 0000000..e03acdc --- /dev/null +++ b/js/hammer.js @@ -0,0 +1,2163 @@ +/*! Hammer.JS - v1.1.3 - 2014-05-22 + * http://eightmedia.github.io/hammer.js + * + * Copyright (c) 2014 Jorik Tangelder ; + * Licensed under the MIT license */ + +(function(window, undefined) { + 'use strict'; + +/** + * @main + * @module hammer + * + * @class Hammer + * @static + */ + +/** + * Hammer, use this to create instances + * ```` + * var hammertime = new Hammer(myElement); + * ```` + * + * @method Hammer + * @param {HTMLElement} element + * @param {Object} [options={}] + * @return {Hammer.Instance} + */ +var Hammer = function Hammer(element, options) { + return new Hammer.Instance(element, options || {}); +}; + +/** + * version, as defined in package.json + * the value will be set at each build + * @property VERSION + * @final + * @type {String} + */ +Hammer.VERSION = '1.1.3'; + +/** + * default settings. + * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled + * by setting it's name (like `swipe`) to false. + * You can set the defaults for all instances by changing this object before creating an instance. + * @example + * ```` + * Hammer.defaults.drag = false; + * Hammer.defaults.behavior.touchAction = 'pan-y'; + * delete Hammer.defaults.behavior.userSelect; + * ```` + * @property defaults + * @type {Object} + */ +Hammer.defaults = { + /** + * this setting object adds styles and attributes to the element to prevent the browser from doing + * its native behavior. The css properties are auto prefixed for the browsers when needed. + * @property defaults.behavior + * @type {Object} + */ + behavior: { + /** + * Disables text selection to improve the dragging gesture. When the value is `none` it also sets + * `onselectstart=false` for IE on the element. Mainly for desktop browsers. + * @property defaults.behavior.userSelect + * @type {String} + * @default 'none' + */ + userSelect: 'none', + + /** + * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming). + * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event. + * @property defaults.behavior.touchAction + * @type {String} + * @default: 'pan-y' + */ + touchAction: 'pan-y', + + /** + * Disables the default callout shown when you touch and hold a touch target. + * On iOS, when you touch and hold a touch target such as a link, Safari displays + * a callout containing information about the link. This property allows you to disable that callout. + * @property defaults.behavior.touchCallout + * @type {String} + * @default 'none' + */ + touchCallout: 'none', + + /** + * Specifies whether zooming is enabled. Used by IE10> + * @property defaults.behavior.contentZooming + * @type {String} + * @default 'none' + */ + contentZooming: 'none', + + /** + * Specifies that an entire element should be draggable instead of its contents. + * Mainly for desktop browsers. + * @property defaults.behavior.userDrag + * @type {String} + * @default 'none' + */ + userDrag: 'none', + + /** + * Overrides the highlight color shown when the user taps a link or a JavaScript + * clickable element in Safari on iPhone. This property obeys the alpha value, if specified. + * + * If you don't specify an alpha value, Safari on iPhone applies a default alpha value + * to the color. To disable tap highlighting, set the alpha value to 0 (invisible). + * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped. + * @property defaults.behavior.tapHighlightColor + * @type {String} + * @default 'rgba(0,0,0,0)' + */ + tapHighlightColor: 'rgba(0,0,0,0)' + } +}; + +/** + * hammer document where the base events are added at + * @property DOCUMENT + * @type {HTMLElement} + * @default window.document + */ +Hammer.DOCUMENT = document; + +/** + * detect support for pointer events + * @property HAS_POINTEREVENTS + * @type {Boolean} + */ +Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled; + +/** + * detect support for touch events + * @property HAS_TOUCHEVENTS + * @type {Boolean} + */ +Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window); + +/** + * detect mobile browsers + * @property IS_MOBILE + * @type {Boolean} + */ +Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent); + +/** + * detect if we want to support mouseevents at all + * @property NO_MOUSEEVENTS + * @type {Boolean} + */ +Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS; + +/** + * interval in which Hammer recalculates current velocity/direction/angle in ms + * @property CALCULATE_INTERVAL + * @type {Number} + * @default 25 + */ +Hammer.CALCULATE_INTERVAL = 25; + +/** + * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup` + * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`) + * @property EVENT_TYPES + * @private + * @writeOnce + * @type {Object} + */ +var EVENT_TYPES = {}; + +/** + * direction strings, for safe comparisons + * @property DIRECTION_DOWN|LEFT|UP|RIGHT + * @final + * @type {String} + * @default 'down' 'left' 'up' 'right' + */ +var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down'; +var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left'; +var DIRECTION_UP = Hammer.DIRECTION_UP = 'up'; +var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right'; + +/** + * pointertype strings, for safe comparisons + * @property POINTER_MOUSE|TOUCH|PEN + * @final + * @type {String} + * @default 'mouse' 'touch' 'pen' + */ +var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse'; +var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch'; +var POINTER_PEN = Hammer.POINTER_PEN = 'pen'; + +/** + * eventtypes + * @property EVENT_START|MOVE|END|RELEASE|TOUCH + * @final + * @type {String} + * @default 'start' 'change' 'move' 'end' 'release' 'touch' + */ +var EVENT_START = Hammer.EVENT_START = 'start'; +var EVENT_MOVE = Hammer.EVENT_MOVE = 'move'; +var EVENT_END = Hammer.EVENT_END = 'end'; +var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release'; +var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch'; + +/** + * if the window events are set... + * @property READY + * @writeOnce + * @type {Boolean} + * @default false + */ +Hammer.READY = false; + +/** + * plugins namespace + * @property plugins + * @type {Object} + */ +Hammer.plugins = Hammer.plugins || {}; + +/** + * gestures namespace + * see `/gestures` for the definitions + * @property gestures + * @type {Object} + */ +Hammer.gestures = Hammer.gestures || {}; + +/** + * setup events to detect gestures on the document + * this function is called when creating an new instance + * @private + */ +function setup() { + if(Hammer.READY) { + return; + } + + // find what eventtypes we add listeners to + Event.determineEventTypes(); + + // Register all gestures inside Hammer.gestures + Utils.each(Hammer.gestures, function(gesture) { + Detection.register(gesture); + }); + + // Add touch events on the document + Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect); + Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect); + + // Hammer is ready...! + Hammer.READY = true; +} + +/** + * @module hammer + * + * @class Utils + * @static + */ +var Utils = Hammer.utils = { + /** + * extend method, could also be used for cloning when `dest` is an empty object. + * changes the dest object + * @method extend + * @param {Object} dest + * @param {Object} src + * @param {Boolean} [merge=false] do a merge + * @return {Object} dest + */ + extend: function extend(dest, src, merge) { + for(var key in src) { + if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) { + continue; + } + dest[key] = src[key]; + } + return dest; + }, + + /** + * simple addEventListener wrapper + * @method on + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + */ + on: function on(element, type, handler) { + element.addEventListener(type, handler, false); + }, + + /** + * simple removeEventListener wrapper + * @method off + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + */ + off: function off(element, type, handler) { + element.removeEventListener(type, handler, false); + }, + + /** + * forEach over arrays and objects + * @method each + * @param {Object|Array} obj + * @param {Function} iterator + * @param {any} iterator.item + * @param {Number} iterator.index + * @param {Object|Array} iterator.obj the source object + * @param {Object} context value to use as `this` in the iterator + */ + each: function each(obj, iterator, context) { + var i, len; + + // native forEach on arrays + if('forEach' in obj) { + obj.forEach(iterator, context); + // arrays + } else if(obj.length !== undefined) { + for(i = 0, len = obj.length; i < len; i++) { + if(iterator.call(context, obj[i], i, obj) === false) { + return; + } + } + // objects + } else { + for(i in obj) { + if(obj.hasOwnProperty(i) && + iterator.call(context, obj[i], i, obj) === false) { + return; + } + } + } + }, + + /** + * find if a string contains the string using indexOf + * @method inStr + * @param {String} src + * @param {String} find + * @return {Boolean} found + */ + inStr: function inStr(src, find) { + return src.indexOf(find) > -1; + }, + + /** + * find if a array contains the object using indexOf or a simple polyfill + * @method inArray + * @param {String} src + * @param {String} find + * @return {Boolean|Number} false when not found, or the index + */ + inArray: function inArray(src, find) { + if(src.indexOf) { + var index = src.indexOf(find); + return (index === -1) ? false : index; + } else { + for(var i = 0, len = src.length; i < len; i++) { + if(src[i] === find) { + return i; + } + } + return false; + } + }, + + /** + * convert an array-like object (`arguments`, `touchlist`) to an array + * @method toArray + * @param {Object} obj + * @return {Array} + */ + toArray: function toArray(obj) { + return Array.prototype.slice.call(obj, 0); + }, + + /** + * find if a node is in the given parent + * @method hasParent + * @param {HTMLElement} node + * @param {HTMLElement} parent + * @return {Boolean} found + */ + hasParent: function hasParent(node, parent) { + while(node) { + if(node == parent) { + return true; + } + node = node.parentNode; + } + return false; + }, + + /** + * get the center of all the touches + * @method getCenter + * @param {Array} touches + * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties + */ + getCenter: function getCenter(touches) { + var pageX = [], + pageY = [], + clientX = [], + clientY = [], + min = Math.min, + max = Math.max; + + // no need to loop when only one touch + if(touches.length === 1) { + return { + pageX: touches[0].pageX, + pageY: touches[0].pageY, + clientX: touches[0].clientX, + clientY: touches[0].clientY + }; + } + + Utils.each(touches, function(touch) { + pageX.push(touch.pageX); + pageY.push(touch.pageY); + clientX.push(touch.clientX); + clientY.push(touch.clientY); + }); + + return { + pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2, + pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2, + clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2, + clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2 + }; + }, + + /** + * calculate the velocity between two points. unit is in px per ms. + * @method getVelocity + * @param {Number} deltaTime + * @param {Number} deltaX + * @param {Number} deltaY + * @return {Object} velocity `x` and `y` + */ + getVelocity: function getVelocity(deltaTime, deltaX, deltaY) { + return { + x: Math.abs(deltaX / deltaTime) || 0, + y: Math.abs(deltaY / deltaTime) || 0 + }; + }, + + /** + * calculate the angle between two coordinates + * @method getAngle + * @param {Touch} touch1 + * @param {Touch} touch2 + * @return {Number} angle + */ + getAngle: function getAngle(touch1, touch2) { + var x = touch2.clientX - touch1.clientX, + y = touch2.clientY - touch1.clientY; + + return Math.atan2(y, x) * 180 / Math.PI; + }, + + /** + * do a small comparision to get the direction between two touches. + * @method getDirection + * @param {Touch} touch1 + * @param {Touch} touch2 + * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN` + */ + getDirection: function getDirection(touch1, touch2) { + var x = Math.abs(touch1.clientX - touch2.clientX), + y = Math.abs(touch1.clientY - touch2.clientY); + + if(x >= y) { + return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + } + return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN; + }, + + /** + * calculate the distance between two touches + * @method getDistance + * @param {Touch}touch1 + * @param {Touch} touch2 + * @return {Number} distance + */ + getDistance: function getDistance(touch1, touch2) { + var x = touch2.clientX - touch1.clientX, + y = touch2.clientY - touch1.clientY; + + return Math.sqrt((x * x) + (y * y)); + }, + + /** + * calculate the scale factor between two touchLists + * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out + * @method getScale + * @param {Array} start array of touches + * @param {Array} end array of touches + * @return {Number} scale + */ + getScale: function getScale(start, end) { + // need two fingers... + if(start.length >= 2 && end.length >= 2) { + return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]); + } + return 1; + }, + + /** + * calculate the rotation degrees between two touchLists + * @method getRotation + * @param {Array} start array of touches + * @param {Array} end array of touches + * @return {Number} rotation + */ + getRotation: function getRotation(start, end) { + // need two fingers + if(start.length >= 2 && end.length >= 2) { + return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]); + } + return 0; + }, + + /** + * find out if the direction is vertical * + * @method isVertical + * @param {String} direction matches `DIRECTION_UP|DOWN` + * @return {Boolean} is_vertical + */ + isVertical: function isVertical(direction) { + return direction == DIRECTION_UP || direction == DIRECTION_DOWN; + }, + + /** + * set css properties with their prefixes + * @param {HTMLElement} element + * @param {String} prop + * @param {String} value + * @param {Boolean} [toggle=true] + * @return {Boolean} + */ + setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) { + var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms']; + prop = Utils.toCamelCase(prop); + + for(var i = 0; i < prefixes.length; i++) { + var p = prop; + // prefixes + if(prefixes[i]) { + p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1); + } + + // test the style + if(p in element.style) { + element.style[p] = (toggle == null || toggle) && value || ''; + break; + } + } + }, + + /** + * toggle browser default behavior by setting css properties. + * `userSelect='none'` also sets `element.onselectstart` to false + * `userDrag='none'` also sets `element.ondragstart` to false + * + * @method toggleBehavior + * @param {HtmlElement} element + * @param {Object} props + * @param {Boolean} [toggle=true] + */ + toggleBehavior: function toggleBehavior(element, props, toggle) { + if(!props || !element || !element.style) { + return; + } + + // set the css properties + Utils.each(props, function(value, prop) { + Utils.setPrefixedCss(element, prop, value, toggle); + }); + + var falseFn = toggle && function() { + return false; + }; + + // also the disable onselectstart + if(props.userSelect == 'none') { + element.onselectstart = falseFn; + } + // and disable ondragstart + if(props.userDrag == 'none') { + element.ondragstart = falseFn; + } + }, + + /** + * convert a string with underscores to camelCase + * so prevent_default becomes preventDefault + * @param {String} str + * @return {String} camelCaseStr + */ + toCamelCase: function toCamelCase(str) { + return str.replace(/[_-]([a-z])/g, function(s) { + return s[1].toUpperCase(); + }); + } +}; + + +/** + * @module hammer + */ +/** + * @class Event + * @static + */ +var Event = Hammer.event = { + /** + * when touch events have been fired, this is true + * this is used to stop mouse events + * @property prevent_mouseevents + * @private + * @type {Boolean} + */ + preventMouseEvents: false, + + /** + * if EVENT_START has been fired + * @property started + * @private + * @type {Boolean} + */ + started: false, + + /** + * when the mouse is hold down, this is true + * @property should_detect + * @private + * @type {Boolean} + */ + shouldDetect: false, + + /** + * simple event binder with a hook and support for multiple types + * @method on + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + * @param {Function} [hook] + * @param {Object} hook.type + */ + on: function on(element, type, handler, hook) { + var types = type.split(' '); + Utils.each(types, function(type) { + Utils.on(element, type, handler); + hook && hook(type); + }); + }, + + /** + * simple event unbinder with a hook and support for multiple types + * @method off + * @param {HTMLElement} element + * @param {String} type + * @param {Function} handler + * @param {Function} [hook] + * @param {Object} hook.type + */ + off: function off(element, type, handler, hook) { + var types = type.split(' '); + Utils.each(types, function(type) { + Utils.off(element, type, handler); + hook && hook(type); + }); + }, + + /** + * the core touch event handler. + * this finds out if we should to detect gestures + * @method onTouch + * @param {HTMLElement} element + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {Function} handler + * @return onTouchHandler {Function} the core event handler + */ + onTouch: function onTouch(element, eventType, handler) { + var self = this; + + var onTouchHandler = function onTouchHandler(ev) { + var srcType = ev.type.toLowerCase(), + isPointer = Hammer.HAS_POINTEREVENTS, + isMouse = Utils.inStr(srcType, 'mouse'), + triggerType; + + // if we are in a mouseevent, but there has been a touchevent triggered in this session + // we want to do nothing. simply break out of the event. + if(isMouse && self.preventMouseEvents) { + return; + + // mousebutton must be down + } else if(isMouse && eventType == EVENT_START && ev.button === 0) { + self.preventMouseEvents = false; + self.shouldDetect = true; + } else if(isPointer && eventType == EVENT_START) { + self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev)); + // just a valid start event, but no mouse + } else if(!isMouse && eventType == EVENT_START) { + self.preventMouseEvents = true; + self.shouldDetect = true; + } + + // update the pointer event before entering the detection + if(isPointer && eventType != EVENT_END) { + PointerEvent.updatePointer(eventType, ev); + } + + // we are in a touch/down state, so allowed detection of gestures + if(self.shouldDetect) { + triggerType = self.doDetect.call(self, ev, eventType, element, handler); + } + + // ...and we are done with the detection + // so reset everything to start each detection totally fresh + if(triggerType == EVENT_END) { + self.preventMouseEvents = false; + self.shouldDetect = false; + PointerEvent.reset(); + // update the pointerevent object after the detection + } + + if(isPointer && eventType == EVENT_END) { + PointerEvent.updatePointer(eventType, ev); + } + }; + + this.on(element, EVENT_TYPES[eventType], onTouchHandler); + return onTouchHandler; + }, + + /** + * the core detection method + * this finds out what hammer-touch-events to trigger + * @method doDetect + * @param {Object} ev + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {HTMLElement} element + * @param {Function} handler + * @return {String} triggerType matches `EVENT_START|MOVE|END` + */ + doDetect: function doDetect(ev, eventType, element, handler) { + var touchList = this.getTouchList(ev, eventType); + var touchListLength = touchList.length; + var triggerType = eventType; + var triggerChange = touchList.trigger; // used by fakeMultitouch plugin + var changedLength = touchListLength; + + // at each touchstart-like event we want also want to trigger a TOUCH event... + if(eventType == EVENT_START) { + triggerChange = EVENT_TOUCH; + // ...the same for a touchend-like event + } else if(eventType == EVENT_END) { + triggerChange = EVENT_RELEASE; + + // keep track of how many touches have been removed + changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1); + } + + // after there are still touches on the screen, + // we just want to trigger a MOVE event. so change the START or END to a MOVE + // but only after detection has been started, the first time we actualy want a START + if(changedLength > 0 && this.started) { + triggerType = EVENT_MOVE; + } + + // detection has been started, we keep track of this, see above + this.started = true; + + // generate some event data, some basic information + var evData = this.collectEventData(element, triggerType, touchList, ev); + + // trigger the triggerType event before the change (TOUCH, RELEASE) events + // but the END event should be at last + if(eventType != EVENT_END) { + handler.call(Detection, evData); + } + + // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed + if(triggerChange) { + evData.changedLength = changedLength; + evData.eventType = triggerChange; + + handler.call(Detection, evData); + + evData.eventType = triggerType; + delete evData.changedLength; + } + + // trigger the END event + if(triggerType == EVENT_END) { + handler.call(Detection, evData); + + // ...and we are done with the detection + // so reset everything to start each detection totally fresh + this.started = false; + } + + return triggerType; + }, + + /** + * we have different events for each device/browser + * determine what we need and set them in the EVENT_TYPES constant + * the `onTouch` method is bind to these properties. + * @method determineEventTypes + * @return {Object} events + */ + determineEventTypes: function determineEventTypes() { + var types; + if(Hammer.HAS_POINTEREVENTS) { + if(window.PointerEvent) { + types = [ + 'pointerdown', + 'pointermove', + 'pointerup pointercancel lostpointercapture' + ]; + } else { + types = [ + 'MSPointerDown', + 'MSPointerMove', + 'MSPointerUp MSPointerCancel MSLostPointerCapture' + ]; + } + } else if(Hammer.NO_MOUSEEVENTS) { + types = [ + 'touchstart', + 'touchmove', + 'touchend touchcancel' + ]; + } else { + types = [ + 'touchstart mousedown', + 'touchmove mousemove', + 'touchend touchcancel mouseup' + ]; + } + + EVENT_TYPES[EVENT_START] = types[0]; + EVENT_TYPES[EVENT_MOVE] = types[1]; + EVENT_TYPES[EVENT_END] = types[2]; + return EVENT_TYPES; + }, + + /** + * create touchList depending on the event + * @method getTouchList + * @param {Object} ev + * @param {String} eventType + * @return {Array} touches + */ + getTouchList: function getTouchList(ev, eventType) { + // get the fake pointerEvent touchlist + if(Hammer.HAS_POINTEREVENTS) { + return PointerEvent.getTouchList(); + } + + // get the touchlist + if(ev.touches) { + if(eventType == EVENT_MOVE) { + return ev.touches; + } + + var identifiers = []; + var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches)); + var touchList = []; + + Utils.each(concat, function(touch) { + if(Utils.inArray(identifiers, touch.identifier) === false) { + touchList.push(touch); + } + identifiers.push(touch.identifier); + }); + + return touchList; + } + + // make fake touchList from mouse position + ev.identifier = 1; + return [ev]; + }, + + /** + * collect basic event data + * @method collectEventData + * @param {HTMLElement} element + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {Array} touches + * @param {Object} ev + * @return {Object} ev + */ + collectEventData: function collectEventData(element, eventType, touches, ev) { + // find out pointerType + var pointerType = POINTER_TOUCH; + if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) { + pointerType = POINTER_MOUSE; + } else if(PointerEvent.matchType(POINTER_PEN, ev)) { + pointerType = POINTER_PEN; + } + + return { + center: Utils.getCenter(touches), + timeStamp: Date.now(), + target: ev.target, + touches: touches, + eventType: eventType, + pointerType: pointerType, + srcEvent: ev, + + /** + * prevent the browser default actions + * mostly used to disable scrolling of the browser + */ + preventDefault: function() { + var srcEvent = this.srcEvent; + srcEvent.preventManipulation && srcEvent.preventManipulation(); + srcEvent.preventDefault && srcEvent.preventDefault(); + }, + + /** + * stop bubbling the event up to its parents + */ + stopPropagation: function() { + this.srcEvent.stopPropagation(); + }, + + /** + * immediately stop gesture detection + * might be useful after a swipe was detected + * @return {*} + */ + stopDetect: function() { + return Detection.stopDetect(); + } + }; + } +}; + + +/** + * @module hammer + * + * @class PointerEvent + * @static + */ +var PointerEvent = Hammer.PointerEvent = { + /** + * holds all pointers, by `identifier` + * @property pointers + * @type {Object} + */ + pointers: {}, + + /** + * get the pointers as an array + * @method getTouchList + * @return {Array} touchlist + */ + getTouchList: function getTouchList() { + var touchlist = []; + // we can use forEach since pointerEvents only is in IE10 + Utils.each(this.pointers, function(pointer) { + touchlist.push(pointer); + }); + + return touchlist; + }, + + /** + * update the position of a pointer + * @method updatePointer + * @param {String} eventType matches `EVENT_START|MOVE|END` + * @param {Object} pointerEvent + */ + updatePointer: function updatePointer(eventType, pointerEvent) { + if(eventType == EVENT_END) { + delete this.pointers[pointerEvent.pointerId]; + } else { + pointerEvent.identifier = pointerEvent.pointerId; + this.pointers[pointerEvent.pointerId] = pointerEvent; + } + }, + + /** + * check if ev matches pointertype + * @method matchType + * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN` + * @param {PointerEvent} ev + */ + matchType: function matchType(pointerType, ev) { + if(!ev.pointerType) { + return false; + } + + var pt = ev.pointerType, + types = {}; + + types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE)); + types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH)); + types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN)); + return types[pointerType]; + }, + + /** + * reset the stored pointers + * @method reset + */ + reset: function resetList() { + this.pointers = {}; + } +}; + + +/** + * @module hammer + * + * @class Detection + * @static + */ +var Detection = Hammer.detection = { + // contains all registred Hammer.gestures in the correct order + gestures: [], + + // data of the current Hammer.gesture detection session + current: null, + + // the previous Hammer.gesture session data + // is a full clone of the previous gesture.current object + previous: null, + + // when this becomes true, no gestures are fired + stopped: false, + + /** + * start Hammer.gesture detection + * @method startDetect + * @param {Hammer.Instance} inst + * @param {Object} eventData + */ + startDetect: function startDetect(inst, eventData) { + // already busy with a Hammer.gesture detection on an element + if(this.current) { + return; + } + + this.stopped = false; + + // holds current session + this.current = { + inst: inst, // reference to HammerInstance we're working for + startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc + lastEvent: false, // last eventData + lastCalcEvent: false, // last eventData for calculations. + futureCalcEvent: false, // last eventData for calculations. + lastCalcData: {}, // last lastCalcData + name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc + }; + + this.detect(eventData); + }, + + /** + * Hammer.gesture detection + * @method detect + * @param {Object} eventData + * @return {any} + */ + detect: function detect(eventData) { + if(!this.current || this.stopped) { + return; + } + + // extend event data with calculations about scale, distance etc + eventData = this.extendEventData(eventData); + + // hammer instance and instance options + var inst = this.current.inst, + instOptions = inst.options; + + // call Hammer.gesture handlers + Utils.each(this.gestures, function triggerGesture(gesture) { + // only when the instance options have enabled this gesture + if(!this.stopped && inst.enabled && instOptions[gesture.name]) { + gesture.handler.call(gesture, eventData, inst); + } + }, this); + + // store as previous event event + if(this.current) { + this.current.lastEvent = eventData; + } + + if(eventData.eventType == EVENT_END) { + this.stopDetect(); + } + + return eventData; + }, + + /** + * clear the Hammer.gesture vars + * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected + * to stop other Hammer.gestures from being fired + * @method stopDetect + */ + stopDetect: function stopDetect() { + // clone current data to the store as the previous gesture + // used for the double tap gesture, since this is an other gesture detect session + this.previous = Utils.extend({}, this.current); + + // reset the current + this.current = null; + this.stopped = true; + }, + + /** + * calculate velocity, angle and direction + * @method getVelocityData + * @param {Object} ev + * @param {Object} center + * @param {Number} deltaTime + * @param {Number} deltaX + * @param {Number} deltaY + */ + getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) { + var cur = this.current, + recalc = false, + calcEv = cur.lastCalcEvent, + calcData = cur.lastCalcData; + + if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) { + center = calcEv.center; + deltaTime = ev.timeStamp - calcEv.timeStamp; + deltaX = ev.center.clientX - calcEv.center.clientX; + deltaY = ev.center.clientY - calcEv.center.clientY; + recalc = true; + } + + if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) { + cur.futureCalcEvent = ev; + } + + if(!cur.lastCalcEvent || recalc) { + calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY); + calcData.angle = Utils.getAngle(center, ev.center); + calcData.direction = Utils.getDirection(center, ev.center); + + cur.lastCalcEvent = cur.futureCalcEvent || ev; + cur.futureCalcEvent = ev; + } + + ev.velocityX = calcData.velocity.x; + ev.velocityY = calcData.velocity.y; + ev.interimAngle = calcData.angle; + ev.interimDirection = calcData.direction; + }, + + /** + * extend eventData for Hammer.gestures + * @method extendEventData + * @param {Object} ev + * @return {Object} ev + */ + extendEventData: function extendEventData(ev) { + var cur = this.current, + startEv = cur.startEvent, + lastEv = cur.lastEvent || startEv; + + // update the start touchlist to calculate the scale/rotation + if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) { + startEv.touches = []; + Utils.each(ev.touches, function(touch) { + startEv.touches.push({ + clientX: touch.clientX, + clientY: touch.clientY + }); + }); + } + + var deltaTime = ev.timeStamp - startEv.timeStamp, + deltaX = ev.center.clientX - startEv.center.clientX, + deltaY = ev.center.clientY - startEv.center.clientY; + + this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY); + + Utils.extend(ev, { + startEvent: startEv, + + deltaTime: deltaTime, + deltaX: deltaX, + deltaY: deltaY, + + distance: Utils.getDistance(startEv.center, ev.center), + angle: Utils.getAngle(startEv.center, ev.center), + direction: Utils.getDirection(startEv.center, ev.center), + scale: Utils.getScale(startEv.touches, ev.touches), + rotation: Utils.getRotation(startEv.touches, ev.touches) + }); + + return ev; + }, + + /** + * register new gesture + * @method register + * @param {Object} gesture object, see `gestures/` for documentation + * @return {Array} gestures + */ + register: function register(gesture) { + // add an enable gesture options if there is no given + var options = gesture.defaults || {}; + if(options[gesture.name] === undefined) { + options[gesture.name] = true; + } + + // extend Hammer default options with the Hammer.gesture options + Utils.extend(Hammer.defaults, options, true); + + // set its index + gesture.index = gesture.index || 1000; + + // add Hammer.gesture to the list + this.gestures.push(gesture); + + // sort the list by index + this.gestures.sort(function(a, b) { + if(a.index < b.index) { + return -1; + } + if(a.index > b.index) { + return 1; + } + return 0; + }); + + return this.gestures; + } +}; + + +/** + * @module hammer + */ + +/** + * create new hammer instance + * all methods should return the instance itself, so it is chainable. + * + * @class Instance + * @constructor + * @param {HTMLElement} element + * @param {Object} [options={}] options are merged with `Hammer.defaults` + * @return {Hammer.Instance} + */ +Hammer.Instance = function(element, options) { + var self = this; + + // setup HammerJS window events and register all gestures + // this also sets up the default options + setup(); + + /** + * @property element + * @type {HTMLElement} + */ + this.element = element; + + /** + * @property enabled + * @type {Boolean} + * @protected + */ + this.enabled = true; + + /** + * options, merged with the defaults + * options with an _ are converted to camelCase + * @property options + * @type {Object} + */ + Utils.each(options, function(value, name) { + delete options[name]; + options[Utils.toCamelCase(name)] = value; + }); + + this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {}); + + // add some css to the element to prevent the browser from doing its native behavoir + if(this.options.behavior) { + Utils.toggleBehavior(this.element, this.options.behavior, true); + } + + /** + * event start handler on the element to start the detection + * @property eventStartHandler + * @type {Object} + */ + this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) { + if(self.enabled && ev.eventType == EVENT_START) { + Detection.startDetect(self, ev); + } else if(ev.eventType == EVENT_TOUCH) { + Detection.detect(ev); + } + }); + + /** + * keep a list of user event handlers which needs to be removed when calling 'dispose' + * @property eventHandlers + * @type {Array} + */ + this.eventHandlers = []; +}; + +Hammer.Instance.prototype = { + /** + * bind events to the instance + * @method on + * @chainable + * @param {String} gestures multiple gestures by splitting with a space + * @param {Function} handler + * @param {Object} handler.ev event object + */ + on: function onEvent(gestures, handler) { + var self = this; + Event.on(self.element, gestures, handler, function(type) { + self.eventHandlers.push({ gesture: type, handler: handler }); + }); + return self; + }, + + /** + * unbind events to the instance + * @method off + * @chainable + * @param {String} gestures + * @param {Function} handler + */ + off: function offEvent(gestures, handler) { + var self = this; + + Event.off(self.element, gestures, handler, function(type) { + var index = Utils.inArray({ gesture: type, handler: handler }); + if(index !== false) { + self.eventHandlers.splice(index, 1); + } + }); + return self; + }, + + /** + * trigger gesture event + * @method trigger + * @chainable + * @param {String} gesture + * @param {Object} [eventData] + */ + trigger: function triggerEvent(gesture, eventData) { + // optional + if(!eventData) { + eventData = {}; + } + + // create DOM event + var event = Hammer.DOCUMENT.createEvent('Event'); + event.initEvent(gesture, true, true); + event.gesture = eventData; + + // trigger on the target if it is in the instance element, + // this is for event delegation tricks + var element = this.element; + if(Utils.hasParent(eventData.target, element)) { + element = eventData.target; + } + + element.dispatchEvent(event); + return this; + }, + + /** + * enable of disable hammer.js detection + * @method enable + * @chainable + * @param {Boolean} state + */ + enable: function enable(state) { + this.enabled = state; + return this; + }, + + /** + * dispose this hammer instance + * @method dispose + * @return {Null} + */ + dispose: function dispose() { + var i, eh; + + // undo all changes made by stop_browser_behavior + Utils.toggleBehavior(this.element, this.options.behavior, false); + + // unbind all custom event handlers + for(i = -1; (eh = this.eventHandlers[++i]);) { + Utils.off(this.element, eh.gesture, eh.handler); + } + + this.eventHandlers = []; + + // unbind the start event listener + Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler); + + return null; + } +}; + + +/** + * @module gestures + */ +/** + * Move with x fingers (default 1) around on the page. + * Preventing the default browser behavior is a good way to improve feel and working. + * ```` + * hammertime.on("drag", function(ev) { + * console.log(ev); + * ev.gesture.preventDefault(); + * }); + * ```` + * + * @class Drag + * @static + */ +/** + * @event drag + * @param {Object} ev + */ +/** + * @event dragstart + * @param {Object} ev + */ +/** + * @event dragend + * @param {Object} ev + */ +/** + * @event drapleft + * @param {Object} ev + */ +/** + * @event dragright + * @param {Object} ev + */ +/** + * @event dragup + * @param {Object} ev + */ +/** + * @event dragdown + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var triggered = false; + + function dragGesture(ev, inst) { + var cur = Detection.current; + + // max touches + if(inst.options.dragMaxTouches > 0 && + ev.touches.length > inst.options.dragMaxTouches) { + return; + } + + switch(ev.eventType) { + case EVENT_START: + triggered = false; + break; + + case EVENT_MOVE: + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.distance < inst.options.dragMinDistance && + cur.name != name) { + return; + } + + var startCenter = cur.startEvent.center; + + // we are dragging! + if(cur.name != name) { + cur.name = name; + if(inst.options.dragDistanceCorrection && ev.distance > 0) { + // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center. + // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0. + // It might be useful to save the original start point somewhere + var factor = Math.abs(inst.options.dragMinDistance / ev.distance); + startCenter.pageX += ev.deltaX * factor; + startCenter.pageY += ev.deltaY * factor; + startCenter.clientX += ev.deltaX * factor; + startCenter.clientY += ev.deltaY * factor; + + // recalculate event data using new start point + ev = Detection.extendEventData(ev); + } + } + + // lock drag to axis? + if(cur.lastEvent.dragLockToAxis || + ( inst.options.dragLockToAxis && + inst.options.dragLockMinDistance <= ev.distance + )) { + ev.dragLockToAxis = true; + } + + // keep direction on the axis that the drag gesture started on + var lastDirection = cur.lastEvent.direction; + if(ev.dragLockToAxis && lastDirection !== ev.direction) { + if(Utils.isVertical(lastDirection)) { + ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN; + } else { + ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT; + } + } + + // first time, trigger dragstart event + if(!triggered) { + inst.trigger(name + 'start', ev); + triggered = true; + } + + // trigger events + inst.trigger(name, ev); + inst.trigger(name + ev.direction, ev); + + var isVertical = Utils.isVertical(ev.direction); + + // block the browser events + if((inst.options.dragBlockVertical && isVertical) || + (inst.options.dragBlockHorizontal && !isVertical)) { + ev.preventDefault(); + } + break; + + case EVENT_RELEASE: + if(triggered && ev.changedLength <= inst.options.dragMaxTouches) { + inst.trigger(name + 'end', ev); + triggered = false; + } + break; + + case EVENT_END: + triggered = false; + break; + } + } + + Hammer.gestures.Drag = { + name: name, + index: 50, + handler: dragGesture, + defaults: { + /** + * minimal movement that have to be made before the drag event gets triggered + * @property dragMinDistance + * @type {Number} + * @default 10 + */ + dragMinDistance: 10, + + /** + * Set dragDistanceCorrection to true to make the starting point of the drag + * be calculated from where the drag was triggered, not from where the touch started. + * Useful to avoid a jerk-starting drag, which can make fine-adjustments + * through dragging difficult, and be visually unappealing. + * @property dragDistanceCorrection + * @type {Boolean} + * @default true + */ + dragDistanceCorrection: true, + + /** + * set 0 for unlimited, but this can conflict with transform + * @property dragMaxTouches + * @type {Number} + * @default 1 + */ + dragMaxTouches: 1, + + /** + * prevent default browser behavior when dragging occurs + * be careful with it, it makes the element a blocking element + * when you are using the drag gesture, it is a good practice to set this true + * @property dragBlockHorizontal + * @type {Boolean} + * @default false + */ + dragBlockHorizontal: false, + + /** + * same as `dragBlockHorizontal`, but for vertical movement + * @property dragBlockVertical + * @type {Boolean} + * @default false + */ + dragBlockVertical: false, + + /** + * dragLockToAxis keeps the drag gesture on the axis that it started on, + * It disallows vertical directions if the initial direction was horizontal, and vice versa. + * @property dragLockToAxis + * @type {Boolean} + * @default false + */ + dragLockToAxis: false, + + /** + * drag lock only kicks in when distance > dragLockMinDistance + * This way, locking occurs only when the distance has become large enough to reliably determine the direction + * @property dragLockMinDistance + * @type {Number} + * @default 25 + */ + dragLockMinDistance: 25 + } + }; +})('drag'); + +/** + * @module gestures + */ +/** + * trigger a simple gesture event, so you can do anything in your handler. + * only usable if you know what your doing... + * + * @class Gesture + * @static + */ +/** + * @event gesture + * @param {Object} ev + */ +Hammer.gestures.Gesture = { + name: 'gesture', + index: 1337, + handler: function releaseGesture(ev, inst) { + inst.trigger(this.name, ev); + } +}; + +/** + * @module gestures + */ +/** + * Touch stays at the same place for x time + * + * @class Hold + * @static + */ +/** + * @event hold + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var timer; + + function holdGesture(ev, inst) { + var options = inst.options, + current = Detection.current; + + switch(ev.eventType) { + case EVENT_START: + clearTimeout(timer); + + // set the gesture so we can check in the timeout if it still is + current.name = name; + + // set timer and if after the timeout it still is hold, + // we trigger the hold event + timer = setTimeout(function() { + if(current && current.name == name) { + inst.trigger(name, ev); + } + }, options.holdTimeout); + break; + + case EVENT_MOVE: + if(ev.distance > options.holdThreshold) { + clearTimeout(timer); + } + break; + + case EVENT_RELEASE: + clearTimeout(timer); + break; + } + } + + Hammer.gestures.Hold = { + name: name, + index: 10, + defaults: { + /** + * @property holdTimeout + * @type {Number} + * @default 500 + */ + holdTimeout: 500, + + /** + * movement allowed while holding + * @property holdThreshold + * @type {Number} + * @default 2 + */ + holdThreshold: 2 + }, + handler: holdGesture + }; +})('hold'); + +/** + * @module gestures + */ +/** + * when a touch is being released from the page + * + * @class Release + * @static + */ +/** + * @event release + * @param {Object} ev + */ +Hammer.gestures.Release = { + name: 'release', + index: Infinity, + handler: function releaseGesture(ev, inst) { + if(ev.eventType == EVENT_RELEASE) { + inst.trigger(this.name, ev); + } + } +}; + +/** + * @module gestures + */ +/** + * triggers swipe events when the end velocity is above the threshold + * for best usage, set `preventDefault` (on the drag gesture) to `true` + * ```` + * hammertime.on("dragleft swipeleft", function(ev) { + * console.log(ev); + * ev.gesture.preventDefault(); + * }); + * ```` + * + * @class Swipe + * @static + */ +/** + * @event swipe + * @param {Object} ev + */ +/** + * @event swipeleft + * @param {Object} ev + */ +/** + * @event swiperight + * @param {Object} ev + */ +/** + * @event swipeup + * @param {Object} ev + */ +/** + * @event swipedown + * @param {Object} ev + */ +Hammer.gestures.Swipe = { + name: 'swipe', + index: 40, + defaults: { + /** + * @property swipeMinTouches + * @type {Number} + * @default 1 + */ + swipeMinTouches: 1, + + /** + * @property swipeMaxTouches + * @type {Number} + * @default 1 + */ + swipeMaxTouches: 1, + + /** + * horizontal swipe velocity + * @property swipeVelocityX + * @type {Number} + * @default 0.6 + */ + swipeVelocityX: 0.6, + + /** + * vertical swipe velocity + * @property swipeVelocityY + * @type {Number} + * @default 0.6 + */ + swipeVelocityY: 0.6 + }, + + handler: function swipeGesture(ev, inst) { + if(ev.eventType == EVENT_RELEASE) { + var touches = ev.touches.length, + options = inst.options; + + // max touches + if(touches < options.swipeMinTouches || + touches > options.swipeMaxTouches) { + return; + } + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.velocityX > options.swipeVelocityX || + ev.velocityY > options.swipeVelocityY) { + // trigger swipe events + inst.trigger(this.name, ev); + inst.trigger(this.name + ev.direction, ev); + } + } + } +}; + +/** + * @module gestures + */ +/** + * Single tap and a double tap on a place + * + * @class Tap + * @static + */ +/** + * @event tap + * @param {Object} ev + */ +/** + * @event doubletap + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var hasMoved = false; + + function tapGesture(ev, inst) { + var options = inst.options, + current = Detection.current, + prev = Detection.previous, + sincePrev, + didDoubleTap; + + switch(ev.eventType) { + case EVENT_START: + hasMoved = false; + break; + + case EVENT_MOVE: + hasMoved = hasMoved || (ev.distance > options.tapMaxDistance); + break; + + case EVENT_END: + if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) { + // previous gesture, for the double tap since these are two different gesture detections + sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp; + didDoubleTap = false; + + // check if double tap + if(prev && prev.name == name && + (sincePrev && sincePrev < options.doubleTapInterval) && + ev.distance < options.doubleTapDistance) { + inst.trigger('doubletap', ev); + didDoubleTap = true; + } + + // do a single tap + if(!didDoubleTap || options.tapAlways) { + current.name = name; + inst.trigger(current.name, ev); + } + } + break; + } + } + + Hammer.gestures.Tap = { + name: name, + index: 100, + handler: tapGesture, + defaults: { + /** + * max time of a tap, this is for the slow tappers + * @property tapMaxTime + * @type {Number} + * @default 250 + */ + tapMaxTime: 250, + + /** + * max distance of movement of a tap, this is for the slow tappers + * @property tapMaxDistance + * @type {Number} + * @default 10 + */ + tapMaxDistance: 10, + + /** + * always trigger the `tap` event, even while double-tapping + * @property tapAlways + * @type {Boolean} + * @default true + */ + tapAlways: true, + + /** + * max distance between two taps + * @property doubleTapDistance + * @type {Number} + * @default 20 + */ + doubleTapDistance: 20, + + /** + * max time between two taps + * @property doubleTapInterval + * @type {Number} + * @default 300 + */ + doubleTapInterval: 300 + } + }; +})('tap'); + +/** + * @module gestures + */ +/** + * when a touch is being touched at the page + * + * @class Touch + * @static + */ +/** + * @event touch + * @param {Object} ev + */ +Hammer.gestures.Touch = { + name: 'touch', + index: -Infinity, + defaults: { + /** + * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page, + * but it improves gestures like transforming and dragging. + * be careful with using this, it can be very annoying for users to be stuck on the page + * @property preventDefault + * @type {Boolean} + * @default false + */ + preventDefault: false, + + /** + * disable mouse events, so only touch (or pen!) input triggers events + * @property preventMouse + * @type {Boolean} + * @default false + */ + preventMouse: false + }, + handler: function touchGesture(ev, inst) { + if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) { + ev.stopDetect(); + return; + } + + if(inst.options.preventDefault) { + ev.preventDefault(); + } + + if(ev.eventType == EVENT_TOUCH) { + inst.trigger('touch', ev); + } + } +}; + +/** + * @module gestures + */ +/** + * User want to scale or rotate with 2 fingers + * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the + * `preventDefault` option. + * + * @class Transform + * @static + */ +/** + * @event transform + * @param {Object} ev + */ +/** + * @event transformstart + * @param {Object} ev + */ +/** + * @event transformend + * @param {Object} ev + */ +/** + * @event pinchin + * @param {Object} ev + */ +/** + * @event pinchout + * @param {Object} ev + */ +/** + * @event rotate + * @param {Object} ev + */ + +/** + * @param {String} name + */ +(function(name) { + var triggered = false; + + function transformGesture(ev, inst) { + switch(ev.eventType) { + case EVENT_START: + triggered = false; + break; + + case EVENT_MOVE: + // at least multitouch + if(ev.touches.length < 2) { + return; + } + + var scaleThreshold = Math.abs(1 - ev.scale); + var rotationThreshold = Math.abs(ev.rotation); + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(scaleThreshold < inst.options.transformMinScale && + rotationThreshold < inst.options.transformMinRotation) { + return; + } + + // we are transforming! + Detection.current.name = name; + + // first time, trigger dragstart event + if(!triggered) { + inst.trigger(name + 'start', ev); + triggered = true; + } + + inst.trigger(name, ev); // basic transform event + + // trigger rotate event + if(rotationThreshold > inst.options.transformMinRotation) { + inst.trigger('rotate', ev); + } + + // trigger pinch event + if(scaleThreshold > inst.options.transformMinScale) { + inst.trigger('pinch', ev); + inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev); + } + break; + + case EVENT_RELEASE: + if(triggered && ev.changedLength < 2) { + inst.trigger(name + 'end', ev); + triggered = false; + } + break; + } + } + + Hammer.gestures.Transform = { + name: name, + index: 45, + defaults: { + /** + * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 + * @property transformMinScale + * @type {Number} + * @default 0.01 + */ + transformMinScale: 0.01, + + /** + * rotation in degrees + * @property transformMinRotation + * @type {Number} + * @default 1 + */ + transformMinRotation: 1 + }, + + handler: transformGesture + }; +})('transform'); + +/** + * @module hammer + */ + +// AMD export +if(typeof define == 'function' && define.amd) { + define(function() { + return Hammer; + }); +// commonjs export +} else if(typeof module !== 'undefined' && module.exports) { + module.exports = Hammer; +// browser export +} else { + window.Hammer = Hammer; +} + +})(window); \ No newline at end of file diff --git a/js/jester.js b/js/jester.js deleted file mode 100644 index 4612443..0000000 --- a/js/jester.js +++ /dev/null @@ -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)); From 208fa70c529f5e241e0a91819c1f9cce1f02eaee Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 20:48:41 +0200 Subject: [PATCH 14/37] removed alert on end of downloads --- js/App.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/App.js b/js/App.js index 758ae03..86eef5b 100644 --- a/js/App.js +++ b/js/App.js @@ -189,8 +189,6 @@ App.prototype.gotUnreadFeeds = function(new_articles) { alert("Reached maximum memory by app" + e.name + " " +e.message +". We will keep working in anycase with:" + localStorage.unread_articles.length); } this.populateList(); - } else { - alert("Downloaded: " + this.unread_articles.length + " articles. Reached: " + localStorage.unread_articles.length + " bytes"); } } }; From b5931a8538cb57c497f0fcc42ba8afe07a631552 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 21:09:32 +0200 Subject: [PATCH 15/37] fixed long images not scrolling --- css/screen.css | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/css/screen.css b/css/screen.css index 7c14659..4c05fec 100644 --- a/css/screen.css +++ b/css/screen.css @@ -140,20 +140,21 @@ section > footer { img { max-width: 100% !important; - height: auto; +} + +#full > article pre { + overflow: auto; } @media screen and (width: 320px) { - #full > article * , #full > article pre { + #full > article > * { max-width: 300px !important; - overflow: auto; } } @media screen and (width: 480px) { - #full > article * { + #full > article > * { max-width: 460px !important; - overflow: auto; } } From 1f404a846c745e210f90f68aa980b43862ffe858 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 21:14:41 +0200 Subject: [PATCH 16/37] disabled word suggestions inf input fields, fixes #31 --- index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 0a79f6a..54b579b 100644 --- a/index.html +++ b/index.html @@ -71,11 +71,11 @@

- + - + - +

From 54c87e58d82fc7fd48069bb96672d037f13d9f15 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 21:19:15 +0200 Subject: [PATCH 17/37] typo --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 54b579b..8c4e733 100644 --- a/index.html +++ b/index.html @@ -73,9 +73,9 @@ - + - +

From 89142c54c0fdadeb027a35326fd5b27dc2c46d8c Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 21:29:39 +0200 Subject: [PATCH 18/37] better error message on login with wrong server address, fixes #15 --- js/App.js | 2 +- js/OwnCloud.js | 6 +++++- js/Pond.js | 6 +++++- js/TinyTinyRSS.js | 6 +++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/js/App.js b/js/App.js index 86eef5b..f5f0db6 100644 --- a/js/App.js +++ b/js/App.js @@ -186,7 +186,7 @@ App.prototype.gotUnreadFeeds = function(new_articles) { } } catch (e) { - alert("Reached maximum memory by app" + e.name + " " +e.message +". We will keep working in anycase with:" + localStorage.unread_articles.length); + alert("Reached maximum memory by app " + e.name + " " + e.message + ". We will keep working in anycase with: " + localStorage.unread_articles.length); } this.populateList(); } diff --git a/js/OwnCloud.js b/js/OwnCloud.js index 25519eb..8272ca6 100644 --- a/js/OwnCloud.js +++ b/js/OwnCloud.js @@ -241,7 +241,11 @@ OwnCloud.login = function(server_url, user, password, callback) { if(xhr.status == 200) { callback(JSON.parse(xhr.responseText)) } else { - alert("error: " + xhr.status + " " + xhr.statusText) + if(xhr.status == 0) { + alert("Something went wrong, please check your credentials and the server address") + } else { + alert("error: " + xhr.status + " " + xhr.statusText); + } } } } diff --git a/js/Pond.js b/js/Pond.js index 7f4d429..e9eb049 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -215,7 +215,11 @@ Pond.login = function(server_url, user, password, callback) { if(xhr.status == 201) { callback(JSON.parse(xhr.responseText)) } 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); + } } } } diff --git a/js/TinyTinyRSS.js b/js/TinyTinyRSS.js index 1fd2813..6405508 100644 --- a/js/TinyTinyRSS.js +++ b/js/TinyTinyRSS.js @@ -175,7 +175,11 @@ TinyTinyRSS.login = function(server_url, user, password, callback) { if(xhr.status == 200) { callback(JSON.parse(xhr.responseText).content) } else { - alert("error: " + xhr.status + " " + xhr.statusText) + if(xhr.status == 0) { + alert("Something went wrong, please check your credentials and the server address") + } else { + alert("error: " + xhr.status + " " + xhr.statusText) + } } } } From f5f9e9761f2238e2a7e5268d15014fece63d2dba Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 22:15:06 +0200 Subject: [PATCH 19/37] fixed circle text color on white --- js/App.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/App.js b/js/App.js index f5f0db6..c471b72 100644 --- a/js/App.js +++ b/js/App.js @@ -260,6 +260,7 @@ App.prototype.updatePieChart = function() { var bg = window.getComputedStyle($("body"), null).backgroundColor; var fg = window.getComputedStyle($(".bar"), null).backgroundColor; + var tx = window.getComputedStyle($(".bar"), null).color; var myColor = [bg, fg]; @@ -284,7 +285,7 @@ App.prototype.updatePieChart = function() { if(all > 0) { ctx.font = "12px FeuraSans, sans-serif"; - ctx.fillStyle = "#fff"; + ctx.fillStyle = tx; ctx.textAlign = "center"; var text = unread + "/" + all; var x = canvas.width / 2; From fd9b3d95803b2525111c9bee7171afa15938e31d Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 22:15:30 +0200 Subject: [PATCH 20/37] added pond with link to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c98a4ea..9c430a5 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ A RSS mobile client with which you can read your RSS feeds and mark them as read - Works also offline. - 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. From 22d6761ba367c2f955ce83a69bd83eaa6d4f75a5 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 22:15:54 +0200 Subject: [PATCH 21/37] added pond to manifest and changed version number --- manifest.webapp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.webapp b/manifest.webapp index c4926f4..170ee25 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -1,6 +1,6 @@ { "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", "icons": { "58": "/img/icon-58.png", @@ -15,9 +15,9 @@ "type": "privileged", "permissions": { "systemXHR": { - "description": "Connection with your own TinyTinyRSS server." + "description": "Connection with your own server." } }, "installs_allowed_from": ["*"], - "version": "0.3.0_iss30" + "version": "0.4.0" } From aec6cd079be356e788f06490f1f029865d7ec113 Mon Sep 17 00:00:00 2001 From: Jeena Date: Thu, 22 May 2014 22:54:43 +0200 Subject: [PATCH 22/37] fixed so the app is working offline again --- js/OwnCloud.js | 2 +- js/Pond.js | 28 ++++++++++++++++++++++------ js/TinyTinyRSS.js | 3 +-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/js/OwnCloud.js b/js/OwnCloud.js index 8272ca6..3ec87e9 100644 --- a/js/OwnCloud.js +++ b/js/OwnCloud.js @@ -227,7 +227,7 @@ OwnCloud.prototype.append = function(key, array) { if (typeof tmp !== "undefined") tmp = JSON.parse(tmp); else tmp = []; - tmp.concat(options.items); + tmp.concat(array); localStorage[key] = JSON.stringify(tmp); }; diff --git a/js/Pond.js b/js/Pond.js index e9eb049..99abb41 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -15,7 +15,13 @@ Pond.prototype.onoffline = 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() { @@ -159,11 +165,11 @@ Pond.prototype.setArticleStatus = function(article, callback, status) { var url = "subscriptions/" + article.feed_id + "/articles/" + article.id - if (navigator.onLine) this.doOperation("PUT", url, options, callback); - else { - this.append("unread_articles", articles); + if (navigator.onLine) { + this.doOperation("PUT", url, options, callback); + } else { + this.append(status + "_articles", articles); } - } Pond.prototype.setArticleRead = function(article, callback) { @@ -181,7 +187,7 @@ Pond.prototype.setArticlesRead = function(articles, callback) { } Pond.prototype.setArticlesUnread = function(articles, callback) { - articles.forEach(function(article) { + articles.forEach(function(article) { this.setArticleStatus(article, callback, "unread"); }) } @@ -194,6 +200,16 @@ Pond.prototype.setArticleUnstarred = function(articles, callback) { // 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() { this.doOperation("DELETE", "auth/sessions/" + this.session_token ); localStorage.feeds = null; diff --git a/js/TinyTinyRSS.js b/js/TinyTinyRSS.js index 6405508..64edd78 100644 --- a/js/TinyTinyRSS.js +++ b/js/TinyTinyRSS.js @@ -150,13 +150,12 @@ TinyTinyRSS.prototype.setArticleUnstarred = function(article, callback) { }; TinyTinyRSS.prototype.append = function(key, array) { - var tmp = localStorage[key]; if (typeof tmp !== "undefined") tmp = JSON.parse(tmp); else tmp = []; - tmp.concat(options.items); + tmp.concat(array); localStorage[key] = JSON.stringify(tmp); }; From 54900c33461da33dc8e2a02146e3bb380979c1de Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 3 Jun 2014 19:13:19 +0200 Subject: [PATCH 23/37] better slide --- js/App.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/App.js b/js/App.js index c471b72..c81fc13 100644 --- a/js/App.js +++ b/js/App.js @@ -96,8 +96,8 @@ App.prototype.after_login = function(backend) { dragBlockHorizontal: true }; var hammertime = new Hammer($("#full"), options); - hammertime.on("swipeleft", function(ev){ _this.showNext() }); - hammertime.on("swiperight", function(ev){ _this.showPrevious(); }); + hammertime.on("dragleft swipeleft", function(ev){ ev.gesture.preventDefault(); _this.showNext() }); + hammertime.on("dragright swiperight", function(ev){ ev.gesture.preventDefault(); _this.showPrevious(); }); this.changeToPage("#list"); From a33fbe311075b313ceb34e56ffc5241d85f58029 Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 3 Jun 2014 22:54:59 +0200 Subject: [PATCH 24/37] fixed images and figures --- css/screen.css | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/css/screen.css b/css/screen.css index 4c05fec..b0f4b1b 100644 --- a/css/screen.css +++ b/css/screen.css @@ -138,8 +138,14 @@ section > footer { box-sizing: border-box; } +figure { + margin: 0; + padding: 0; +} + img { max-width: 100% !important; + height: auto; } #full > article pre { @@ -147,13 +153,13 @@ img { } @media screen and (width: 320px) { - #full > article > * { + #full > article * { max-width: 300px !important; } } @media screen and (width: 480px) { - #full > article > * { + #full > article * { max-width: 460px !important; } } From 7161642224b4dd468477fb8a23c293e0821b70f3 Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 3 Jun 2014 22:55:32 +0200 Subject: [PATCH 25/37] easier swiping --- js/App.js | 9 ++++++--- js/hammer.js | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/js/App.js b/js/App.js index c81fc13..73c4257 100644 --- a/js/App.js +++ b/js/App.js @@ -96,8 +96,10 @@ App.prototype.after_login = function(backend) { dragBlockHorizontal: true }; var hammertime = new Hammer($("#full"), options); - hammertime.on("dragleft swipeleft", function(ev){ ev.gesture.preventDefault(); _this.showNext() }); - hammertime.on("dragright swiperight", function(ev){ ev.gesture.preventDefault(); _this.showPrevious(); }); + 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"); @@ -176,7 +178,6 @@ App.prototype.gotUnreadFeeds = function(new_articles) { try { //To check if when it fails it is the same localStorage.unread_articles = JSON.stringify(this.unread_articles); - //alert("Size probando:"+probando.length) var size = parseInt(localStorage.maxDownload); if(localStorage.unread_articles.length < size) { var num = parseInt(localStorage.numArticles); @@ -316,6 +317,8 @@ App.prototype.showFull = function(article, slide_back) { $(page_id + " .author").innerHTML = "– " + article.author; $(page_id + " .article").innerHTML = article.content; + + // Open all links in browser $$(page_id + " .article a").forEach(function(o, i) { o.target = "_blank"; }); diff --git a/js/hammer.js b/js/hammer.js index e03acdc..703ee55 100644 --- a/js/hammer.js +++ b/js/hammer.js @@ -1825,7 +1825,7 @@ Hammer.gestures.Swipe = { * @type {Number} * @default 0.6 */ - swipeVelocityX: 0.6, + swipeVelocityX: 0.2, /** * vertical swipe velocity @@ -1833,7 +1833,7 @@ Hammer.gestures.Swipe = { * @type {Number} * @default 0.6 */ - swipeVelocityY: 0.6 + swipeVelocityY: 0.2 }, handler: function swipeGesture(ev, inst) { From dfcc5ba27afc236f394a344cd8d0ba5a490ef66b Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 3 Jun 2014 22:55:43 +0200 Subject: [PATCH 26/37] version bump --- manifest.webapp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.webapp b/manifest.webapp index 170ee25..03ad1cc 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -19,5 +19,5 @@ } }, "installs_allowed_from": ["*"], - "version": "0.4.0" + "version": "0.4.1" } From 3f75c49e3e61434a9ec768c6e24299b3b828a17a Mon Sep 17 00:00:00 2001 From: mossroy Date: Wed, 6 Aug 2014 12:18:27 +0200 Subject: [PATCH 27/37] 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. --- js/App.js | 66 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/js/App.js b/js/App.js index 73c4257..60e141c 100644 --- a/js/App.js +++ b/js/App.js @@ -162,35 +162,47 @@ App.prototype.reload = function() { App.prototype.gotUnreadFeeds = function(new_articles) { - if(new_articles == null || !this.validate(new_articles)) { // on error load the saved unread articles. - - var old_articles = localStorage.unread_articles; - if(old_articles) { - this.unread_articles = JSON.parse(old_articles); - } - this.populateList(); - + 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") { + $("#url").value = localStorage.server_url; + $("#login form").backend[0].checked = true; + alert("Your TinyTinyRSS session has expired. Please login again"); + this.login.log_in(); + } + else { + // On other errors, load the saved unread articles. + var old_articles = localStorage.unread_articles; + if(old_articles) { + this.unread_articles = JSON.parse(old_articles); + } + this.populateList(); + } + } else { + + this.unread_articles = this.unread_articles.concat(new_articles); - this.unread_articles = this.unread_articles.concat(new_articles); - - if(new_articles.length > 0) { - try { - //To check if when it fails it is the same - 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(); - } + if(new_articles.length > 0) { + try { + //To check if when it fails it is the same + 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(); + } } }; From b2f319af71a51957c72fe1a27efc77b26e1073f3 Mon Sep 17 00:00:00 2001 From: mossroy Date: Wed, 6 Aug 2014 15:22:59 +0200 Subject: [PATCH 28/37] 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 --- js/App.js | 3 +-- js/Login.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/js/App.js b/js/App.js index 60e141c..c2187d8 100644 --- a/js/App.js +++ b/js/App.js @@ -168,9 +168,8 @@ App.prototype.gotUnreadFeeds = function(new_articles) { // 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") { - $("#url").value = localStorage.server_url; - $("#login form").backend[0].checked = true; alert("Your TinyTinyRSS session has expired. Please login again"); + this.login.fillLoginFormFromLocalSotrage(); this.login.log_in(); } else { diff --git a/js/Login.js b/js/Login.js index 72a9490..b8ed527 100644 --- a/js/Login.js +++ b/js/Login.js @@ -67,6 +67,7 @@ Login.prototype.authenticate = function(e) { if(data.version) { var auth = btoa(user + ':' + password); localStorage.server_url = server_url; + localStorage.username = user; localStorage.session_id = auth; localStorage.backend = "OwnCloud"; _this.app.after_login(localStorage.backend); @@ -83,6 +84,7 @@ Login.prototype.authenticate = function(e) { Pond.login(server_url, user, password, function(data) { if(data.session_token) { localStorage.server_url = server_url; + localStorage.username = user; localStorage.session_id = data.session_token; localStorage.backend = "Pond"; _this.app.after_login(localStorage.backend); @@ -106,6 +108,7 @@ Login.prototype.authenticate = function(e) { } else { localStorage.server_url = server_url; + localStorage.username = user; localStorage.session_id = data.session_id; localStorage.backend = "TinyTinyRSS"; _this.app.after_login(localStorage.backend); @@ -120,7 +123,23 @@ Login.prototype.authenticate = function(e) { return false; }; +Login.prototype.fillLoginFormFromLocalSotrage = function() { + $("#url").value = localStorage.server_url; + $("#un").value = localStorage.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() { + this.fillLoginFormFromLocalSotrage(); localStorage.removeItem("server_url"); localStorage.removeItem("session_id"); localStorage.removeItem("unread_articles"); From 568ca8005de07cdda278771c1d427ddee9f85bac Mon Sep 17 00:00:00 2001 From: mossroy Date: Wed, 6 Aug 2014 15:29:34 +0200 Subject: [PATCH 29/37] Issue #30 : Fix a typo in function name --- js/App.js | 2 +- js/Login.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/App.js b/js/App.js index c2187d8..520f9ab 100644 --- a/js/App.js +++ b/js/App.js @@ -169,7 +169,7 @@ App.prototype.gotUnreadFeeds = function(new_articles) { // 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.fillLoginFormFromLocalSotrage(); + this.login.fillLoginFormFromLocalStorage(); this.login.log_in(); } else { diff --git a/js/Login.js b/js/Login.js index b8ed527..8f8212c 100644 --- a/js/Login.js +++ b/js/Login.js @@ -123,7 +123,7 @@ Login.prototype.authenticate = function(e) { return false; }; -Login.prototype.fillLoginFormFromLocalSotrage = function() { +Login.prototype.fillLoginFormFromLocalStorage = function() { $("#url").value = localStorage.server_url; $("#un").value = localStorage.username; var backendName = localStorage.backend; @@ -139,7 +139,7 @@ Login.prototype.fillLoginFormFromLocalSotrage = function() { } Login.prototype.log_out = function() { - this.fillLoginFormFromLocalSotrage(); + this.fillLoginFormFromLocalStorage(); localStorage.removeItem("server_url"); localStorage.removeItem("session_id"); localStorage.removeItem("unread_articles"); From cc88b341b2fde13e34e0bff7516852b8889f861d Mon Sep 17 00:00:00 2001 From: mossroy Date: Wed, 6 Aug 2014 15:37:08 +0200 Subject: [PATCH 30/37] Issue #30 : Avoid displaying "undefined" for username when it was not yet stored in the localStorage --- js/Login.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/Login.js b/js/Login.js index 8f8212c..c786023 100644 --- a/js/Login.js +++ b/js/Login.js @@ -124,8 +124,14 @@ Login.prototype.authenticate = function(e) { }; Login.prototype.fillLoginFormFromLocalStorage = function() { - $("#url").value = localStorage.server_url; - $("#un").value = localStorage.username; + 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; From d6a2ca9fd6723c1b29dff4ea94314c95890289c5 Mon Sep 17 00:00:00 2001 From: Jeena Date: Wed, 27 Aug 2014 22:56:12 +0200 Subject: [PATCH 31/37] OwnCloud: sending right Content-Type, fixes #36 --- js/OwnCloud.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/OwnCloud.js b/js/OwnCloud.js index 3ec87e9..89a77b3 100644 --- a/js/OwnCloud.js +++ b/js/OwnCloud.js @@ -59,6 +59,7 @@ OwnCloud.prototype.doOperation = function(method, operation, new_options, callba } } 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); From 2bf7e97c55d5d22b217f318eb9259f515fa673df Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 29 Aug 2014 20:19:55 +0200 Subject: [PATCH 32/37] version bump --- manifest.webapp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.webapp b/manifest.webapp index 03ad1cc..1d35659 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -19,5 +19,5 @@ } }, "installs_allowed_from": ["*"], - "version": "0.4.1" + "version": "0.4.2" } From 2a2d35dfde17e94bdcfe04877cfa5c3a4f451de1 Mon Sep 17 00:00:00 2001 From: Bonbadil Date: Wed, 15 Oct 2014 22:00:32 +0200 Subject: [PATCH 33/37] Add a publish button on article view. --- css/screen.css | 5 ++++- index.html | 1 + js/App.js | 26 ++++++++++++++++++++++++++ js/TinyTinyRSS.js | 40 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/css/screen.css b/css/screen.css index b0f4b1b..88bd159 100644 --- a/css/screen.css +++ b/css/screen.css @@ -120,11 +120,14 @@ section > header h1 { overflow: auto; } -.reload, .all-read, #setstarred, #setunread { +.reload, .all-read, #setstarred, #setunread, #setpublished { float: right; margin-left: 10px; } +#setpublished { display: none;} +#setpublished.active { display: inline;} + .settings, .list { float: left; } diff --git a/index.html b/index.html index 8c4e733..bbd0e05 100644 --- a/index.html +++ b/index.html @@ -87,6 +87,7 @@

+ P diff --git a/js/App.js b/js/App.js index 520f9ab..3d1d674 100644 --- a/js/App.js +++ b/js/App.js @@ -54,6 +54,8 @@ App.prototype.after_login = function(backend) { _this.toggleCurrentUnread(); } else if(url == "#starred") { _this.toggleStarred(); + } else if(url == "#published") { + _this.togglePublished(); } else if(url == "#logout") { _this.logout(); } else if(url == "#reset-info") { @@ -109,6 +111,7 @@ App.prototype.after_login = function(backend) { this.backend = new Pond(this, localStorage.server_url, localStorage.session_id) } else { this.backend = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id); + $("#setpublished").addClass("active"); } var numArticles = localStorage.numArticles; @@ -346,6 +349,12 @@ App.prototype.showFull = function(article, slide_back) { $("#setstarred").innerHTML = "☆"; } + if(article.published) { + $("#setpublished").innerHTML = "U"; + } else { + $("#setpublished").innerHTML = "P"; + } + }; App.prototype.showNext = function() { @@ -455,6 +464,23 @@ App.prototype.toggleStarred = function() { }; +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").innerHTML = "U"; + } + else { + article.published = false; + this.backend.setArticleUnpublished(article); + $("#setpublished").innerHTML = "P"; + } + +}; + App.prototype.goToList = function() { this.changeToPage("#list"); }; diff --git a/js/TinyTinyRSS.js b/js/TinyTinyRSS.js index 64edd78..2644d02 100644 --- a/js/TinyTinyRSS.js +++ b/js/TinyTinyRSS.js @@ -13,7 +13,7 @@ TinyTinyRSS.prototype.onoffline = function() { TinyTinyRSS.prototype.ononline = function() { - ["read", "unread", "starred", "unstarred"].forEach(function(type) { + ["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 } @@ -149,6 +149,44 @@ 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]; From 8b0cc42075d5f15233adaafa87dc71d5aaa2fbce Mon Sep 17 00:00:00 2001 From: Bonbadil Date: Wed, 15 Oct 2014 22:58:33 +0200 Subject: [PATCH 34/37] Use cloud symbols for the publish button. --- index.html | 2 +- js/App.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index bbd0e05..237f465 100644 --- a/index.html +++ b/index.html @@ -87,7 +87,7 @@
- P + diff --git a/js/App.js b/js/App.js index 3d1d674..f962593 100644 --- a/js/App.js +++ b/js/App.js @@ -350,9 +350,9 @@ App.prototype.showFull = function(article, slide_back) { } if(article.published) { - $("#setpublished").innerHTML = "U"; + $("#setpublished").innerHTML = ""; } else { - $("#setpublished").innerHTML = "P"; + $("#setpublished").innerHTML = "☁"; } }; @@ -471,12 +471,12 @@ App.prototype.togglePublished = function() { if(!article.published) { article.published = true; this.backend.setArticlePublished(article); - $("#setpublished").innerHTML = "U"; + $("#setpublished").innerHTML = ""; } else { article.published = false; this.backend.setArticleUnpublished(article); - $("#setpublished").innerHTML = "P"; + $("#setpublished").innerHTML = "☁"; } }; From de93b8059afbdc17b0adc6f5ef042dda45331c49 Mon Sep 17 00:00:00 2001 From: Bonbadil Date: Thu, 16 Oct 2014 00:06:00 +0200 Subject: [PATCH 35/37] Two-states buttons are now using opacity instead of two icons. --- css/screen.css | 4 ++-- index.html | 8 ++++---- js/App.js | 44 ++++++++++++++++++++++---------------------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/css/screen.css b/css/screen.css index 88bd159..6ea4412 100644 --- a/css/screen.css +++ b/css/screen.css @@ -125,8 +125,8 @@ section > header h1 { margin-left: 10px; } -#setpublished { display: none;} -#setpublished.active { display: inline;} +.invisible { display: none; } +.inactive { opacity: 0.4; } .settings, .list { float: left; diff --git a/index.html b/index.html index 237f465..24795ac 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,7 @@
🔄 - +
@@ -87,9 +87,9 @@
- - - + + +
diff --git a/js/App.js b/js/App.js index f962593..be3383c 100644 --- a/js/App.js +++ b/js/App.js @@ -111,7 +111,7 @@ App.prototype.after_login = function(backend) { this.backend = new Pond(this, localStorage.server_url, localStorage.session_id) } else { this.backend = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id); - $("#setpublished").addClass("active"); + $("#setpublished").removeClass("invisible"); } var numArticles = localStorage.numArticles; @@ -158,7 +158,7 @@ App.prototype.setColor = function(color) { App.prototype.reload = function() { this.unread_articles = []; - $("#all-read").innerHTML = "❌"; + $("#all-read").addClass('inactive'); var number=parseInt(localStorage.numArticles); this.backend.reload(this.gotUnreadFeeds.bind(this),number); }; @@ -251,9 +251,9 @@ App.prototype.updateList = function() { }, this); if(unread > 0) { - $("#all-read").innerHTML = "❌"; + $("#all-read").addClass('inactive'); } else { - $("#all-read").innerHTML = "✓"; + $("#all-read").removeClass('inactive'); } this.updatePieChart(); @@ -338,21 +338,21 @@ App.prototype.showFull = function(article, slide_back) { }); if(article.unread) { - $("#setunread").innerHTML = "❌"; + $("#setunread").addClass('inactive'); } else { - $("#setunread").innerHTML = "✓"; + $("#setunread").removeClass('inactive'); } - if(article.marked) { - $("#setstarred").innerHTML = "★"; + if(!article.marked) { + $("#setstarred").addClass('inactive'); } else { - $("#setstarred").innerHTML = "☆"; + $("#setstarred").removeClass('inactive'); } - if(article.published) { - $("#setpublished").innerHTML = ""; + if(!article.published) { + $("#setpublished").addClass('inactive'); } else { - $("#setpublished").innerHTML = "☁"; + $("#setpublished").removeClass('inactive'); } }; @@ -390,7 +390,7 @@ App.prototype.setCurrentRead = function() { article.set_unread = false; - $("#setunread").innerHTML = "✓"; + $("#setunread").removeClass('inactive'); this.updatePieChart(); }; @@ -400,11 +400,11 @@ App.prototype.toggleCurrentUnread = function() { if(article.unread) { article.unread = false; article.set_unread = false; - $("#setunread").innerHTML = "✓"; + $("#setunread").removeClass('inactive'); } else { article.unread = true; article.set_unread = true; - $("#setunread").innerHTML = "❌"; + $("#setunread").addClass('inactive'); } this.updateList(); @@ -413,7 +413,7 @@ App.prototype.toggleCurrentUnread = function() { App.prototype.toggleAllRead = function() { - if($("#all-read").innerHTML == "❌") { // set all read + if($("#all-read").hasClass('inactive')) { // set all read var articles = []; for (var i = 0; i < this.unread_articles.length; i++) { @@ -422,7 +422,7 @@ App.prototype.toggleAllRead = function() { article.set_unread = false; articles.push(article); } - $("#all-read").innerHTML = "✓"; + $("#all-read").removeClass('inactive'); this.updateList(); @@ -437,7 +437,7 @@ App.prototype.toggleAllRead = function() { article.set_unread = false; articles.push(article); } - $("#all-read").innerHTML = "❌"; + $("#all-read").addClass('inactive'); this.updateList(); this.backend.setArticlesUnread(articles); @@ -453,13 +453,13 @@ App.prototype.toggleStarred = function() { article.marked = true; this.updateList(); this.backend.setArticleStarred(article); - $("#setstarred").innerHTML = "★"; + $("#setstarred").removeClass('inactive'); } else { article.marked = false; this.updateList(); this.backend.setArticleUnstarred(article); - $("#setstarred").innerHTML = "☆"; + $("#setstarred").addClass('inactive'); } }; @@ -471,12 +471,12 @@ App.prototype.togglePublished = function() { if(!article.published) { article.published = true; this.backend.setArticlePublished(article); - $("#setpublished").innerHTML = ""; + $("#setpublished").removeClass('inactive'); } else { article.published = false; this.backend.setArticleUnpublished(article); - $("#setpublished").innerHTML = "☁"; + $("#setpublished").addClass('inactive'); } }; From 3e4be0fe29718a4c652b5650b41686d41bfa07c0 Mon Sep 17 00:00:00 2001 From: Jeena Date: Mon, 27 Oct 2014 00:27:11 +0100 Subject: [PATCH 36/37] hide publish button for nono TTRSS --- css/screen.css | 3 +- index.html | 2 +- js/App.js | 94 +++++++++++++++++++++++--------------------------- 3 files changed, 45 insertions(+), 54 deletions(-) diff --git a/css/screen.css b/css/screen.css index 6ea4412..15170fc 100644 --- a/css/screen.css +++ b/css/screen.css @@ -83,7 +83,7 @@ label { } .hidden { - display: none; + display: none !important; } section { @@ -125,7 +125,6 @@ section > header h1 { margin-left: 10px; } -.invisible { display: none; } .inactive { opacity: 0.4; } .settings, .list { diff --git a/index.html b/index.html index 24795ac..6d62518 100644 --- a/index.html +++ b/index.html @@ -87,7 +87,7 @@
- + diff --git a/js/App.js b/js/App.js index be3383c..ff92dad 100644 --- a/js/App.js +++ b/js/App.js @@ -33,7 +33,7 @@ App.prototype.after_login = function(backend) { // do not reload page e.preventDefault(); - e.stopPropagation(); + e.stopPropagation(); var url = window.location.hash; @@ -111,7 +111,7 @@ App.prototype.after_login = function(backend) { this.backend = new Pond(this, localStorage.server_url, localStorage.session_id) } else { this.backend = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id); - $("#setpublished").removeClass("invisible"); + $("#setpublished").removeClass("hidden"); } var numArticles = localStorage.numArticles; @@ -166,45 +166,43 @@ App.prototype.reload = function() { App.prototype.gotUnreadFeeds = function(new_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; - if(old_articles) { - this.unread_articles = JSON.parse(old_articles); - } - this.populateList(); - } - + + // 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; + if(old_articles) { + this.unread_articles = JSON.parse(old_articles); + } + this.populateList(); + } + } else { - - this.unread_articles = this.unread_articles.concat(new_articles); + + this.unread_articles = this.unread_articles.concat(new_articles); - if(new_articles.length > 0) { - try { - //To check if when it fails it is the same - 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(); - } + if(new_articles.length > 0) { + try { + //To check if when it fails it is the same + 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(); + } } }; @@ -425,7 +423,6 @@ App.prototype.toggleAllRead = function() { $("#all-read").removeClass('inactive'); this.updateList(); - this.backend.setArticlesRead(articles); } else { @@ -441,7 +438,6 @@ App.prototype.toggleAllRead = function() { this.updateList(); this.backend.setArticlesUnread(articles); - } }; @@ -454,8 +450,7 @@ App.prototype.toggleStarred = function() { this.updateList(); this.backend.setArticleStarred(article); $("#setstarred").removeClass('inactive'); - } - else { + } else { article.marked = false; this.updateList(); this.backend.setArticleUnstarred(article); @@ -472,8 +467,7 @@ App.prototype.togglePublished = function() { article.published = true; this.backend.setArticlePublished(article); $("#setpublished").removeClass('inactive'); - } - else { + } else { article.published = false; this.backend.setArticleUnpublished(article); $("#setpublished").addClass('inactive'); @@ -518,20 +512,18 @@ App.prototype.fontChange = function(size) { }; -App.prototype.numArticles= function(askfor) { +App.prototype.numArticles = function(askfor) { if(askfor < 200 && askfor > 0) { localStorage.numArticles=askfor; - } - else { + } else { localStorage.numArticles=100; } }; -App.prototype.maxDownload= function(maxdata) { +App.prototype.maxDownload = function(maxdata) { if(maxdata < 5000000 && maxdata > 100000) { localStorage.maxDownload=maxdata; - } - else { + } else { localStorage.maxDownload=500000; } }; From 710416015c382495a50e72566abd3152e14d484f Mon Sep 17 00:00:00 2001 From: Jeena Date: Mon, 27 Oct 2014 00:29:27 +0100 Subject: [PATCH 37/37] version bump --- manifest.webapp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.webapp b/manifest.webapp index 1d35659..a640452 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -19,5 +19,5 @@ } }, "installs_allowed_from": ["*"], - "version": "0.4.2" + "version": "0.4.3" }