initial commit

This commit is contained in:
jeena 2013-08-21 15:13:39 +02:00
commit 3a85245a8d
20 changed files with 11838 additions and 0 deletions

BIN
css/images/ajax-loader.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

3
css/jquery.mobile-1.3.1.min.css vendored Normal file

File diff suppressed because one or more lines are too long

34
css/screen.css Normal file
View file

@ -0,0 +1,34 @@
img {
max-width: 100%;
}
#full header *, #full2 header * {
margin: 0;
padding: 0;
}
#full header, #full2 header {
border-bottom: 1px solid #aaa;
margin-bottom: 1em;
padding-bottom: 1em;
}
#full h1, #full2 h1 {
font-weight: normal;
font-size: 1.2em;
}
#full h1 a, #full2 h1 a {
text-decoration: none;
}
#list .ui-li-heading {
font-weight: normal;
}
#list .unread .ui-li-heading {
font-weight: bold;
}
.smallogo { text-align: center; margin: 0; padding: 0; }
.smallogo img { width: 50%; }

BIN
img/icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
img/icon-58.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
img/icon-60.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
img/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

96
index.html Normal file
View file

@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<title>Feedthemonkey</title>
<link rel="stylesheet" href="css/jquery.mobile-1.3.1.min.css" />
<!-- Extra Codiqa features -->
<link rel="stylesheet" href="css/screen.css" />
<!-- jQuery and jQuery Mobile -->
<script src="js/jquery-1.9.1.min.js"></script>
<script src="js/jquery.mobile-1.3.1.js"></script>
<!-- Extra Codiqa features -->
<script src="js/codiqa.ext.js"></script>
<script src="js/App.js"></script>
<script src="js/TinyTinyRSS.js"></script>
<script src="js/Login.js"></script>
<script src="js/application.js"></script>
</head>
<body>
<div data-role="page" id="list">
<div data-theme="a" data-role="header">
<a data-role="button" href="#list" data-icon="refresh" data-iconpos="right" class="ui-btn-left" id="reload">
Reload
</a>
<h3>
Feedthemonkey
</h3>
<span class="ui-btn-right count" data-theme="a">0 / 0</span>
</div>
<div data-role="content">
<ul data-role="listview" data-inset="false">
<li><a href="#full">Loading ...</a></li>
</ul>
</div>
</div>
<div data-role="page" id="login" data-close-btn="none">
<div data-theme="a" data-role="header" data-close-btn="none">
<h3>
Please log in
</h3>
</div>
<div data-role="content">
<form>
<div>
<p class="smallogo"><img src="img/splash.png" alt=""></p>
<label for="url" class="ui-hidden-accessible">URL:</label>
<input type="text" name="url" id="url" value="" placeholder="http://example.com/tt-rss/" data-theme="a" />
<label for="un" class="ui-hidden-accessible">Username:</label>
<input type="text" name="user" id="un" value="" placeholder="username" data-theme="a" />
<label for="pw" class="ui-hidden-accessible">Password:</label>
<input type="password" name="pass" id="pw" value="" placeholder="password" data-theme="a" />
<button type="submit" data-theme="b">Sign in</button>
</div>
</form>
</div>
</div>
<div data-role="page" id="full">
<div data-theme="a" data-role="header">
<a data-role="button" data-transition="slide" data-direction="reverse" href="#list"
data-icon="home" data-iconpos="left" class="ui-btn-left back">
List
</a>
<h3 class="feed_title"></h3>
<span class="ui-btn-right count" data-theme="a">0 / 0</span>
</div>
<div data-role="content">
<header>
<p><span class="feed_title"></span> <span class="author"></span></p>
<h1><a class="title" href="" target="_blank"></a></h1>
<p><timedate class="date"></timedate></p>
</header>
<article class="article"></article>
</div>
<div data-role="popup" id="popup" class="ui-content">
<p><span></span> set unread</p>
</div>
</div>
</body>
</html>

162
js/App.js Normal file
View file

