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

- + - + - +

@@ -87,16 +87,15 @@

- - - + +

-

+

diff --git a/js/App.js b/js/App.js index ff92dad..d2cb11e 100644 --- a/js/App.js +++ b/js/App.js @@ -8,12 +8,19 @@ function App() { 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); + var _this = this; + + window.onkeydown = function(e) { + if(e.keyCode == 39) { + _this.showNext(); + } else if(e.keyCode == 37) { + _this.showPrevious(); + } else if(e.keyCode == 13) { + _this.openInBrowser(); + } else if(e.keyCode == 82) { + _this.reload(); + } + } }; App.prototype.authenticate = function() { @@ -21,11 +28,11 @@ 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; @@ -33,7 +40,7 @@ App.prototype.after_login = function(backend) { // do not reload page e.preventDefault(); - e.stopPropagation(); + e.stopPropagation(); var url = window.location.hash; @@ -54,8 +61,6 @@ 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") { @@ -93,15 +98,11 @@ App.prototype.after_login = function(backend) { } // set up swiping - var options = { - dragLockToAxis: true, - dragBlockHorizontal: true - }; - var hammertime = new Hammer($("#full"), options); - hammertime.on("dragleft", function(ev){ ev.gesture.preventDefault(); }); - hammertime.on("dragright", function(ev){ ev.gesture.preventDefault(); }); - hammertime.on("swipeleft", function(ev){ _this.showNext(); ev.gesture.preventDefault(); }); - hammertime.on("swiperight", function(ev){ _this.showPrevious(); ev.gesture.preventDefault(); }); + jester($("#full")).flick(function(touches, direction) { + if(direction == "left") _this.showNext(); + else _this.showPrevious(); + }); + this.changeToPage("#list"); @@ -111,16 +112,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("hidden"); } - - var numArticles = localStorage.numArticles; - if(!numArticles) numArticles = 50; - this.numArticles(numArticles); - var maxDownload = localStorage.maxDownload; - if(!maxDownload) maxDownload = 500000; - this.maxDownload(maxDownload); - this.reload(); }; @@ -158,51 +150,30 @@ App.prototype.setColor = function(color) { App.prototype.reload = function() { this.unread_articles = []; - $("#all-read").addClass('inactive'); - var number=parseInt(localStorage.numArticles); - this.backend.reload(this.gotUnreadFeeds.bind(this),number); + $("#all-read").innerHTML = "❌"; + this.backend.reload(this.gotUnreadFeeds.bind(this)); }; 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(); + 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(); + } else { - + 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.backend.getUnreadFeeds(this.gotUnreadFeeds.bind(this), this.unread_articles); + } else { + localStorage.unread_articles = JSON.stringify(this.unread_articles); this.populateList(); - } + } } }; @@ -225,8 +196,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 += "
"; } @@ -249,9 +222,9 @@ App.prototype.updateList = function() { }, this); if(unread > 0) { - $("#all-read").addClass('inactive'); + $("#all-read").innerHTML = "❌"; } else { - $("#all-read").removeClass('inactive'); + $("#all-read").innerHTML = "✓"; } this.updatePieChart(); @@ -273,7 +246,6 @@ 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]; @@ -298,7 +270,7 @@ App.prototype.updatePieChart = function() { if(all > 0) { ctx.font = "12px FeuraSans, sans-serif"; - ctx.fillStyle = tx; + ctx.fillStyle = "#fff"; ctx.textAlign = "center"; var text = unread + "/" + all; var x = canvas.width / 2; @@ -318,9 +290,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; @@ -328,29 +300,23 @@ 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; - - // Open all links in browser + 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"; }); if(article.unread) { - $("#setunread").addClass('inactive'); + $("#setunread").innerHTML = "❌"; } else { - $("#setunread").removeClass('inactive'); + $("#setunread").innerHTML = "✓"; } - if(!article.marked) { - $("#setstarred").addClass('inactive'); + if(article.marked) { + $("#setstarred").innerHTML = "★"; } else { - $("#setstarred").removeClass('inactive'); - } - - if(!article.published) { - $("#setpublished").addClass('inactive'); - } else { - $("#setpublished").removeClass('inactive'); + $("#setstarred").innerHTML = "☆"; } }; @@ -377,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 @@ -388,7 +358,7 @@ App.prototype.setCurrentRead = function() { article.set_unread = false; - $("#setunread").removeClass('inactive'); + $("#setunread").innerHTML = "✓"; this.updatePieChart(); }; @@ -398,11 +368,11 @@ App.prototype.toggleCurrentUnread = function() { if(article.unread) { article.unread = false; article.set_unread = false; - $("#setunread").removeClass('inactive'); + $("#setunread").innerHTML = "✓"; } else { article.unread = true; article.set_unread = true; - $("#setunread").addClass('inactive'); + $("#setunread").innerHTML = "❌"; } this.updateList(); @@ -411,7 +381,7 @@ App.prototype.toggleCurrentUnread = function() { App.prototype.toggleAllRead = function() { - if($("#all-read").hasClass('inactive')) { // set all read + if($("#all-read").innerHTML == "❌") { // set all read var articles = []; for (var i = 0; i < this.unread_articles.length; i++) { @@ -420,9 +390,10 @@ App.prototype.toggleAllRead = function() { article.set_unread = false; articles.push(article); } - $("#all-read").removeClass('inactive'); + $("#all-read").innerHTML = "✓"; this.updateList(); + this.backend.setArticlesRead(articles); } else { @@ -434,10 +405,11 @@ App.prototype.toggleAllRead = function() { article.set_unread = false; articles.push(article); } - $("#all-read").addClass('inactive'); + $("#all-read").innerHTML = "❌"; this.updateList(); this.backend.setArticlesUnread(articles); + } }; @@ -449,28 +421,13 @@ App.prototype.toggleStarred = function() { article.marked = true; this.updateList(); this.backend.setArticleStarred(article); - $("#setstarred").removeClass('inactive'); - } else { + $("#setstarred").innerHTML = "★"; + } + else { article.marked = false; this.updateList(); this.backend.setArticleUnstarred(article); - $("#setstarred").addClass('inactive'); - } - -}; - -App.prototype.togglePublished = function() { - var article = this.unread_articles[this.currentIndex]; - if(!article) return; // happens if we're not on a full article site - - if(!article.published) { - article.published = true; - this.backend.setArticlePublished(article); - $("#setpublished").removeClass('inactive'); - } else { - article.published = false; - this.backend.setArticleUnpublished(article); - $("#setpublished").addClass('inactive'); + $("#setstarred").innerHTML = "☆"; } }; @@ -510,20 +467,5 @@ 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/Login.js b/js/Login.js index c786023..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; @@ -67,7 +53,6 @@ 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); @@ -84,7 +69,6 @@ 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); @@ -108,7 +92,6 @@ 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); @@ -123,29 +106,7 @@ Login.prototype.authenticate = function(e) { return false; }; -Login.prototype.fillLoginFormFromLocalStorage = function() { - var serverUrl = localStorage.server_url; - if (serverUrl) { - $("#url").value = serverUrl; - } - var userName = localStorage.username; - if (userName) { - $("#un").value = userName; - } - var backendName = localStorage.backend; - if (backendName === "TinyTinyRSS") { - $("#login form").backend[0].checked = true; - } - else if (backendName === "OwnCloud") { - $("#login form").backend[1].checked = true; - } - else if (backendName === "Pond") { - $("#login form").backend[2].checked = true; - } -} - Login.prototype.log_out = function() { - this.fillLoginFormFromLocalStorage(); localStorage.removeItem("server_url"); localStorage.removeItem("session_id"); localStorage.removeItem("unread_articles"); diff --git a/js/OwnCloud.js b/js/OwnCloud.js deleted file mode 100644 index 89a77b3..0000000 --- a/js/OwnCloud.js +++ /dev/null @@ -1,259 +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.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - xhr.withCredentials = true; - xhr.setRequestHeader('Authorization', 'Basic ' + this.session_id); - var body = JSON.stringify(options); - xhr.send(body); -} - -OwnCloud.prototype.reload = function(callback,limit) { - var _this = this; - this.getFeeds(function() { _this.getUnreadFeeds(callback,0,limit); }); -}; - -OwnCloud.prototype.getUnreadFeeds = function(callback, skip, limit) { - if(skip) { - skip = skip[skip.length - 1].id; - } - - var options = { - batchSize: limit || 700, - offset: skip || 0, - type: 3, - id: 0, - getRead: false - }; - - var _this = this; - this.doOperation("GET", "items", options, function(data) { - - var items = data.items; - - function isFeedAvailable(o) { - return !!_this.feeds[o.feedId]; - } - - if(items.every(isFeedAvailable)) { - callback(items.map(_this.normalize_article, _this)); - } else { - _this.getFeeds(function() { - callback(items.map(_this.normalize_article, _this)); - }); - } - }); -}; - - -OwnCloud.prototype.toString = function() { - return "OwnCloud" -}; - -OwnCloud.prototype.getFeeds = function(callback) { - var _this = this; - this.doOperation("GET", "feeds", {}, function(data) { - - _this.feeds = {}; - for (var i = 0; i < data.feeds.length; i++) { - var feed = data.feeds[i]; - _this.feeds[feed.id] = feed; - } - - localStorage.feeds = JSON.stringify(_this.feeds); - callback(); - }); -}; - -OwnCloud.prototype.setArticlesRead = function(articles, callback) { - - var options = { - items: articles.map(function(o) { return o.id; }), - }; - - if (navigator.onLine) { - this.doOperation("PUT", "items/read/multiple", options, callback); - } else { - this.append("read_articles", articles); - } -} - -OwnCloud.prototype.setArticleRead = function(article, callback) { - this.setArticlesRead([article], callback); -} - -OwnCloud.prototype.setArticlesUnread = function(articles, callback) { - - var options = { - items: articles.map(function(o) { return o.id; }), - }; - - if (navigator.onLine) this.doOperation("PUT", "items/unread/multiple", options, callback); - else { - this.append("unread_articles", articles); - } -}; - -OwnCloud.prototype.setArticleUnread = function(article, callback) { - this.setArticlesUnread([article], callback); -} - -OwnCloud.prototype.setArticlesStarred = function(articles, callback) { - - var options = { - items: articles.map(function(o) { return { feedId: o.feed_id, guidHash: o.guid_hash }; }) - }; - - if (navigator.onLine) { - this.doOperation("PUT", "items/star/multiple", options, callback); - } else { - this.append("starred_articles", articles); - } -}; - -OwnCloud.prototype.setArticleStarred = function(article, callback) { - this.setArticlesStarred([article], callback); -} - -OwnCloud.prototype.setArticlesUnstarred = function(articles, callback) { - - var options = { - items: articles.map(function(o) { return { feedId: o.feed_id, guidHash: o.guid_hash }; }) - }; - - if (navigator.onLine) { - this.doOperation("PUT", "items/unstar/multiple", options, callback); - } else { - this.append("unstarred_articles", articles); - } -}; - -OwnCloud.prototype.setArticleUnstarred = function(articles, callback) { - this.setArticlesUnstarred([articles], callback); -} - -OwnCloud.prototype.normalize_article = function(article) { - var feed = this.feeds[article.feedId]; - var feed_title = ""; - if(feed) { - feed_title = feed.title; - } - - return { - id: article.id, - guid_hash: article.guidHash, - title: article.title, - content: article.body, - feed_title: feed_title, - feed_id: article.feedId, - excerpt: article.body.stripHTML().substring(0, 100), - updated: article.pubDate, - link: article.link, - marked: article.starred, - unread: article.unread - } -}; - -OwnCloud.prototype.logOut = function() { - this.doOperation("logout"); - localStorage.feeds = null; -}; - -OwnCloud.prototype.getFeedFor = function(o) { - return this.feeds[o.feedId]; -}; - -OwnCloud.prototype.append = function(key, array) { - - var tmp = localStorage[key]; - - if (typeof tmp !== "undefined") tmp = JSON.parse(tmp); - else tmp = []; - - tmp.concat(array); - localStorage[key] = JSON.stringify(tmp); -}; - -OwnCloud.login = function(server_url, user, password, callback) { - - var url = server_url + "/index.php/apps/news/api/v1-2/version"; - - var xhr = new XMLHttpRequest({mozSystem: true}); - xhr.onreadystatechange = function() { - if(xhr.readyState == 4) { - if(xhr.status == 200) { - callback(JSON.parse(xhr.responseText)) - } else { - if(xhr.status == 0) { - alert("Something went wrong, please check your credentials and the server address") - } else { - alert("error: " + xhr.status + " " + xhr.statusText); - } - } - } - } - - xhr.open("GET", url, true); - xhr.withCredentials = true; - var auth = btoa(user + ':' + password); - xhr.setRequestHeader('Authorization', 'Basic ' + auth); - xhr.send(); -} diff --git a/js/Pond.js b/js/Pond.js index 99abb41..1c424a6 100644 --- a/js/Pond.js +++ b/js/Pond.js @@ -15,13 +15,7 @@ Pond.prototype.onoffline = function() { }; Pond.prototype.ononline = function() { - ["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]); - } - }); + // Send read }; Pond.prototype.toString = function() { @@ -75,15 +69,15 @@ Pond.prototype.doOperation = function(method, operation, new_options, callback) }; -Pond.prototype.reload = function(callback,limit) { +Pond.prototype.reload = function(callback) { var _this = this; - this.getFeeds(function() { _this.getUnreadFeeds(callback,0,limit); }); + this.getFeeds(function() { _this.getUnreadFeeds(callback); }); }; -Pond.prototype.getUnreadFeeds = function(callback, skip, limit) { +Pond.prototype.getUnreadFeeds = function(callback, skip) { var options = { status: "unread", - limit: limit || 100 + limit: 100 }; if(skip && skip.length > 0) { @@ -112,8 +106,6 @@ Pond.prototype.getUnreadFeeds = function(callback, skip, limit) { 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++) { @@ -165,11 +157,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(status + "_articles", articles); + if (navigator.onLine) this.doOperation("PUT", url, options, callback); + else { + this.append("unread_articles", articles); } + } Pond.prototype.setArticleRead = function(article, callback) { @@ -187,7 +179,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"); }) } @@ -200,16 +192,6 @@ 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; @@ -231,11 +213,7 @@ Pond.login = function(server_url, user, password, callback) { if(xhr.status == 201) { callback(JSON.parse(xhr.responseText)) } else { - if(xhr.status == 0) { - alert("Something went wrong, please check your credentials and the server address") - } else { - alert("error: " + typeof(xhr.status) + " " + xhr.statusText + "\n\n" + xhr.responseText); - } + alert("error: " + typeof(xhr.status) + " " + xhr.statusText + "\n\n" + xhr.responseText) } } } @@ -244,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 deleted file mode 100644 index 2644d02..0000000 --- a/js/TinyTinyRSS.js +++ /dev/null @@ -1,225 +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", "published", "unpublished"].forEach(function(type) { - var articles = localStorage[type + "_articles"]; - if(articles) { - var callback = function(ok) { if(ok) localStorage[type + "_articles"] = null } - this.call("setArticles" + type.capitalize(), [JSON.parse(articles), callback]); - } - }); -}; - -TinyTinyRSS.prototype.doOperation = function(operation, new_options, callback) { - if(!navigator.onLine) { - callback(null); - return; - } - - var url = this.server_url + "/api/"; - var options = { - sid: this.session_id, - op: operation - }; - - for (var key in new_options) { - options[key] = new_options[key]; - } - - var xhr = new XMLHttpRequest({mozSystem: true}); - xhr.onreadystatechange = function() { - if(xhr.readyState == 4) { - if(xhr.status == 200) { - if(callback) - callback(JSON.parse(xhr.responseText).content); - } else { - if(xhr.status != 0) alert("error: " + xhr.status + " " + xhr.statusText); - if(callback) callback(null); - } - } - } - xhr.open("POST", url, true); - xhr.send(JSON.stringify(options)); -} - -TinyTinyRSS.prototype.reload = function(callback,limit) { - this.getUnreadFeeds(callback, 0, limit); -}; - -TinyTinyRSS.prototype.getUnreadFeeds = function(callback, skip, limit) { - skip = skip.length; - var options = { - show_excerpt: false, - view_mode: "unread", - show_content: true, - feed_id: -4, - limit: limit || 0, - skip: skip || 0 - }; - - this.doOperation("getHeadlines", options, callback); -} - -TinyTinyRSS.prototype.setArticlesRead = function(articles, callback) { - - var options = { - article_ids: articles.map(function(o) { return o.id }).join(","), - mode: 0, - field: 2 - }; - - if (navigator.onLine) { - this.doOperation("updateArticle", options, callback); - } else { - this.append("read_articles", articles); - } -}; - -TinyTinyRSS.prototype.setArticleRead = function(article, callback) { - this.setArticlesRead([article], callback); -}; - - -TinyTinyRSS.prototype.setArticlesUnread = function(articles, callback) { - - var options = { - article_ids: articles.map(function(o) { return o.id }).join(","), - mode: 1, - field: 2 - }; - - if (navigator.onLine) { - this.doOperation("updateArticle", options, callback); - } else { - this.append("unread_articles", articles); - } -}; - -TinyTinyRSS.prototype.setArticleUnread = function(article, callback) { - this.setArticlesUnread([article], callback); -}; - -TinyTinyRSS.prototype.setArticlesStarred = function(articles, callback) { - - var options = { - article_ids: articles.map(function(o) { return o.id }).join(","), - mode: 1, - field: 0 - }; - - if (navigator.onLine) { - this.doOperation("updateArticle", options); - } else { - this.append("starred_articles", articles); - } -}; - -TinyTinyRSS.prototype.setArticleStarred = function(article, callback) { - this.setArticlesStarred([article], callback); -}; - -TinyTinyRSS.prototype.setArticlesUnstarred = function(articles, callback) { - - var options = { - article_ids: articles.map(function(o) { return o.id}).join(","), - mode: 0, - field: 0 - }; - - if (navigator.onLine) { - this.doOperation("updateArticle", options, callback); - } else { - this.append("unstarred_articles", articles); - } -}; - -TinyTinyRSS.prototype.setArticleUnstarred = function(article, callback) { - this.setArticlesUnstarred([article], callback); -}; - -TinyTinyRSS.prototype.setArticlesPublished = function(articles, callback) { - - var options = { - article_ids: articles.map(function(o) { return o.id }).join(","), - mode: 1, - field: 1 - }; - - if (navigator.onLine) { - this.doOperation("updateArticle", options); - } else { - this.append("published_articles", articles); - } -}; - -TinyTinyRSS.prototype.setArticlePublished = function(article, callback) { - this.setArticlesPublished([article], callback); -}; - -TinyTinyRSS.prototype.setArticlesUnpublished = function(articles, callback) { - - var options = { - article_ids: articles.map(function(o) { return o.id}).join(","), - mode: 0, - field: 1 - }; - - if (navigator.onLine) { - this.doOperation("updateArticle", options, callback); - } else { - this.append("unpublished_articles", articles); - } -}; - -TinyTinyRSS.prototype.setArticleUnpublished = function(article, callback) { - this.setArticlesUnpublished([article], callback); -}; - -TinyTinyRSS.prototype.append = function(key, array) { - var tmp = localStorage[key]; - - if (typeof tmp !== "undefined") tmp = JSON.parse(tmp); - else tmp = []; - - tmp.concat(array); - localStorage[key] = JSON.stringify(tmp); -}; - -TinyTinyRSS.prototype.logOut = function() { - this.doOperation("logout"); -}; - -TinyTinyRSS.login = function(server_url, user, password, callback) { - - var url = server_url + "/api/"; - var options = {op: "login", user: user, password: password}; - - var xhr = new XMLHttpRequest({mozSystem: true}); - xhr.onreadystatechange = function() { - if(xhr.readyState == 4) { - if(xhr.status == 200) { - callback(JSON.parse(xhr.responseText).content) - } else { - if(xhr.status == 0) { - alert("Something went wrong, please check your credentials and the server address") - } else { - alert("error: " + xhr.status + " " + xhr.statusText) - } - } - } - } - xhr.open("POST", url, true); - xhr.send(JSON.stringify(options)); -} diff --git a/js/application.js b/js/application.js index 5e38312..ebacb08 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 = /[^\>](https?:\/\/[^\s\<]*)/ig; + return this.replace(exp," $1"); +} + if(!window.app) window.app = new App(); diff --git a/js/hammer.js b/js/hammer.js deleted file mode 100644 index 703ee55..0000000 --- a/js/hammer.js +++ /dev/null @@ -1,2163 +0,0 @@ -/*! 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.2, - - /** - * vertical swipe velocity - * @property swipeVelocityY - * @type {Number} - * @default 0.6 - */ - swipeVelocityY: 0.2 - }, - - 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 new file mode 100644 index 0000000..4612443 --- /dev/null +++ b/js/jester.js @@ -0,0 +1,599 @@ +/* + * 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)); diff --git a/manifest.webapp b/manifest.webapp index a640452..2149404 100644 --- a/manifest.webapp +++ b/manifest.webapp @@ -1,6 +1,6 @@ { "name": "FeedMonkey", - "description": "A feed mobile client with which you can read your RSS feeds and mark them as read on your server. Works also offline.", + "description": "A TinyTinyRSS 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 server." + "description": "Connection with your own TinyTinyRSS server." } }, "installs_allowed_from": ["*"], - "version": "0.4.3" -} + "version": "0.3.0" +} \ No newline at end of file