diff --git a/css/screen.css b/css/screen.css index 77aed80..97c1bc7 100644 --- a/css/screen.css +++ b/css/screen.css @@ -73,6 +73,11 @@ button:active, a:active, .button:active { padding: 10px 20px; } +#login .backends label { + display: block; + margin-bottom: 1px; +} + label { display: none; } @@ -130,7 +135,7 @@ img { } @media screen and (width: 320px) { - #full > article * { + #full > article * , #full > article pre { max-width: 300px !important; overflow: auto; } @@ -356,4 +361,4 @@ canvas { #settings .button.small { width: 20%; text-align: center; -} \ No newline at end of file +} diff --git a/index.html b/index.html index 95997c9..3b158b8 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,7 @@ + @@ -61,6 +62,10 @@ +

+ + +

diff --git a/js/App.js b/js/App.js index 579ccd9..0438248 100644 --- a/js/App.js +++ b/js/App.js @@ -13,7 +13,7 @@ App.prototype.authenticate = function() { }; -App.prototype.after_login = function() { +App.prototype.after_login = function(backend) { var request = window.navigator.mozApps.getSelf(); request.onsuccess = function() { @@ -92,12 +92,16 @@ App.prototype.after_login = function() { this.changeToPage("#list"); - this.ttrss = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id); + if(backend == "OwnCloud") { + this.backend = new OwnCloud(this, localStorage.server_url, localStorage.session_id); + } else { + this.backend = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id); + } this.reload(); }; App.prototype.logout = function() { - this.ttrss.logOut(); + this.backend.logOut(); this.unread_articles = []; this.populateList(); this.login.log_out(); @@ -131,7 +135,7 @@ App.prototype.setColor = function(color) { App.prototype.reload = function() { this.unread_articles = []; $("#all-read").innerHTML = "❌"; - this.ttrss.getUnreadFeeds(this.gotUnreadFeeds.bind(this)); + this.backend.reload(this.gotUnreadFeeds.bind(this)); }; App.prototype.gotUnreadFeeds = function(new_articles) { @@ -149,7 +153,7 @@ App.prototype.gotUnreadFeeds = function(new_articles) { this.unread_articles = this.unread_articles.concat(new_articles); if(new_articles.length > 0) { - this.ttrss.getUnreadFeeds(this.gotUnreadFeeds.bind(this), this.unread_articles.length); + this.backend.getUnreadFeeds(this.gotUnreadFeeds.bind(this), this.unread_articles); } else { localStorage.unread_articles = JSON.stringify(this.unread_articles); this.populateList(); @@ -210,7 +214,9 @@ App.prototype.updateList = function() { this.updatePieChart(); }; -App.prototype.updatePieChart = function(all, unread) { +App.prototype.updatePieChart = function() { + + if(!this.unread_articles) return; // happens on loginpage var all = this.unread_articles.length; var unread = 0; @@ -312,7 +318,7 @@ App.prototype.setCurrentRead = function() { if(!article.set_unread) { article.unread = false; this.updateList(); - this.ttrss.setArticleRead(article.id); + this.backend.setArticleRead(article); } article.set_unread = false; @@ -335,39 +341,39 @@ App.prototype.toggleCurrentUnread = function() { } this.updateList(); - this.ttrss.setArticleUnread(article.id); + this.backend.setArticleUnread(article); }; App.prototype.toggleAllRead = function() { if($("#all-read").innerHTML == "❌") { // set all read - var ids = []; + var articles = []; for (var i = 0; i < this.unread_articles.length; i++) { var article = this.unread_articles[i]; article.unread = false; article.set_unread = false; - ids.push(article.id); + articles.push(article); } $("#all-read").innerHTML = "✓"; this.updateList(); - this.ttrss.setArticleRead(ids.join(",")); + this.backend.setArticlesRead(articles); } else { - var ids = []; + var articles = []; for (var i = 0; i < this.unread_articles.length; i++) { var article = this.unread_articles[i]; article.unread = true; article.set_unread = false; - ids.push(article.id); + articles.push(article); } $("#all-read").innerHTML = "❌"; this.updateList(); - this.ttrss.setArticleUnread(ids.join(",")); + this.backend.setArticlesUnread(articles); } }; @@ -379,13 +385,13 @@ App.prototype.toggleStarred = function() { if(!article.marked) { article.marked = true; this.updateList(); - this.ttrss.setArticleStarred(article.id); + this.backend.setArticleStarred(article); $("#setstarred").innerHTML = "★"; } else { article.marked = false; this.updateList(); - this.ttrss.setArticleUnStarred(article.id); + this.backend.setArticleUnstarred(article); $("#setstarred").innerHTML = "☆"; } diff --git a/js/Login.js b/js/Login.js index f405154..62a5542 100644 --- a/js/Login.js +++ b/js/Login.js @@ -1,11 +1,10 @@ function Login(app) { this.app = app; - if(!this.is_logged_in()) { this.log_in(); if(!this.onLine()) alert("You need to be on line to log in to your server."); } - else this.app.after_login(); + else this.app.after_login(localStorage.backend); }; Login.prototype.onLine = function() { @@ -13,11 +12,22 @@ Login.prototype.onLine = function() { }; Login.prototype.is_logged_in = function() { - return localStorage.server_url && localStorage.session_id; + return localStorage.backend && localStorage.server_url && localStorage.session_id; }; 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 { + $("#url").placeholder = "http://example.com/tt-rss/"; + } + } + }); + }); $("#login form").addEventListener('submit', this.authenticate.bind(this)); }; @@ -26,6 +36,9 @@ Login.prototype.authenticate = function(e) { e.preventDefault(); e.stopPropagation(); + var backend = "TinyTinyRSS"; + if($("#login form").backend[1].checked) backend = "OwnCloud"; + var server_url = $("#url").value; var user = $("#un").value; var password = $("#pw").value; @@ -45,26 +58,47 @@ Login.prototype.authenticate = function(e) { } var _this = this; - TinyTinyRSS.login(server_url, user, password, function(data) { - if(data.error) { - if(data.error == "API_DISABLED") { - alert("You need to enable API access in your TTRSS preferences.\n\nTo do so go to your server log in and then in Preferences -> General -> Enable API access. Check the box and save. Then try again to log in.") - } else if(data.error == "LOGIN_ERROR") { - alert("Login error\n\nIt seems you provided a wrong username or password.") - } else { - alert(data.error); - } - } else { - localStorage.server_url = server_url; - localStorage.session_id = data.session_id; - _this.app.after_login(); - - $("#url").value = ""; - $("#un").value = ""; - $("#pw").value = ""; - } - }); + if(backend == "OwnCloud") { + OwnCloud.login(server_url, user, password, function(data) { + if(data.version) { + var auth = btoa(user + ':' + password); + localStorage.server_url = server_url; + localStorage.session_id = auth; + localStorage.backend = "OwnCloud"; + _this.app.after_login(localStorage.backend); + + $("#url").value = ""; + $("#un").value = ""; + $("#pw").value = ""; + } else { + alert("Something went wrong, please check every input field and try again."); + } + }); + + } else { + TinyTinyRSS.login(server_url, user, password, function(data) { + if(data.error) { + if(data.error == "API_DISABLED") { + alert("You need to enable API access in your TTRSS preferences.\n\nTo do so go to your server log in and then in Preferences -> General -> Enable API access. Check the box and save. Then try again to log in.") + } else if(data.error == "LOGIN_ERROR") { + alert("Login error\n\nIt seems you provided a wrong username or password.") + } else { + alert(data.error); + } + + } else { + localStorage.server_url = server_url; + localStorage.session_id = data.session_id; + localStorage.backend = "TinyTinyRSS"; + _this.app.after_login(localStorage.backend); + + $("#url").value = ""; + $("#un").value = ""; + $("#pw").value = ""; + } + }); + } return false; }; diff --git a/js/OwnCloud.js b/js/OwnCloud.js new file mode 100644 index 0000000..34694a8 --- /dev/null +++ b/js/OwnCloud.js @@ -0,0 +1,253 @@ +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(); + xhr.onreadystatechange = function() { + if(xhr.readyState == 4) { + if(xhr.status == 200) { + if(callback) + callback(JSON.parse(xhr.responseText)); + } else { + if(xhr.status != 0) alert("error: " + xhr.status + " " + xhr.statusText); + if(callback) callback(null); + } + } + } + xhr.open(method, url, true); + xhr.withCredentials = true; + xhr.setRequestHeader('Authorization', 'Basic ' + this.session_id); + var body = JSON.stringify(options); + xhr.send(body); +} + +OwnCloud.prototype.reload = function(callback) { + var _this = this; + this.getFeeds(function() { _this.getUnreadFeeds(callback); }); +}; + +OwnCloud.prototype.getUnreadFeeds = function(callback, skip) { + if(skip) { + skip = skip[skip.length - 1].id; + } + + var options = { + batchSize: 700, + offset: skip || 0, + type: 3, + id: 0, + getRead: false + }; + + var _this = this; + this.doOperation("GET", "items", options, function(data) { + var items = data.items; + + function isFeedAvailable(o) { + return !!_this.feeds[o.feedId]; + } + + if(items.every(isFeedAvailable)) { + callback(items.map(_this.normalize_article, _this)); + } else { + _this.getFeeds(function() { + callback(items.map(_this.normalize_article, _this)); + }); + } + }); +}; + + +OwnCloud.prototype.toString = function() { + return "OwnCloud" +}; + +OwnCloud.prototype.getFeeds = function(callback) { + var _this = this; + this.doOperation("GET", "feeds", {}, function(data) { + + this.feeds = {}; + for (var i = 0; i < data.feeds.length; i++) { + var feed = data.feeds[i]; + this.feeds[feed.id] = feed; + } + + localStorage.feeds = JSON.stringify(this.feeds); + + callback(); + }); +}; + +OwnCloud.prototype.setArticlesRead = function(articles, callback) { + + var options = { + items: articles.map(function(o) { return o.id; }), + }; + + if (navigator.onLine) { + this.doOperation("PUT", "items/read/multiple", options, callback); + } else { + this.append("read_articles", articles); + } +} + +OwnCloud.prototype.setArticleRead = function(article, callback) { + this.setArticlesRead([article], callback); +} + +OwnCloud.prototype.setArticlesUnread = function(articles, callback) { + + var options = { + items: articles.map(function(o) { return o.id; }), + }; + + if (navigator.onLine) this.doOperation("PUT", "items/unread/multiple", options, callback); + else { + this.append("unread_articles", articles); + } +}; + +OwnCloud.prototype.setArticleUnread = function(article, callback) { + this.setArticlesUnread([article], callback); +} + +OwnCloud.prototype.setArticlesStarred = function(articles, callback) { + console.log(JSON.stringify(articles)) + 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, 50), + updated: article.pubDate, + link: article.link, + marked: article.starred, + unread: article.unread + } +}; + +OwnCloud.prototype.logOut = function() { + this.doOperation("logout"); +}; + +OwnCloud.prototype.getFeedFor = function(o) { + return this.feeds[o.feedId]; +}; + +OwnCloud.prototype.append = function(key, array) { + + var tmp = localStorage[key]; + + if (typeof tmp !== "undefined") tmp = JSON.parse(tmp); + else tmp = []; + + tmp.concat(options.items); + localStorage[key] = JSON.stringify(tmp); +}; + +OwnCloud.login = function(server_url, user, password, callback) { + + var url = server_url + "/index.php/apps/news/api/v1-2/version"; + + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if(xhr.readyState == 4) { + if(xhr.status == 200) { + callback(JSON.parse(xhr.responseText)) + } else { + alert("error: " + xhr.status + " " + xhr.statusText) + } + } + } + + xhr.open("GET", url, true); + xhr.withCredentials = true; + var auth = btoa(user + ':' + password); + xhr.setRequestHeader('Authorization', 'Basic ' + auth); + xhr.send(); +} diff --git a/js/TinyTinyRSS.js b/js/TinyTinyRSS.js index 05a4994..4479f49 100644 --- a/js/TinyTinyRSS.js +++ b/js/TinyTinyRSS.js @@ -12,22 +12,14 @@ TinyTinyRSS.prototype.onoffline = function() { }; TinyTinyRSS.prototype.ononline = function() { - var read_articles = localStorage.read_articles; - if (read_articles ) { - read_articles = JSON.parse(localStorage.read_articles); - this.setArticleRead(read_articles.join(","), function() { - localStorage.read_articles = null; - }); - } - - var unread_articles = localStorage.unread_articles; - if (unread_articles) { - unread_articles = JSON.parse(unread_articles); - this.setArticleUnread(unread_articles.join(","), function() { - localStorage.unread_articles(); - }); - } + ["read", "unread", "starred", "unstarred"].forEach(function(type) { + var articles = localStorage[type + "_articles"]; + if(articles) { + var callback = function(ok) { if(ok) localStorage[type + "_articles"] = null } + this.call("setArticles" + type.capitalize(), [JSON.parse(articles), callback]); + } + }); }; TinyTinyRSS.prototype.doOperation = function(operation, new_options, callback) { @@ -62,7 +54,12 @@ TinyTinyRSS.prototype.doOperation = function(operation, new_options, callback) { xhr.send(JSON.stringify(options)); } +TinyTinyRSS.prototype.reload = function(callback) { + this.getUnreadFeeds(callback, []); +}; + TinyTinyRSS.prototype.getUnreadFeeds = function(callback, skip) { + skip = skip.length; var options = { show_excerpt: false, view_mode: "unread", @@ -74,63 +71,92 @@ TinyTinyRSS.prototype.getUnreadFeeds = function(callback, skip) { this.doOperation("getHeadlines", options, callback); } -TinyTinyRSS.prototype.setArticleRead = function(article_id) { +TinyTinyRSS.prototype.setArticlesRead = function(articles, callback) { + var options = { - article_ids: article_id, + 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 { - var read_articles = localStorage.read_articles; - if(typeof read_articles !== "undefined") read_articles = JSON.parse(read_articles); - else read_articles = []; - read_articles.push(article_id); - localStorage.read_articles = JSON.stringify(read_articles); + this.append("starred_articles", articles); } }; -TinyTinyRSS.prototype.setArticleStarred = function(article_id) { - var options = { - article_ids: article_id, - mode: 1, - field: 0 - }; - - if (navigator.onLine) { - this.doOperation("updateArticle", options); - } +TinyTinyRSS.prototype.setArticleStarred = function(article, callback) { + this.setArticlesStarred([article], callback); }; -TinyTinyRSS.prototype.setArticleUnStarred = function(article_id) { +TinyTinyRSS.prototype.setArticlesUnstarred = function(articles, callback) { + var options = { - article_ids: article_id, + article_ids: articles.map(function(o) { return o.id}).join(","), mode: 0, field: 0 }; if (navigator.onLine) { - this.doOperation("updateArticle", options); - } + this.doOperation("updateArticle", options, callback); + } else { + this.append("unstarred_articles", articles); + } }; -TinyTinyRSS.prototype.setArticleUnread = function(article_id) { - var options = { - article_ids: article_id, - mode: 1, - field: 2 - }; +TinyTinyRSS.prototype.setArticleUnstarred = function(article, callback) { + this.setArticlesUnstarred([article], callback); +}; - if (navigator.onLine) this.doOperation("updateArticle", options); - else { - var unread_articles = localStorage.unread_articles; - if (typeof unread_articles !== "undefined") unread_articles = JSON.parse(unread_articles); - else unread_articles = []; - unread_articles.push(article_id); - localStorage.unread_articles = JSON.stringify(unread_articles); - } +TinyTinyRSS.prototype.append = function(key, array) { + + var tmp = localStorage[key]; + + if (typeof tmp !== "undefined") tmp = JSON.parse(tmp); + else tmp = []; + + tmp.concat(options.items); + localStorage[key] = JSON.stringify(tmp); }; TinyTinyRSS.prototype.logOut = function() { diff --git a/js/application.js b/js/application.js index 3c81170..6ab7903 100644 --- a/js/application.js +++ b/js/application.js @@ -34,5 +34,25 @@ Node.prototype.removeClass = function(cls) { } }; +var __entityMap = { + "&": "&", + "<": "<", + ">": ">" +}; + +String.prototype.escapeHTML = function() { + return String(this).replace(/[&<>]/g, function (s) { + return __entityMap[s]; + }); +} + +String.prototype.stripHTML = function() { + return this.replace(/(<([^>]+)>)/ig, ""); +} + +String.prototype.capitalize = function() { + return this.charAt(0).toUpperCase() + this.slice(1); +} + if(!window.app) window.app = new App();