@ -0,0 +1,162 @@
function App() {
this.login = new Login(this);
this.currentIndex = -1;
this.page = 2;
var _this = this;
$("#reload").on("vclick", function() {
$(this).removeClass('ui-btn-active ui-focus');
_this.reload();
});
$("#full").bind("swipeleft", this.showNext.bind(this));
$("#full").bind("swiperight", this.showPrevious.bind(this));
$("#full").bind("taphold", this.setCurrentUnread.bind(this));
$(".back").on("vclick", this.setCurrentRead.bind(this));
$(".count").button();
var _this = this;
var aop = function(event, ui) { setTimeout(function() { $("#popup").popup("close") }, 2000) };
$("#popup").popup({ afteropen: aop});
};
App.prototype.authenticate = function() {
};
App.prototype.toString = function() {
return "App";
};
App.prototype.after_login = function() {
$.mobile.changePage($("#list"));
this.ttrss = new TinyTinyRSS(this, localStorage.server_url, localStorage.session_id);
this.reload();
};
App.prototype.reload = function() {
this.unread_articles = [];
this.ttrss.getUnreadFeeds(this.gotUnreadFeeds.bind(this));
};
App.prototype.gotUnreadFeeds = function(new_articles) {
if(new_articles == null) { // on error load the saved unread articles.
this.unread_articles = JSON.parse(localStorage.unread_articles);
this.populateList();
} else {
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);
} else {
localStorage.unread_articles = JSON.stringify(this.unread_articles);
this.populateList();
}
}
};
App.prototype.populateList = function() {
var ul = $("#list ul");
var html_str = "";
for (var i = 0; i < this.unread_articles.length; i++) {
var article = this.unread_articles[i];
html_str += "<li"+ (article.unread ? " class='unread'" : "") +"><a href='#full-"+i+"'><p class='ui-li-desc'><strong>" + article.feed_title + "</strong></p><h3 class='ui-li-heading'>" + article.title + "</h3><p class='ui-li-desc'>" + article.excerpt + "</p></a></li>";
}
ul.html(html_str);
ul.listview("refresh");
//$(".count").html(this.unread_articles.length + " / " + this.unread_articles.length);
//$(".count").button("refresh");
};
App.prototype.updateList = function() {
var unread = 0;
var _this = this;
$("#list ul li").each(function(i, o) {
if(!_this.unread_articles[i].unread) $(this).removeClass("unread");
else {
unread++;
$(this).addClass("unread");
}
});
//$("#list ul").listview("refresh");
//$(".count").html(unread + " / " + this.unread_articles.length);
//$(".count").button("refresh");
};
App.prototype.showFull = function(article, slide_back) {
this.currentIndex = this.unread_articles.indexOf(article);
var page_id = "#full";
$(page_id + " .date").html("");
$(page_id + " .title").html("");
$(page_id + " .title").attr("href", "");
$(page_id + " .title").attr("title", "");
$(page_id + " .feed_title").html("");
$(page_id + " .author").html("");
$(page_id + " .article").html("");
$(page_id + " .date").html((new Date(parseInt(article.updated, 10) * 1000)).toLocaleString());
$(page_id + " .title").html(article.title);
$(page_id + " .title").prop("href", article.link);
$(page_id + " .title").prop("title", article.link);
$(page_id + " .feed_title").html(article.feed_title);
if(article.author && article.author.length > 0)
$(page_id + " .author").html("&ndash; " + article.author);
$(page_id + " .article").html(article.content);
$.mobile.changePage($(page_id), { transition: "slide", reverse: slide_back });
};
App.prototype.showNext = function() {
this.setCurrentRead();
if(this.currentIndex >= this.unread_articles.length - 1) {
this.goToList();
} else {
this.currentIndex++;
this.showFull(this.unread_articles[this.currentIndex], false);
}
};
App.prototype.showPrevious = function() {
this.setCurrentRead();
if(this.currentIndex <= 0) {
this.goToList();
} else {
this.currentIndex--;
this.showFull(this.unread_articles[this.currentIndex], true);
}
};
App.prototype.setCurrentRead = function() {
var article = this.unread_articles[this.currentIndex];
if(!article.set_unread) {
article.unread = false;
this.updateList();
var _this = this;
setTimeout(function() { this.ttrss.setArticleRead(article.id); }, 500);
}
article.set_unread = false;
};
App.prototype.setCurrentUnread = function() {
var article = this.unread_articles[this.currentIndex];
article.unread = true;
article.set_unread = true;
this.ttrss.setArticleUnread(article.id);
this.updateList();
$("#popup").popup("open");
};
App.prototype.goToList = function() {
$.mobile.changePage("#list", {transition: "slide", reverse: true});
};

63
js/Login.js Normal file
View file

@ -0,0 +1,63 @@
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();
};
Login.prototype.onLine = function() {
return navigator.onLine;
};
Login.prototype.is_logged_in = function() {
return localStorage.server_url && localStorage.session_id;
};
Login.prototype.log_in = function() {
$.mobile.changePage("#login", { role: "dialog", transition: "flip", "close-btn": "none" });
$("#login form").on('submit', this.authenticate.bind(this));
};
Login.prototype.authenticate = function(e) {
var server_url = $(e.target).find("#url").val();
var user = $(e.target).find("#un").val();
var password = $(e.target).find("#pw").val();
if(!this.onLine()) {
alert("You need to be on line to log in to your server.");
return false;
}
var errs = [];
if(!server_url || server_url.indexOf("http") != 0) errs.push("add a server url that starts with http");
if(!user) errs.push("add a username");
if(!password) errs.push("add a password");
if(errs.length > 0) {
alert("Please " + errs.join(",\n") + ".");
return false;
}
var _this = this;
TinyTinyRSS.login(server_url, user, password, function(data) {
if(data.error) {
alert(data.error);
} else {
localStorage.server_url = server_url;
localStorage.session_id = data.session_id;
_this.app.after_login();
}
});
return false;
};
Login.prototype.log_out = function() {
localStorage.server_url = null;
localStorage.session_id = null;
localStorage.unread_articles = null;
this.log_in();
}

97
js/TinyTinyRSS.js Normal file
View file

@ -0,0 +1,97 @@
function TinyTinyRSS(app, server_url, session_id) {
this.app = app;
this.server_url = server_url;
this.session_id = session_id;
}
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) {
$.mobile.loading("hide");
if(xhr.status == 200) {
if(callback)
callback(JSON.parse(xhr.responseText).content);
} else {
alert("error: " + xhr.status + " " + xhr.statusText);
if(callback)
callback(null);
}
}
}
xhr.open("POST", url, true);
xhr.send(JSON.stringify(options));
$.mobile.loading("show");
}
TinyTinyRSS.prototype.getUnreadFeeds = function(callback, skip) {
var options = {
show_excerpt: false,
view_mode: "unread",
show_content: true,
feed_id: -4,
skip: skip || 0
};
this.doOperation("getHeadlines", options, callback);
}
TinyTinyRSS.prototype.setArticleRead = function(article_id) {
var options = {
article_ids: article_id,
mode: 0,
field: 2
};
this.doOperation("updateArticle", options);
};
TinyTinyRSS.prototype.setArticleUnread = function(article_id) {
var options = {
article_ids: article_id,
mode: 1,
field: 2
};
this.doOperation("updateArticle", options);
};
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) {
$.mobile.loading("hide");
if(xhr.status == 200) {
callback(JSON.parse(xhr.responseText).content)
} else {
alert("error: " + xhr.status + " " + xhr.statusText)
}
}
}
xhr.open("POST", url, true);
xhr.send(JSON.stringify(options));
$.mobile.loading("show");
}

39
js/application.js Normal file
View file

@ -0,0 +1,39 @@
function debug(obj) {
if(typeof obj != "string")
obj = JSON.stringify(obj);
alert(obj)
}
// Handle login stuff if needed
$(document).on("pageshow", function() {
if(!window.app) window.app = new App();
});
// Listen for any attempts to call changePage().
$(document).bind( "pagebeforechange", function( e, data ) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if ( typeof data.toPage === "string" ) {
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var u = $.mobile.path.parseUrl( data.toPage ),
re = /^#full-/;
if ( u.hash.search(re) !== -1 ) {
// We're being asked to display the items for a specific category.
// Call our internal method that builds the content for the category
// on the fly based on our in-memory category data structure.
var i = parseInt(u.hash.split("-")[1], 10);
app.showFull(app.unread_articles[i], false);
// Make sure to tell changePage() we've handled this call so it doesn't
// have to do anything.
e.preventDefault();
}
}
});

127
js/codiqa.ext.js Normal file
View file

@ -0,0 +1,127 @@
window.CodiqaControls = {
types: {},
instances: {},
define: function(type, control) {
control._type = type;
this.types[type] = control;
},
register: function(type, id, opts) {
var instance = new this.types[type]();
instance._type = type;
instance._id = id;
instance._opts = opts;
this.instances[id] = instance;
if(!this.types[type].prototype._isInited) {
this.types[type].prototype.initType();
}
return instance;
},
init: function() {
for(var type in this.types) {
this.types[type].prototype.initType();
}
},
refresh: function() {
for(var x in this.instances) {
this.instances[x].refresh && this.instances[x].refresh();
}
},
callbackInit: function() {
},
getInstances: function(type) {
var x, instance, instances = [];
for(x in this.instances) {
instance = this.instances[x];
if(instance._type === type) {
instances.push(instance);
}
}
return instances;
}
};
CodiqaControls.GoogleMap = function () {};
CodiqaControls.GoogleMap.prototype.initType = function() {
if( window.CodiqaControls.getInstances('googlemaps').length ) {
if(this._isInited) {
if(window.google && window.google.maps) {
CodiqaControls.GoogleMap.prototype.callbackInit();
}
} else {
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "https://maps.googleapis.com/maps/api/js?sensor=true&callback=CodiqaControls.types.googlemaps.prototype.callbackInit";
document.getElementsByTagName("head")[0].appendChild(script);
this._isInited = true;
}
}
};
CodiqaControls.GoogleMap.prototype.callbackInit = function() {
var x, instances = window.CodiqaControls.getInstances('googlemaps');
for(x = 0; x < instances.length; x++) {
instances[x]._opts.ready(instances[x]);
}
};
CodiqaControls.GoogleMap.prototype.refresh = function() {
if( this.map && this.el && $(this.el).closest('.ui-page-active').length ) {
google.maps.event.trigger(this.map, 'resize');
this.center && this.map.setCenter(this.center);
}
};
window.CodiqaControls.define('googlemaps', CodiqaControls.GoogleMap);
(function($) {
$.widget('mobile.tabbar', $.mobile.navbar, {
_create: function() {
// Set the theme before we call the prototype, which will
// ensure buttonMarkup() correctly grabs the inheritied theme.
// We default to the "a" swatch if none is found
var theme = this.element.jqmData('theme') || "a";
this.element.addClass('ui-footer ui-footer-fixed ui-bar-' + theme);
// Make sure the page has padding added to it to account for the fixed bar
this.element.closest('[data-role="page"]').addClass('ui-page-footer-fixed');
// Call the NavBar _create prototype
$.mobile.navbar.prototype._create.call(this);
},
// Set the active URL for the Tab Bar, and highlight that button on the bar
setActive: function(url) {
// Sometimes the active state isn't properly cleared, so we reset it ourselves
this.element.find('a').removeClass('ui-btn-active ui-state-persist');
this.element.find('a[href="' + url + '"]').addClass('ui-btn-active ui-state-persist');
}
});
$(document).on('pagecreate create', function(e) {
return $(e.target).find(":jqmData(role='tabbar')").tabbar();
});
$(document).on('pageshow', ":jqmData(role='page')", function(e) {
// Grab the id of the page that's showing, and select it on the Tab Bar on the page
var tabBar, id = $(e.target).attr('id');
tabBar = $.mobile.activePage.find(':jqmData(role="tabbar")');
if(tabBar.length) {
tabBar.tabbar('setActive', '#' + id);
}
window.CodiqaControls.refresh();
});
window.CodiqaControls.init();
})(jQuery);

5
js/jquery-1.9.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

11191
js/jquery.mobile-1.3.1.js Normal file

File diff suppressed because it is too large Load diff

21
manifest.webapp Normal file
View file

@ -0,0 +1,21 @@
{
"name": "Feedthemonkey",
"description": "A TinyTinyRSS mobile client.",
"launch_path": "index.html",
"icons": {
"58": "/img/icon-58.png",
"60": "/img/icon-60.png",
"128": "/img/icon-128.png"
},
"developer": {
"name": "Jabs Nu",
"url": "http://jabs.nu"
},
"default_locale": "en",
"type": "privileged",
"permissions": {
"systemXHR": {}
},
"installs_allowed_from": ["*"],
"version": "0.1"
}