restructuring of the whole code basis
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// Constants.js
|
||||
// Tentia
|
||||
//
|
||||
// Created by Jeena on 19.09.11.
|
||||
// Licence: BSD (see attached LICENCE.txt file).
|
||||
//
|
||||
|
||||
|
||||
OAUTH_CONSUMER_KEY = "JPmU8KJhiBKfpohCiWLg"
|
||||
OAUTH_CONSUMER_SECRET = "jtfSrnDrRcUnL1nqTMcAW0055m63EMClmkxhiBjQ"
|
||||
OAUTH_SIGNATURE_METHOD = "HMAC-SHA1"
|
||||
OAUTH_REQUEST_TOKEN_URL = "http://twitter.com/oauth/request_token"
|
||||
OAUTH_USER_AUTHORIZATION_URL = "http://twitter.com/oauth/authorize"
|
||||
OAUTH_ACCESS_TOKEN_URL = "http://twitter.com/oauth/access_token"
|
||||
OAUTH_SERVICE_NAME = "twitter.com"
|
||||
|
||||
APP_NAME = "Tentia";
|
||||
|
||||
API_PATH = "http://api.twitter.com/1/";
|
||||
WEBSITE_PATH = "http://twitter.com/";
|
457
WebKit/Core.js
|
@ -1,457 +0,0 @@
|
|||
//
|
||||
// Core.js
|
||||
// Tentia
|
||||
//
|
||||
// Created by Jeena on 15.04.10.
|
||||
// Licence: BSD (see attached LICENCE.txt file).
|
||||
//
|
||||
|
||||
function Core(action) {
|
||||
this.max_length = 200;
|
||||
// this.timeout = 2 * 60 * 1000;
|
||||
this.timeout = 10 * 1000; // every 10 seconds
|
||||
this.action = action;
|
||||
this.getNewData();
|
||||
this.unread_mentions = 0;
|
||||
this.since_id = null;
|
||||
this.since_id_entity = null;
|
||||
this.since_time = 0;
|
||||
|
||||
this.body = document.createElement("ol");
|
||||
this.body.className = this.action;
|
||||
this.cache = {};
|
||||
this.is_not_init = false;
|
||||
|
||||
var _this = this;
|
||||
setInterval(function() { _this.getNewData() }, this.timeout);
|
||||
}
|
||||
|
||||
Core.prototype.newStatus = function(status) {
|
||||
|
||||
if(status != null && status.length > 0) {
|
||||
for(var i = status.length-1, c=0; i>=c; --i) {
|
||||
if(this.body.childNodes.length > 0) {
|
||||
if(this.body.childNodes.length > this.max_length) {
|
||||
this.body.removeChild(this.body.lastChild);
|
||||
}
|
||||
this.body.insertBefore(this.getItem(status[i]), this.body.firstChild);
|
||||
} else {
|
||||
this.body.appendChild(this.getItem(status[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.action == "mentions" && this.is_not_init) {
|
||||
this.unread_mentions += status.length;
|
||||
controller.unreadMentions_(this.unread_mentions);
|
||||
}
|
||||
this.is_not_init = true;
|
||||
}
|
||||
|
||||
Core.prototype.logout = function() {
|
||||
this.body.innerHTML = "";
|
||||
}
|
||||
|
||||
Core.prototype.getItem = function(status) {
|
||||
|
||||
var _this = this;
|
||||
this.since_id = status.id;
|
||||
this.since_id_entity = status.entity;
|
||||
|
||||
var template = this.getTemplate();
|
||||
|
||||
template.reply_to.onclick = function() {
|
||||
var mentions = [];
|
||||
for (var i = 0; i < status.mentions.length; i++) {
|
||||
var mention = status.mentions[i];
|
||||
if(mention.entity != controller.stringForKey_("entity"))
|
||||
mentions.push(mention);
|
||||
};
|
||||
replyTo(status.entity, status.id, mentions);
|
||||
return false;
|
||||
}
|
||||
//template.retweet.onclick = function() { template.retweet.className = "hidden"; _this.retweet(status.id_str, template.item); return false; }
|
||||
|
||||
template.username.innerText = status.entity;
|
||||
template.username.href = status.entity; // FIXME open profile
|
||||
|
||||
findProfileURL(status.entity, function(profile_url) {
|
||||
if (profile_url) {
|
||||
getURL(profile_url, "GET", function(resp) {
|
||||
var profile = JSON.parse(resp.responseText);
|
||||
var basic = profile["https://tent.io/types/info/basic/v0.1.0"];
|
||||
|
||||
if (profile && basic) {
|
||||
if(basic.name) {
|
||||
template.username.title = template.username.innerText;
|
||||
template.username.innerText = basic.name;
|
||||
}
|
||||
if(basic.avatar_url) {
|
||||
template.image.onerror = function() { template.image.src = 'default-avatar.png' };
|
||||
template.image.src = basic.avatar_url;
|
||||
}
|
||||
}
|
||||
}, null, false); // do not send auth-headers
|
||||
}
|
||||
});
|
||||
|
||||
template.in_reply.parentNode.className = "hidden";
|
||||
|
||||
template.message.innerHTML = replaceUsernamesWithLinks(replaceURLWithHTMLLinks(status.content.text, status.entities, template.message));
|
||||
findMentions(template.message, status.mentions);
|
||||
|
||||
var time = document.createElement("abbr");
|
||||
time.innerText = ISODateString(new Date(status.published_at * 1000));
|
||||
time.title = time.innerText;
|
||||
time.className = "timeago";
|
||||
$(time).timeago();
|
||||
template.ago.appendChild(time);
|
||||
|
||||
// {"type":"Point","coordinates":[57.10803113,12.25854746]}
|
||||
if (status.content && status.content.location && status.content.location.type == "Point") {
|
||||
template.geo.href = "http://maps.google.com/maps?q=" + status.content.location.coordinates[0] + "," + status.content.location.coordinates[1];
|
||||
template.geo.style.display = "";
|
||||
}
|
||||
|
||||
template.source.href = status.app.url;
|
||||
template.source.innerHTML = status.app.name;
|
||||
template.source.title = status.app.url;
|
||||
|
||||
return template.item;
|
||||
}
|
||||
|
||||
Core.prototype.getTemplate = function() {
|
||||
|
||||
if(this.template == "undefined") {
|
||||
return jQuery.extend(true, {}, this.template);
|
||||
}
|
||||
|
||||
var a = document.createElement("a");
|
||||
|
||||
var item = document.createElement("li");
|
||||
|
||||
var reply_to = a.cloneNode();
|
||||
reply_to.className = "reply_to"
|
||||
reply_to.innerText = " ";
|
||||
reply_to.href = "#";
|
||||
item.appendChild(reply_to);
|
||||
|
||||
var retweet = a.cloneNode();
|
||||
retweet.className = "retweet";
|
||||
retweet.innerText = " ";
|
||||
retweet.href = "#";
|
||||
// item.appendChild(retweet); // FIXME
|
||||
|
||||
|
||||
var image = document.createElement("img");
|
||||
image.className = "image";
|
||||
image.src = "default-avatar.png";
|
||||
image.onmousedown = function(e) { e.preventDefault(); };
|
||||
item.appendChild(image);
|
||||
|
||||
var image_username = a.cloneNode();
|
||||
image.appendChild(image_username);
|
||||
|
||||
var data = document.createElement("div");
|
||||
data.className = "data";
|
||||
item.appendChild(data);
|
||||
|
||||
var head = document.createElement("h1");
|
||||
data.appendChild(head);
|
||||
|
||||
var username = a.cloneNode();
|
||||
head.appendChild(username);
|
||||
|
||||
var in_reply = document.createElement("span");
|
||||
in_reply.className = "reply";
|
||||
head.appendChild(in_reply);
|
||||
|
||||
var space = document.createTextNode(" ");
|
||||
head.appendChild(space);
|
||||
|
||||
var geo = document.createElement("a");
|
||||
geo.style.display = "none";
|
||||
head.appendChild(geo);
|
||||
|
||||
var pin = document.createElement("img");
|
||||
pin.src = "pin.png";
|
||||
pin.alt = "Map link";
|
||||
geo.appendChild(pin);
|
||||
|
||||
var in_reply_text = document.createTextNode(" in reply to ");
|
||||
in_reply.appendChild(in_reply_text)
|
||||
|
||||
var in_reply_a = a.cloneNode();
|
||||
in_reply.appendChild(in_reply_a);
|
||||
|
||||
var message = document.createElement("p");
|
||||
message.className = "message";
|
||||
data.appendChild(message);
|
||||
|
||||
var images = document.createElement("p")
|
||||
images.className = "images";
|
||||
data.appendChild(images);
|
||||
|
||||
var date = message.cloneNode();
|
||||
date.className = "date";
|
||||
data.appendChild(date);
|
||||
|
||||
var ago = a.cloneNode();
|
||||
date.appendChild(ago);
|
||||
|
||||
var from = document.createTextNode(" from ");
|
||||
date.appendChild(from)
|
||||
|
||||
var source = document.createElement("a");
|
||||
source.className = "source";
|
||||
date.appendChild(source)
|
||||
|
||||
this.template = {
|
||||
item: item,
|
||||
reply_to: reply_to,
|
||||
retweet: retweet,
|
||||
image: image,
|
||||
username: username,
|
||||
in_reply: in_reply_a,
|
||||
message: message,
|
||||
ago: ago,
|
||||
source: source,
|
||||
geo: geo,
|
||||
images: images
|
||||
}
|
||||
|
||||
return jQuery.extend(true, {}, this.template);
|
||||
}
|
||||
|
||||
Core.prototype.getNewData = function() {
|
||||
|
||||
var those = this;
|
||||
var url = URI(mkApiRootPath("/posts"));
|
||||
url.addSearch("post_types", "https://tent.io/types/post/status/v0.1.0");
|
||||
url.addSearch("limit", this.max_length);
|
||||
if(this.since_id) {
|
||||
url.addSearch("since_id", this.since_id);
|
||||
url.addSearch("since_id_entity", this.since_id_entity);
|
||||
}
|
||||
|
||||
if (this.action == "mentions") {
|
||||
url.addSearch("mentioned_entity", controller.stringForKey_("entity"));
|
||||
}
|
||||
|
||||
var http_method = "GET";
|
||||
var callback = function(resp) {
|
||||
|
||||
try {
|
||||
var json = JSON.parse(resp.responseText)
|
||||
} catch (e) {
|
||||
//alert(resp.responseText);
|
||||
alert(url + " JSON parse error");
|
||||
throw e;
|
||||
}
|
||||
|
||||
those.newStatus(json);
|
||||
}
|
||||
|
||||
var data = null;
|
||||
|
||||
if (controller.stringForKey_("user_access_token")) {
|
||||
getURL(url.toString(), http_method, callback, data); // FIXME: error callback
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Core.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity) {
|
||||
|
||||
var _this = this;
|
||||
|
||||
var url = URI(mkApiRootPath("/posts"));
|
||||
|
||||
var http_method = "POST";
|
||||
var callback = function(data) { _this.getNewData(); }
|
||||
|
||||
var data = {
|
||||
"type": "https://tent.io/types/post/status/v0.1.0",
|
||||
"published_at": (new Date().getTime() / 1000),
|
||||
"permissions": {
|
||||
"public": true
|
||||
},
|
||||
"content": {
|
||||
"text": content,
|
||||
},
|
||||
};
|
||||
|
||||
var mentions = parseMentions(content, in_reply_to_status_id, in_reply_to_entity);
|
||||
if (mentions.length > 0) {
|
||||
data["mentions"] = mentions;
|
||||
}
|
||||
|
||||
getURL(url.toString(), http_method, callback, JSON.stringify(data)); // FIXME: error callback
|
||||
}
|
||||
|
||||
/* Helper functions */
|
||||
|
||||
function replaceURLWithHTMLLinks(text, entities, message_node) {
|
||||
var exp = /(([^\^]https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_()|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||
return text.replace(exp, "<a href='$1'>$1</a>");
|
||||
}
|
||||
|
||||
function replaceUsernamesWithLinks(text, mentions) {
|
||||
return text; // FIXME!
|
||||
var username = /(^|\s)(\^)(\w+)/ig;
|
||||
var hash = /(^|\s)(#)(\w+)/ig;
|
||||
text = text.replace(username, "$1$2<a href='tentia://profile/$3'>$3</a>");
|
||||
return text.replace(hash, "$1$2<a href='http://search.twitter.com/search?q=%23$3'>$3</a>");
|
||||
}
|
||||
|
||||
function replyTo(entity, status_id, mentions) {
|
||||
var string = "^" + entity.replace("https://", "") + " ";
|
||||
for (var i = 0; i < mentions.length; i++) {
|
||||
var e = mentions[i].entity.replace("https://", "");
|
||||
if(string.indexOf(e) == -1) string += "^" + e + " ";
|
||||
}
|
||||
controller.openNewMessageWindowInReplyTo_statusId_withString_(entity, status_id, string);
|
||||
}
|
||||
|
||||
function loadPlugin(url) {
|
||||
var plugin = document.createElement("script");
|
||||
plugin.type = "text/javascript";
|
||||
plugin.src = url;
|
||||
document.getElementsByTagName("head")[0].appendChild(plugin);
|
||||
}
|
||||
|
||||
String.prototype.startsWith = function(prefix) {
|
||||
return this.indexOf(prefix) === 0;
|
||||
}
|
||||
|
||||
String.prototype.endsWith = function(suffix) {
|
||||
return this.match(suffix+"$") == suffix;
|
||||
};
|
||||
|
||||
function getUrlVars(url)
|
||||
{
|
||||
var vars = [], hash;
|
||||
if(url.indexOf("#") > -1) url = url.slice(0, url.indexOf("#"));
|
||||
var hashes = url.slice(url.indexOf('?') + 1).split('&');
|
||||
for(var i = 0; i < hashes.length; i++)
|
||||
{
|
||||
hash = hashes[i].split('=');
|
||||
vars.push(hash[0]);
|
||||
vars[hash[0]] = hash[1];
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
function replaceShortened(url, message_node) {
|
||||
var api = "http://api.bitly.com";
|
||||
if(url.startsWith("http://j.mp/")) {
|
||||
api = "http://api.j.mp";
|
||||
}
|
||||
|
||||
var api_url = api + "/v3/expand?format=json&apiKey=R_4fc2a1aa461d076556016390fa6400f6&login=twittia&shortUrl=" + url; // FIXME: new api key
|
||||
|
||||
$.ajax({
|
||||
url: api_url,
|
||||
success: function(data) {
|
||||
var new_url = data.data.expand[0].long_url;
|
||||
if (new_url) {
|
||||
var regex = new RegExp(url, "g");
|
||||
message_node.innerHTML = message_node.innerHTML.replace(regex, new_url);
|
||||
}
|
||||
},
|
||||
error:function (xhr, ajaxOptions, thrownError) {
|
||||
alert(xhr.status);
|
||||
alert(thrownError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findMentions(node, mentions) {
|
||||
var text = node.innerHTML;
|
||||
var mentions_in_text = [];
|
||||
var res = text.match(/(\^\S+)/ig);
|
||||
|
||||
if (res) {
|
||||
for (var i = 0; i < res.length; i++) {
|
||||
var name = res[i];
|
||||
var e = name.substring(1);
|
||||
if (e.substring(0,7) != "http://" && e.substring(0,8) != "https://") {
|
||||
e = "https://" + e;
|
||||
}
|
||||
for (var j = 0; j < mentions.length; j++) {
|
||||
var m = mentions[j];
|
||||
if(m.entity.startsWith(e)) {
|
||||
mentions_in_text.push({
|
||||
entity: m.entity,
|
||||
text: name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < mentions_in_text.length; i++) {
|
||||
var mention = mentions_in_text[i];
|
||||
|
||||
(function(mention) { // need this closure
|
||||
findProfileURL(mention.entity, function(profile_url) {
|
||||
if (profile_url) {
|
||||
getURL(profile_url, "GET", function(resp) {
|
||||
var profile = JSON.parse(resp.responseText);
|
||||
var basic = profile["https://tent.io/types/info/basic/v0.1.0"];
|
||||
|
||||
if (profile && basic) {
|
||||
if(basic.name) {
|
||||
var new_text = node.innerHTML.replace(
|
||||
mention.text,
|
||||
"<strong class='name' title='" + mention.entity + "'" + ">"
|
||||
+ basic.name
|
||||
+ "</strong>"
|
||||
);
|
||||
node.innerHTML = new_text;
|
||||
}
|
||||
}
|
||||
}, null, false); // do not send auth-headers
|
||||
}
|
||||
});
|
||||
})(mention);
|
||||
}
|
||||
}
|
||||
|
||||
function parseMentions(text, post_id, entity) {
|
||||
var mentions = [];
|
||||
|
||||
if (post_id && entity) {
|
||||
mentions.push({
|
||||
post: post_id,
|
||||
entity: entity
|
||||
})
|
||||
}
|
||||
|
||||
var res = text.match(/(\^\S+)/ig);
|
||||
|
||||
if (res) {
|
||||
for (var i = 0; i < res.length; i++) {
|
||||
var e = res[i].substring(1);
|
||||
if (e.substring(0,7) != "http://" && e.substring(0,8) != "https://") {
|
||||
e = "https://" + e;
|
||||
}
|
||||
if (e != entity) {
|
||||
mentions.push({entity:e});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mentions;
|
||||
}
|
||||
|
||||
function ISODateString(d){
|
||||
function pad(n){return n<10 ? '0'+n : n}
|
||||
return d.getUTCFullYear()+'-'
|
||||
+ pad(d.getUTCMonth()+1)+'-'
|
||||
+ pad(d.getUTCDate())+'T'
|
||||
+ pad(d.getUTCHours())+':'
|
||||
+ pad(d.getUTCMinutes())+':'
|
||||
+ pad(d.getUTCSeconds())+'Z'
|
||||
}
|
||||
|
||||
var tentia_instance;
|
|
@ -1,142 +0,0 @@
|
|||
//
|
||||
// OauthImplementation.js
|
||||
// Tentia
|
||||
//
|
||||
// Created by Jeena on 19.09.11.
|
||||
// Licence: BSD (see attached LICENCE.txt file).
|
||||
//
|
||||
|
||||
function getUrlVars(url) {
|
||||
var vars = [], hash;
|
||||
if(url.indexOf("#") > -1) url = url.slice(0, url.indexOf("#"));
|
||||
var hashes = url.slice(url.indexOf('?') + 1).split('&');
|
||||
for(var i = 0; i < hashes.length; i++)
|
||||
{
|
||||
hash = hashes[i].split('=');
|
||||
vars.push(hash[0]);
|
||||
vars[hash[0]] = hash[1];
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
function OauthImplementation() {
|
||||
this.app_info = {
|
||||
"id": null,
|
||||
"name": "Tentia",
|
||||
"description": "A small TentStatus client.",
|
||||
"url": "http://jabs.nu/Tentia/",
|
||||
"icon": "http://jabs.nu/Tentia/icon.png",
|
||||
"redirect_uris": [
|
||||
"tentia://oauthtoken"
|
||||
],
|
||||
"scopes": {
|
||||
"read_posts": "Uses posts to show them in a list",
|
||||
"write_posts": "Posts on users behalf"
|
||||
}
|
||||
};
|
||||
this.register_data = null;
|
||||
this.profile = null;
|
||||
this.state = null;
|
||||
|
||||
this.authenticate();
|
||||
}
|
||||
|
||||
OauthImplementation.prototype.authenticate = function() {
|
||||
this.entity = controller.stringForKey_("entity");
|
||||
this.requestProfileURL(this.entity);
|
||||
}
|
||||
|
||||
OauthImplementation.prototype.apiRoot = function() {
|
||||
return this.profile["https://tent.io/types/info/core/v0.1.0"]["servers"][0];
|
||||
}
|
||||
|
||||
OauthImplementation.prototype.requestProfileURL = function (entity) {
|
||||
var those = this;
|
||||
findProfileURL(entity, function(profile_url) {
|
||||
those.register(profile_url);
|
||||
});
|
||||
}
|
||||
|
||||
OauthImplementation.prototype.register = function (url) {
|
||||
var those = this;
|
||||
getURL(url, "GET", function(resp) {
|
||||
those.profile = JSON.parse(resp.responseText);
|
||||
controller.setString_forKey_(those.apiRoot(), "api_root");
|
||||
var callback = function(resp) {
|
||||
var data = JSON.parse(resp.responseText);
|
||||
those.authRequest(data);
|
||||
}
|
||||
getURL(mkApiRootPath("/apps"), "POST", callback, JSON.stringify(those.app_info));
|
||||
});
|
||||
}
|
||||
|
||||
OauthImplementation.prototype.authRequest = function(register_data) {
|
||||
// id
|
||||
// mac_key_id
|
||||
// mac_key
|
||||
// mac_algorithm
|
||||
this.register_data = register_data;
|
||||
|
||||
// Needed for later App Registration Modification
|
||||
controller.setString_forKey_(register_data["mac_key"], "app_mac_key");
|
||||
controller.setString_forKey_(register_data["mac_key_id"], "app_mac_key_id");
|
||||
controller.setString_forKey_(register_data["id"], "app_id");
|
||||
controller.setString_forKey_(register_data["mac_algorithm"], "app_mac_algorithm");
|
||||
|
||||
this.state = makeid(19);
|
||||
var auth = "/oauth/authorize?client_id=" + register_data["id"]
|
||||
+ "&redirect_uri=" + escape(this.app_info["redirect_uris"][0])
|
||||
+ "&scope=" + Object.keys(this.app_info["scopes"]).join(",")
|
||||
+ "&state=" + this.state
|
||||
+ "&tent_post_types=" + escape("https://tent.io/types/posts/status/v0.1.0");
|
||||
|
||||
controller.openURL_(this.apiRoot() + auth);
|
||||
}
|
||||
|
||||
OauthImplementation.prototype.requestAccessToken = function(responseBody) {
|
||||
// /oauthtoken?code=51d0115b04d1ed94001dde751c5b360f&state=aQfH1VEohYsQr86qqyv
|
||||
var urlVars = getUrlVars(responseBody);
|
||||
if(this.state && this.state != "" && urlVars["state"] == this.state) {
|
||||
|
||||
var url = mkApiRootPath("/apps/") + this.register_data["id"] + "/authorizations";
|
||||
|
||||
var requestBody = JSON.stringify({
|
||||
'code' : urlVars["code"],
|
||||
'token_type' : "mac"
|
||||
});
|
||||
|
||||
var those = this;
|
||||
var http_method = "POST";
|
||||
var callback = function(resp) {
|
||||
those.requestAccessTokenTicketFinished(resp.responseText);
|
||||
};
|
||||
|
||||
var auth_header = makeAuthHeader(
|
||||
url,
|
||||
http_method,
|
||||
controller.stringForKey_("app_mac_key"),
|
||||
controller.stringForKey_("app_mac_key_id")
|
||||
);
|
||||
|
||||
getURL(url, http_method, callback, requestBody, auth_header);
|
||||
|
||||
} else {
|
||||
alert("State is not the same: {" + this.state + "} vs {" + urlVars["state"] + "}")
|
||||
}
|
||||
|
||||
this.state = null; // reset the state
|
||||
}
|
||||
|
||||
OauthImplementation.prototype.requestAccessTokenTicketFinished = function(responseBody) {
|
||||
|
||||
var access = JSON.parse(responseBody);
|
||||
|
||||
controller.setString_forKey_(access["access_token"], "user_access_token");
|
||||
controller.setString_forKey_(access["mac_key"], "user_mac_key");
|
||||
controller.setString_forKey_(access["mac_algorithm"], "user_mac_algorithm");
|
||||
controller.setString_forKey_(access["token_type"], "user_token_type");
|
||||
|
||||
controller.loggedIn();
|
||||
}
|
||||
|
||||
var tentia_oauth;
|
|
@ -6,7 +6,7 @@ html, body {
|
|||
body {
|
||||
font-family: "Lucida Grande", Tahoma, sans-serif;
|
||||
font-size: 11px;
|
||||
background: #dedede url(Icon.png) bottom center no-repeat;
|
||||
background: #dedede url(../img/Icon.png) bottom center no-repeat;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -42,11 +42,11 @@ li:first-child {
|
|||
}
|
||||
|
||||
li:nth-child(odd), .error {
|
||||
background: url(odd-bg.png) repeat-x bottom #fafafa;
|
||||
background: url(../img/odd-bg.png) repeat-x bottom #fafafa;
|
||||
}
|
||||
|
||||
li:nth-child(even) {
|
||||
background: url(even-bg.png) repeat-x bottom #f2f2f2;
|
||||
background: url(../img/even-bg.png) repeat-x bottom #f2f2f2;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
|
@ -143,7 +143,7 @@ p {
|
|||
.retweeted span {
|
||||
height: 11px;
|
||||
width: 15px;
|
||||
background: url(sprite-icons.png) no-repeat -192px -1px;
|
||||
background: url(../img/sprite-icons.png) no-repeat -192px -1px;
|
||||
display: inline-block;
|
||||
margin: 0 3px 0 4px;
|
||||
}
|
||||
|
@ -224,7 +224,7 @@ li:first-child:hover .date {
|
|||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
background: url(sprite-icons.png) no-repeat -16px 0;
|
||||
background: url(../img/sprite-icons.png) no-repeat -16px 0;
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
// its different for app authentication and user authentication:
|
||||
// for apps its id: mac_key_id and secret: mac_key,
|
||||
// for users its id: access_token and secret:mac_key
|
||||
|
||||
function getURL(url, http_method, callback, data, auth_header) {
|
||||
$.ajax({
|
||||
beforeSend: function(xhr) {
|
||||
if (data) xhr.setRequestHeader("Content-Length", data.length);
|
||||
|
||||
if (auth_header) { // if is_set? auth_header
|
||||
xhr.setRequestHeader("Authorization", auth_header);
|
||||
} else {
|
||||
var user_access_token = controller.stringForKey_("user_access_token");
|
||||
if (auth_header !== false && user_access_token) {
|
||||
auth_header = makeAuthHeader(
|
||||
url,
|
||||
http_method,
|
||||
controller.stringForKey_("user_mac_key"),
|
||||
user_access_token
|
||||
)
|
||||
xhr.setRequestHeader("Authorization", auth_header);
|
||||
}
|
||||
}
|
||||
},
|
||||
url: url,
|
||||
accepts: "application/vnd.tent.v0+json",
|
||||
contentType: "application/vnd.tent.v0+json",
|
||||
type: http_method,
|
||||
complete: callback,
|
||||
data: data,
|
||||
processData: false,
|
||||
error: function(xhr, ajaxOptions, thrownError) {
|
||||
alert("getURL " + xhr.statusText + " " + http_method + " (" + url + "): '" + xhr.responseText + "'");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeAuthHeader(url, http_method, mac_key, mac_key_id) {
|
||||
|
||||
url = URI(url);
|
||||
var nonce = makeid(8);
|
||||
var time_stamp = parseInt((new Date).getTime() / 1000, 10);
|
||||
|
||||
var port = url.port();
|
||||
if (!port) {
|
||||
port = url.protocol() == "https" ? "443" : "80";
|
||||
}
|
||||
|
||||
var normalizedRequestString = ""
|
||||
+ time_stamp + '\n'
|
||||
+ nonce + '\n'
|
||||
+ http_method + '\n'
|
||||
+ url.path() + url.search() + url.hash() + '\n'
|
||||
+ url.hostname() + '\n'
|
||||
+ port + '\n'
|
||||
+ '\n' ;
|
||||
|
||||
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, mac_key);
|
||||
hmac.update(normalizedRequestString);
|
||||
var hash = hmac.finalize();
|
||||
var mac = hash.toString(CryptoJS.enc.Base64);
|
||||
|
||||
return 'MAC id="' + mac_key_id +
|
||||
'", ts="' + time_stamp +
|
||||
'", nonce="' + nonce +
|
||||
'", mac="' + mac + '"';
|
||||
}
|
||||
|
||||
function makeid(len) {
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < len; i++ )
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function findProfileURL(entity, callback) {
|
||||
$.ajax({
|
||||
url: entity,
|
||||
type: "HEAD",
|
||||
complete: function(resp) {
|
||||
if(resp) {
|
||||
var headers = resp.getAllResponseHeaders();
|
||||
var regex = /Link: <([^>]*)>; rel="https:\/\/tent.io\/rels\/profile"/; // FIXME: parse it!
|
||||
var ret = headers.match(regex);
|
||||
var profile_url = null;
|
||||
if(ret && ret.length > 1) {
|
||||
var profile_url = ret[1];
|
||||
if (profile_url == "/profile") {
|
||||
profile_url = entity + "/profile";
|
||||
}
|
||||
}
|
||||
|
||||
if (profile_url) {
|
||||
callback(profile_url);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(xhr, ajaxOptions, thrownError) {
|
||||
alert("findProfileURL " + xhr.statusText + " (" + entity + "): " + xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function mkApiRootPath(path) {
|
||||
var api_root = controller.stringForKey_("api_root");
|
||||
if((api_root.substring(api_root.length - 1, api_root.length) != "/") && (path.substring(0, 1) != "/")) {
|
||||
api_root += "/";
|
||||
} else if((api_root.substring(api_root.length - 1, api_root.length) == "/") && (path.substring(0, 1) == "/")) {
|
||||
api_root = api_root.substring(0, api_root.length -1);
|
||||
}
|
||||
return api_root + path;
|
||||
}
|
||||
|
||||
function debug(string) {
|
||||
if (typeof string == "Object") {
|
||||
string = JSON.stirngify(string);
|
||||
}
|
||||
alert("DEBUG: " + string);
|
||||
}
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 171 B After Width: | Height: | Size: 171 B |
Before Width: | Height: | Size: 166 B After Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 630 B |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
@ -3,15 +3,8 @@
|
|||
<head>
|
||||
<title>Tentia</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
<link rel="stylesheet" href="~/Library/Application%20Support/Style.css" type="text/css" />
|
||||
<script type="text/javascript" src="jQuery.js"></script>
|
||||
<script type="text/javascript" src="jQuery-Plugins.js"></script>
|
||||
<script type="text/javascript" src="hmac-sha256.js"></script>
|
||||
<script type="text/javascript" src="enc-base64-min.js"></script>
|
||||
<script type="text/javascript" src="URI.min.js"></script>
|
||||
<script type="text/javascript" src="hmac-helper.js"></script>
|
||||
<script type="text/javascript" src="Core.js"></script>
|
||||
<link rel="stylesheet" href="css/default.css" type="text/css" />
|
||||
<script data-main="scripts/main" src="scripts/lib/vendor/require-jquery.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Tentia</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<script type="text/javascript" src="jQuery.js"></script>
|
||||
<script type="text/javascript" src="jQuery-Plugins.js"></script>
|
||||
<script type="text/javascript" src="hmac-sha256.js"></script>
|
||||
<script type="text/javascript" src="enc-base64-min.js"></script>
|
||||
<script type="text/javascript" src="URI.min.js"></script>
|
||||
<script type="text/javascript" src="hmac-helper.js"></script>
|
||||
<script type="text/javascript" src="OauthImplementation.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
45
WebKit/scripts/controller/Mentions.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
define([
|
||||
"helper/HostApp",
|
||||
"controller/Timeline"
|
||||
],
|
||||
|
||||
function(HostApp, Timeline) {
|
||||
|
||||
|
||||
function Mentions() {
|
||||
|
||||
this.is_not_init = false;
|
||||
this.action = "mentions";
|
||||
this.unread_mentions = 0;
|
||||
|
||||
Timeline.call(this);
|
||||
}
|
||||
|
||||
Mentions.prototype = Object.create(Timeline.prototype);
|
||||
|
||||
Mentions.prototype.newStatus = function(status) {
|
||||
|
||||
Timeline.prototype.newStatus.call(this, status);
|
||||
|
||||
if(this.action == "mentions" && this.is_not_init) {
|
||||
this.unread_mentions += status.length;
|
||||
HostApp.unreadMentions(this.unread_mentions);
|
||||
}
|
||||
|
||||
this.is_not_init = true;
|
||||
|
||||
}
|
||||
|
||||
Mentions.prototype.getNewData = function(add_to_search) {
|
||||
add_to_search = add_to_search || {};
|
||||
|
||||
if (!add_to_search["mentioned_entity"]) {
|
||||
add_to_search["mentioned_entity"] = HostApp.stringForKey("entity");
|
||||
}
|
||||
|
||||
Timeline.prototype.getNewData.call(this, add_to_search);
|
||||
}
|
||||
|
||||
return Mentions;
|
||||
|
||||
});
|
134
WebKit/scripts/controller/Oauth.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
define([
|
||||
"helper/HostApp",
|
||||
"helper/Paths",
|
||||
"helper/Hmac"
|
||||
],
|
||||
|
||||
function(HostApp, Paths, Hmac) {
|
||||
|
||||
function Oauth() {
|
||||
this.app_info = {
|
||||
"id": null,
|
||||
"name": "Tentia",
|
||||
"description": "A small TentStatus client.",
|
||||
"url": "http://jabs.nu/Tentia/",
|
||||
"icon": "http://jabs.nu/Tentia/icon.png",
|
||||
"redirect_uris": [
|
||||
"tentia://oauthtoken"
|
||||
],
|
||||
"scopes": {
|
||||
"read_posts": "Uses posts to show them in a list",
|
||||
"write_posts": "Posts on users behalf"
|
||||
}
|
||||
};
|
||||
|
||||
this.register_data = null;
|
||||
this.profile = null;
|
||||
this.state = null;
|
||||
}
|
||||
|
||||
Oauth.prototype.authenticate = function() {
|
||||
this.entity = HostApp.stringForKey("entity");
|
||||
this.requestProfileURL(this.entity);
|
||||
}
|
||||
|
||||
Oauth.prototype.apiRoot = function() {
|
||||
return this.profile["https://tent.io/types/info/core/v0.1.0"]["servers"][0];
|
||||
}
|
||||
|
||||
Oauth.prototype.requestProfileURL = function (entity) {
|
||||
var those = this;
|
||||
Paths.findProfileURL(entity, function(profile_url) {
|
||||
those.register(profile_url);
|
||||
});
|
||||
}
|
||||
|
||||
Oauth.prototype.register = function (url) {
|
||||
var those = this;
|
||||
Paths.getURL(url, "GET", function(resp) {
|
||||
those.profile = JSON.parse(resp.responseText);
|
||||
HostApp.setStringForKey(those.apiRoot(), "api_root");
|
||||
var callback = function(resp) {
|
||||
var data = JSON.parse(resp.responseText);
|
||||
those.authRequest(data);
|
||||
}
|
||||
Paths.getURL(Paths.mkApiRootPath("/apps"), "POST", callback, JSON.stringify(those.app_info));
|
||||
});
|
||||
}
|
||||
|
||||
Oauth.prototype.authRequest = function(register_data) {
|
||||
// id
|
||||
// mac_key_id
|
||||
// mac_key
|
||||
// mac_algorithm
|
||||
this.register_data = register_data;
|
||||
|
||||
// Needed for later App Registration Modification
|
||||
HostApp.setStringForKey(register_data["mac_key"], "app_mac_key");
|
||||
HostApp.setStringForKey(register_data["mac_key_id"], "app_mac_key_id");
|
||||
HostApp.setStringForKey(register_data["id"], "app_id");
|
||||
HostApp.setStringForKey(register_data["mac_algorithm"], "app_mac_algorithm");
|
||||
|
||||
this.state = Hmac.makeid(19);
|
||||
var auth = "/oauth/authorize?client_id=" + register_data["id"]
|
||||
+ "&redirect_uri=" + escape(this.app_info["redirect_uris"][0])
|
||||
+ "&scope=" + Object.keys(this.app_info["scopes"]).join(",")
|
||||
+ "&state=" + this.state
|
||||
+ "&tent_post_types=" + escape("https://tent.io/types/posts/status/v0.1.0");
|
||||
|
||||
HostApp.openURL(this.apiRoot() + auth);
|
||||
}
|
||||
|
||||
Oauth.prototype.requestAccessToken = function(responseBody) {
|
||||
// /oauthtoken?code=51d0115b04d1ed94001dde751c5b360f&state=aQfH1VEohYsQr86qqyv
|
||||
var urlVars = Paths.getUrlVars(responseBody);
|
||||
if(this.state && this.state != "" && urlVars["state"] == this.state) {
|
||||
|
||||
var url = Paths.mkApiRootPath("/apps/") + this.register_data["id"] + "/authorizations";
|
||||
|
||||
var requestBody = JSON.stringify({
|
||||
'code' : urlVars["code"],
|
||||
'token_type' : "mac"
|
||||
});
|
||||
|
||||
var those = this;
|
||||
var http_method = "POST";
|
||||
var callback = function(resp) {
|
||||
those.requestAccessTokenTicketFinished(resp.responseText);
|
||||
};
|
||||
|
||||
var auth_header = Hmac.makeAuthHeader(
|
||||
url,
|
||||
http_method,
|
||||
HostApp.stringForKey("app_mac_key"),
|
||||
HostApp.stringForKey("app_mac_key_id")
|
||||
);
|
||||
|
||||
Paths.getURL(url, http_method, callback, requestBody, auth_header);
|
||||
|
||||
} else {
|
||||
alert("State is not the same: {" + this.state + "} vs {" + urlVars["state"] + "}")
|
||||
}
|
||||
|
||||
this.state = null; // reset the state
|
||||
}
|
||||
|
||||
Oauth.prototype.requestAccessTokenTicketFinished = function(responseBody) {
|
||||
|
||||
var access = JSON.parse(responseBody);
|
||||
|
||||
HostApp.setStringForKey(access["access_token"], "user_access_token");
|
||||
HostApp.setStringForKey(access["mac_key"], "user_mac_key");
|
||||
HostApp.setStringForKey(access["mac_algorithm"], "user_mac_algorithm");
|
||||
HostApp.setStringForKey(access["token_type"], "user_token_type");
|
||||
|
||||
HostApp.loggedIn();
|
||||
}
|
||||
|
||||
Oauth.prototype.logout = function() {
|
||||
|
||||
}
|
||||
|
||||
return Oauth;
|
||||
|
||||
});
|
106
WebKit/scripts/controller/Timeline.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
define([
|
||||
"helper/Core",
|
||||
"helper/Paths",
|
||||
"helper/HostApp",
|
||||
"lib/URI"
|
||||
],
|
||||
|
||||
function(Core, Paths, HostApp, URI) {
|
||||
|
||||
function Timeline() {
|
||||
|
||||
Core.call(this);
|
||||
|
||||
this.action = "timeline";
|
||||
|
||||
this.max_length = 20;
|
||||
this.timeout = 10 * 1000; // every 10 seconds
|
||||
this.since_id = null;
|
||||
this.since_id_entity = null;
|
||||
this.since_time = 0;
|
||||
|
||||
this.body = document.createElement("ol");
|
||||
this.body.className = this.action;
|
||||
document.body.appendChild(this.body);
|
||||
|
||||
var _this = this;
|
||||
this.reloadIntervall = setInterval(function() { _this.getNewData() }, this.timeout);
|
||||
|
||||
this.getNewData();
|
||||
}
|
||||
|
||||
Timeline.prototype = Object.create(Core.prototype);
|
||||
|
||||
|
||||
Timeline.prototype.newStatus = function(statuses) {
|
||||
|
||||
if(statuses != null && statuses.length > 0) {
|
||||
for(var i = statuses.length-1, c=0; i>=c; --i) {
|
||||
|
||||
var status = statuses[i];
|
||||
this.since_id = status.id;
|
||||
this.since_id_entity = status.entity;
|
||||
|
||||
if(this.body.childNodes.length > 0) {
|
||||
if(this.body.childNodes.length > this.max_length) {
|
||||
this.body.removeChild(this.body.lastChild);
|
||||
}
|
||||
this.body.insertBefore(this.getStatusDOMElement(status), this.body.firstChild);
|
||||
} else {
|
||||
this.body.appendChild(this.getStatusDOMElement(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timeline.prototype.getNewData = function(add_to_search) {
|
||||
|
||||
add_to_search = add_to_search || {};
|
||||
|
||||
var those = this;
|
||||
var url = URI(Paths.mkApiRootPath("/posts"));
|
||||
url.addSearch("post_types", "https://tent.io/types/post/status/v0.1.0");
|
||||
url.addSearch("limit", this.max_length);
|
||||
if(this.since_id) {
|
||||
url.addSearch("since_id", this.since_id);
|
||||
url.addSearch("since_id_entity", this.since_id_entity);
|
||||
}
|
||||
|
||||
for (key in add_to_search) {
|
||||
url.addSearch(key, add_to_search[key]);
|
||||
}
|
||||
|
||||
var http_method = "GET";
|
||||
var callback = function(resp) {
|
||||
|
||||
try {
|
||||
var json = JSON.parse(resp.responseText)
|
||||
} catch (e) {
|
||||
//alert(resp.responseText);
|
||||
alert(url + " JSON parse error");
|
||||
throw e;
|
||||
}
|
||||
|
||||
those.newStatus(json);
|
||||
}
|
||||
|
||||
var data = null;
|
||||
|
||||
if (HostApp.stringForKey("user_access_token")) {
|
||||
Paths.getURL(url.toString(), http_method, callback, data); // FIXME: error callback
|
||||
}
|
||||
}
|
||||
|
||||
Timeline.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity) {
|
||||
var _this = this;
|
||||
var callback = function(data) { _this.getNewData(); }
|
||||
Core.prototype.sendNewMessage.call(this, content, in_reply_to_status_id, in_reply_to_entity, callback);
|
||||
}
|
||||
|
||||
Timeline.prototype.foo = function(a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
return Timeline;
|
||||
|
||||
});
|
358
WebKit/scripts/helper/Core.js
Normal file
|
@ -0,0 +1,358 @@
|
|||
define([
|
||||
"jquery",
|
||||
"helper/Paths",
|
||||
"lib/URI",
|
||||
"lib/vendor/jquery.plugins"
|
||||
],
|
||||
|
||||
function(jQuery, Paths, URI) {
|
||||
|
||||
function Core() {
|
||||
|
||||
}
|
||||
|
||||
Core.prototype.getTemplate = function() {
|
||||
|
||||
if(this.template == "undefined") {
|
||||
return jQuery.extend(true, {}, this.template);
|
||||
}
|
||||
|
||||
var a = document.createElement("a");
|
||||
|
||||
var item = document.createElement("li");
|
||||
|
||||
var reply_to = a.cloneNode();
|
||||
reply_to.className = "reply_to"
|
||||
reply_to.innerText = " ";
|
||||
reply_to.href = "#";
|
||||
item.appendChild(reply_to);
|
||||
|
||||
var retweet = a.cloneNode();
|
||||
retweet.className = "retweet";
|
||||
retweet.innerText = " ";
|
||||
retweet.href = "#";
|
||||
// item.appendChild(retweet); // FIXME
|
||||
|
||||
|
||||
var image = document.createElement("img");
|
||||
image.className = "image";
|
||||
image.src = "img/default-avatar.png";
|
||||
image.onmousedown = function(e) { e.preventDefault(); };
|
||||
item.appendChild(image);
|
||||
|
||||
var image_username = a.cloneNode();
|
||||
image.appendChild(image_username);
|
||||
|
||||
var data = document.createElement("div");
|
||||
data.className = "data";
|
||||
item.appendChild(data);
|
||||
|
||||
var head = document.createElement("h1");
|
||||
data.appendChild(head);
|
||||
|
||||
var username = a.cloneNode();
|
||||
head.appendChild(username);
|
||||
|
||||
var in_reply = document.createElement("span");
|
||||
in_reply.className = "reply";
|
||||
head.appendChild(in_reply);
|
||||
|
||||
var space = document.createTextNode(" ");
|
||||
head.appendChild(space);
|
||||
|
||||
var geo = document.createElement("a");
|
||||
geo.style.display = "none";
|
||||
head.appendChild(geo);
|
||||
|
||||
var pin = document.createElement("img");
|
||||
pin.src = "img/pin.png";
|
||||
pin.alt = "Map link";
|
||||
geo.appendChild(pin);
|
||||
|
||||
var in_reply_text = document.createTextNode(" in reply to ");
|
||||
in_reply.appendChild(in_reply_text)
|
||||
|
||||
var in_reply_a = a.cloneNode();
|
||||
in_reply.appendChild(in_reply_a);
|
||||
|
||||
var message = document.createElement("p");
|
||||
message.className = "message";
|
||||
data.appendChild(message);
|
||||
|
||||
var images = document.createElement("p")
|
||||
images.className = "images";
|
||||
data.appendChild(images);
|
||||
|
||||
var date = message.cloneNode();
|
||||
date.className = "date";
|
||||
data.appendChild(date);
|
||||
|
||||
var ago = a.cloneNode();
|
||||
date.appendChild(ago);
|
||||
|
||||
var from = document.createTextNode(" from ");
|
||||
date.appendChild(from)
|
||||
|
||||
var source = document.createElement("a");
|
||||
source.className = "source";
|
||||
date.appendChild(source)
|
||||
|
||||
this.template = {
|
||||
item: item,
|
||||
reply_to: reply_to,
|
||||
retweet: retweet,
|
||||
image: image,
|
||||
username: username,
|
||||
in_reply: in_reply_a,
|
||||
message: message,
|
||||
ago: ago,
|
||||
source: source,
|
||||
geo: geo,
|
||||
images: images
|
||||
}
|
||||
|
||||
return jQuery.extend(true, {}, this.template);
|
||||
}
|
||||
|
||||
Core.prototype.getStatusDOMElement = function(status) {
|
||||
|
||||
var _this = this;
|
||||
|
||||
var template = this.getTemplate();
|
||||
|
||||
|
||||
template.reply_to.onclick = function() {
|
||||
var mentions = [];
|
||||
for (var i = 0; i < status.mentions.length; i++) {
|
||||
var mention = status.mentions[i];
|
||||
if(mention.entity != HostApp.stringForKey("entity"))
|
||||
mentions.push(mention);
|
||||
}
|
||||
this.replyTo(status.entity, status.id, mentions);
|
||||
return false;
|
||||
}
|
||||
|
||||
//template.retweet.onclick = function() { template.retweet.className = "hidden"; _this.retweet(status.id_str, template.item); return false; }
|
||||
|
||||
template.username.innerText = status.entity;
|
||||
template.username.href = status.entity; // FIXME open profile
|
||||
|
||||
Paths.findProfileURL(status.entity, function(profile_url) {
|
||||
if (profile_url) {
|
||||
Paths.getURL(profile_url, "GET", function(resp) {
|
||||
var profile = JSON.parse(resp.responseText);
|
||||
var basic = profile["https://tent.io/types/info/basic/v0.1.0"];
|
||||
|
||||
if (profile && basic) {
|
||||
if(basic.name) {
|
||||
template.username.title = template.username.innerText;
|
||||
template.username.innerText = basic.name;
|
||||
}
|
||||
if(basic.avatar_url) {
|
||||
template.image.onerror = function() { template.image.src = 'img/default-avatar.png' };
|
||||
template.image.src = basic.avatar_url;
|
||||
}
|
||||
}
|
||||
}, null, false); // do not send auth-headers
|
||||
}
|
||||
});
|
||||
|
||||
template.in_reply.parentNode.className = "hidden";
|
||||
|
||||
template.message.innerHTML = this.replaceUsernamesWithLinks(
|
||||
this.replaceURLWithHTMLLinks(status.content.text, status.entities, template.message)
|
||||
);
|
||||
|
||||
this.findMentions(template.message, status.mentions);
|
||||
|
||||
var time = document.createElement("abbr");
|
||||
time.innerText = this.ISODateString(new Date(status.published_at * 1000));
|
||||
time.title = time.innerText;
|
||||
time.className = "timeago";
|
||||
jQuery(time).timeago();
|
||||
template.ago.appendChild(time);
|
||||
|
||||
// {"type":"Point","coordinates":[57.10803113,12.25854746]}
|
||||
if (status.content && status.content.location && status.content.location.type == "Point") {
|
||||
template.geo.href = "http://maps.google.com/maps?q=" + status.content.location.coordinates[0] + "," + status.content.location.coordinates[1];
|
||||
template.geo.style.display = "";
|
||||
}
|
||||
|
||||
template.source.href = status.app.url;
|
||||
template.source.innerHTML = status.app.name;
|
||||
template.source.title = status.app.url;
|
||||
|
||||
return template.item;
|
||||
}
|
||||
|
||||
Core.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity, callback) {
|
||||
|
||||
var url = URI(Paths.mkApiRootPath("/posts"));
|
||||
|
||||
var http_method = "POST";
|
||||
|
||||
var data = {
|
||||
"type": "https://tent.io/types/post/status/v0.1.0",
|
||||
"published_at": (new Date().getTime() / 1000),
|
||||
"permissions": {
|
||||
"public": true
|
||||
},
|
||||
"content": {
|
||||
"text": content,
|
||||
},
|
||||
};
|
||||
|
||||
var mentions = this.parseMentions(content, in_reply_to_status_id, in_reply_to_entity);
|
||||
if (mentions.length > 0) {
|
||||
data["mentions"] = mentions;
|
||||
}
|
||||
|
||||
Paths.getURL(url.toString(), http_method, callback, JSON.stringify(data)); // FIXME: error callback
|
||||
}
|
||||
|
||||
|
||||
Core.prototype.logout = function() {
|
||||
this.body.innerHTML = "";
|
||||
}
|
||||
|
||||
|
||||
// Helper functions
|
||||
|
||||
Core.prototype.replaceShortened = function(url, message_node) {
|
||||
var api = "http://api.bitly.com";
|
||||
if(url.startsWith("http://j.mp/")) {
|
||||
api = "http://api.j.mp";
|
||||
}
|
||||
|
||||
var api_url = api + "/v3/expand?format=json&apiKey=R_4fc2a1aa461d076556016390fa6400f6&login=twittia&shortUrl=" + url; // FIXME: new api key
|
||||
|
||||
jQuery.ajax({
|
||||
url: api_url,
|
||||
success: function(data) {
|
||||
var new_url = data.data.expand[0].long_url;
|
||||
if (new_url) {
|
||||
var regex = new RegExp(url, "g");
|
||||
message_node.innerHTML = message_node.innerHTML.replace(regex, new_url);
|
||||
}
|
||||
},
|
||||
error:function (xhr, ajaxOptions, thrownError) {
|
||||
alert(xhr.status);
|
||||
alert(thrownError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Core.prototype.findMentions = function(node, mentions) {
|
||||
var text = node.innerHTML;
|
||||
var mentions_in_text = [];
|
||||
var res = text.match(/(\^\S+)/ig);
|
||||
|
||||
if (res) {
|
||||
for (var i = 0; i < res.length; i++) {
|
||||
var name = res[i];
|
||||
var e = name.substring(1);
|
||||
if (e.substring(0,7) != "http://" && e.substring(0,8) != "https://") {
|
||||
e = "https://" + e;
|
||||
}
|
||||
for (var j = 0; j < mentions.length; j++) {
|
||||
var m = mentions[j];
|
||||
if(m.entity.startsWith(e)) {
|
||||
mentions_in_text.push({
|
||||
entity: m.entity,
|
||||
text: name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < mentions_in_text.length; i++) {
|
||||
var mention = mentions_in_text[i];
|
||||
|
||||
(function(mention) { // need this closure
|
||||
Paths.findProfileURL(mention.entity, function(profile_url) {
|
||||
if (profile_url) {
|
||||
Paths.getURL(profile_url, "GET", function(resp) {
|
||||
var profile = JSON.parse(resp.responseText);
|
||||
var basic = profile["https://tent.io/types/info/basic/v0.1.0"];
|
||||
|
||||
if (profile && basic) {
|
||||
if(basic.name) {
|
||||
var new_text = node.innerHTML.replace(
|
||||
mention.text,
|
||||
"<strong class='name' title='" + mention.entity + "'" + ">"
|
||||
+ basic.name
|
||||
+ "</strong>"
|
||||
);
|
||||
node.innerHTML = new_text;
|
||||
}
|
||||
}
|
||||
}, null, false); // do not send auth-headers
|
||||
}
|
||||
});
|
||||
})(mention);
|
||||
}
|
||||
}
|
||||
|
||||
Core.prototype.parseMentions = function(text, post_id, entity) {
|
||||
var mentions = [];
|
||||
|
||||
if (post_id && entity) {
|
||||
mentions.push({
|
||||
post: post_id,
|
||||
entity: entity
|
||||
})
|
||||
}
|
||||
|
||||
var res = text.match(/(\^\S+)/ig);
|
||||
|
||||
if (res) {
|
||||
for (var i = 0; i < res.length; i++) {
|
||||
var e = res[i].substring(1);
|
||||
if (e.substring(0,7) != "http://" && e.substring(0,8) != "https://") {
|
||||
e = "https://" + e;
|
||||
}
|
||||
if (e != entity) {
|
||||
mentions.push({entity:e});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mentions;
|
||||
}
|
||||
|
||||
Core.prototype.ISODateString = function(d){
|
||||
function pad(n){return n<10 ? '0'+n : n}
|
||||
return d.getUTCFullYear()+'-'
|
||||
+ pad(d.getUTCMonth()+1)+'-'
|
||||
+ pad(d.getUTCDate())+'T'
|
||||
+ pad(d.getUTCHours())+':'
|
||||
+ pad(d.getUTCMinutes())+':'
|
||||
+ pad(d.getUTCSeconds())+'Z'
|
||||
}
|
||||
|
||||
Core.prototype.replaceURLWithHTMLLinks = function(text, entities, message_node) {
|
||||
var exp = /(([^\^]https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_()|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||
return text.replace(exp, "<a href='$1'>$1</a>");
|
||||
}
|
||||
|
||||
Core.prototype.replaceUsernamesWithLinks = function(text, mentions) {
|
||||
return text; // FIXME!
|
||||
var username = /(^|\s)(\^)(\w+)/ig;
|
||||
var hash = /(^|\s)(#)(\w+)/ig;
|
||||
text = text.replace(username, "$1$2<a href='tentia://profile/$3'>$3</a>");
|
||||
return text.replace(hash, "$1$2<a href='http://search.twitter.com/search?q=%23$3'>$3</a>");
|
||||
}
|
||||
|
||||
Core.prototype.replyTo = function(entity, status_id, mentions) {
|
||||
var string = "^" + entity.replace("https://", "") + " ";
|
||||
for (var i = 0; i < mentions.length; i++) {
|
||||
var e = mentions[i].entity.replace("https://", "");
|
||||
if(string.indexOf(e) == -1) string += "^" + e + " ";
|
||||
}
|
||||
controller.openNewMessageWindowInReplyTo_statusId_withString_(entity, status_id, string);
|
||||
}
|
||||
|
||||
return Core;
|
||||
|
||||
});
|
56
WebKit/scripts/helper/Hmac.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
define([
|
||||
"lib/URI",
|
||||
"lib/CryptoJS"
|
||||
],
|
||||
|
||||
function(URI, CryptoJS) {
|
||||
|
||||
var Hmac = {};
|
||||
|
||||
Hmac.makeAuthHeader = function(url, http_method, mac_key, mac_key_id) {
|
||||
|
||||
url = URI(url);
|
||||
var nonce = Hmac.makeid(8);
|
||||
var time_stamp = parseInt((new Date).getTime() / 1000, 10);
|
||||
|
||||
var port = url.port();
|
||||
if (!port) {
|
||||
port = url.protocol() == "https" ? "443" : "80";
|
||||
}
|
||||
|
||||
var normalizedRequestString = ""
|
||||
+ time_stamp + '\n'
|
||||
+ nonce + '\n'
|
||||
+ http_method + '\n'
|
||||
+ url.path() + url.search() + url.hash() + '\n'
|
||||
+ url.hostname() + '\n'
|
||||
+ port + '\n'
|
||||
+ '\n' ;
|
||||
|
||||
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, mac_key);
|
||||
hmac.update(normalizedRequestString);
|
||||
var hash = hmac.finalize();
|
||||
var mac = hash.toString(CryptoJS.enc.Base64);
|
||||
|
||||
return 'MAC id="' + mac_key_id +
|
||||
'", ts="' + time_stamp +
|
||||
'", nonce="' + nonce +
|
||||
'", mac="' + mac + '"';
|
||||
}
|
||||
|
||||
Hmac.makeid = function(len) {
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < len; i++ )
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
return Hmac;
|
||||
});
|
||||
|
||||
// its different for app authentication and user authentication:
|
||||
// for apps its id: mac_key_id and secret: mac_key,
|
||||
// for users its id: access_token and secret:mac_key
|
51
WebKit/scripts/helper/HostApp.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
define(function() {
|
||||
|
||||
var HostApp = {};
|
||||
|
||||
HostApp.setStringForKey = function(string, key) {
|
||||
if (OS_TYPE == "mac") {
|
||||
controller.setString_forKey_(string, key);
|
||||
} else {
|
||||
controller.setStringForKey(string, key);
|
||||
}
|
||||
}
|
||||
|
||||
HostApp.stringForKey = function(key) {
|
||||
if (OS_TYPE == "mac") {
|
||||
return controller.stringForKey_(key);
|
||||
} else {
|
||||
return controller.stringForKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
HostApp.openURL = function(url) {
|
||||
if (OS_TYPE == "mac") {
|
||||
controller.openURL_(url);
|
||||
} else {
|
||||
controller.openURL(URL);
|
||||
}
|
||||
}
|
||||
|
||||
HostApp.loggedIn = function() {
|
||||
controller.loggedIn();
|
||||
}
|
||||
|
||||
HostApp.logout = function() {
|
||||
if (OS_TYPE == "mac") {
|
||||
controller.logout_(self);
|
||||
} else {
|
||||
controller.logout(self);
|
||||
}
|
||||
}
|
||||
|
||||
HostApp.unreadMentions = function(i) {
|
||||
if (OS_TYPE == "mac") {
|
||||
controller.unreadMentions_(i);
|
||||
} else {
|
||||
controller.unreadMentions(i);
|
||||
}
|
||||
}
|
||||
|
||||
return HostApp;
|
||||
|
||||
});
|
95
WebKit/scripts/helper/Paths.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
define([
|
||||
"jquery",
|
||||
"helper/HostApp",
|
||||
"helper/Hmac"
|
||||
],
|
||||
|
||||
function(jQuery, HostApp, Hmac) {
|
||||
var Paths = {};
|
||||
|
||||
Paths.getUrlVars = function(url) {
|
||||
var vars = [], hash;
|
||||
if(url.indexOf("#") > -1) url = url.slice(0, url.indexOf("#"));
|
||||
var hashes = url.slice(url.indexOf('?') + 1).split('&');
|
||||
for(var i = 0; i < hashes.length; i++)
|
||||
{
|
||||
hash = hashes[i].split('=');
|
||||
vars.push(hash[0]);
|
||||
vars[hash[0]] = hash[1];
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
Paths.getURL = function(url, http_method, callback, data, auth_header) {
|
||||
jQuery.ajax({
|
||||
beforeSend: function(xhr) {
|
||||
if (data) xhr.setRequestHeader("Content-Length", data.length);
|
||||
|
||||
if (auth_header) { // if is_set? auth_header
|
||||
xhr.setRequestHeader("Authorization", auth_header);
|
||||
} else {
|
||||
var user_access_token = HostApp.stringForKey("user_access_token");
|
||||
if (auth_header !== false && user_access_token) {
|
||||
auth_header = Hmac.makeAuthHeader(
|
||||
url,
|
||||
http_method,
|
||||
HostApp.stringForKey("user_mac_key"),
|
||||
user_access_token
|
||||
)
|
||||
xhr.setRequestHeader("Authorization", auth_header);
|
||||
}
|
||||
}
|
||||
},
|
||||
url: url,
|
||||
accepts: "application/vnd.tent.v0+json",
|
||||
contentType: "application/vnd.tent.v0+json",
|
||||
type: http_method,
|
||||
complete: callback,
|
||||
data: data,
|
||||
processData: false,
|
||||
error: function(xhr, ajaxOptions, thrownError) {
|
||||
alert("getURL " + xhr.statusText + " " + http_method + " (" + url + "): '" + xhr.responseText + "'");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Paths.findProfileURL = function(entity, callback) {
|
||||
jQuery.ajax({
|
||||
url: entity,
|
||||
type: "HEAD",
|
||||
complete: function(resp) {
|
||||
if(resp) {
|
||||
var headers = resp.getAllResponseHeaders();
|
||||
var regex = /Link: <([^>]*)>; rel="https:\/\/tent.io\/rels\/profile"/; // FIXME: parse it!
|
||||
var ret = headers.match(regex);
|
||||
var profile_url = null;
|
||||
if(ret && ret.length > 1) {
|
||||
var profile_url = ret[1];
|
||||
if (profile_url == "/profile") {
|
||||
profile_url = entity + "/profile";
|
||||
}
|
||||
}
|
||||
|
||||
if (profile_url) {
|
||||
callback(profile_url);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(xhr, ajaxOptions, thrownError) {
|
||||
alert("findProfileURL " + xhr.statusText + " (" + entity + "): " + xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Paths.mkApiRootPath = function(path) {
|
||||
var api_root = HostApp.stringForKey("api_root");
|
||||
if((api_root.substring(api_root.length - 1, api_root.length) != "/") && (path.substring(0, 1) != "/")) {
|
||||
api_root += "/";
|
||||
} else if((api_root.substring(api_root.length - 1, api_root.length) == "/") && (path.substring(0, 1) == "/")) {
|
||||
api_root = api_root.substring(0, api_root.length -1);
|
||||
}
|
||||
return api_root + path;
|
||||
}
|
||||
|
||||
return Paths;
|
||||
});
|
BIN
WebKit/scripts/lib/.DS_Store
vendored
Normal file
7
WebKit/scripts/lib/CryptoJS.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
define([
|
||||
"lib/vendor/hmac-sha256"
|
||||
],
|
||||
|
||||
function() {
|
||||
return CryptoJS;
|
||||
});
|
7
WebKit/scripts/lib/URI.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
define([
|
||||
"lib/vendor/URI.min"
|
||||
],
|
||||
|
||||
function() {
|
||||
return URI;
|
||||
})
|
BIN
WebKit/scripts/lib/vendor/.DS_Store
vendored
Normal file
|
@ -15,3 +15,15 @@ n;n++){if(16>n)m[n]=e[f+n]|0;else{var i=m[n-15],p=m[n-2];m[n]=((i<<25|i>>>7)^(i<
|
|||
g=8*e.sigBytes;f[g>>>5]|=128<<24-g%32;f[(g+64>>>9<<4)+15]=b;e.sigBytes=4*f.length;this._process()}});i.SHA256=e._createHelper(l);i.HmacSHA256=e._createHmacHelper(l)})(Math);
|
||||
(function(){var h=CryptoJS,i=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(e,f){e=this._hasher=e.create();"string"==typeof f&&(f=i.parse(f));var h=e.blockSize,k=4*h;f.sigBytes>k&&(f=e.finalize(f));for(var o=this._oKey=f.clone(),m=this._iKey=f.clone(),q=o.words,r=m.words,b=0;b<h;b++)q[b]^=1549556828,r[b]^=909522486;o.sigBytes=m.sigBytes=k;this.reset()},reset:function(){var e=this._hasher;e.reset();e.update(this._iKey)},update:function(e){this._hasher.update(e);return this},finalize:function(e){var f=
|
||||
this._hasher,e=f.finalize(e);f.reset();return f.finalize(this._oKey.clone().concat(e))}})})();
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
CryptoJS v3.0.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2012 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(function(){var h=CryptoJS,i=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();for(var b=[],a=0;a<f;a+=3)for(var d=(e[a>>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g<f;g++)b.push(c.charAt(d>>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var b=b.replace(/\s/g,""),e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));
|
||||
for(var c=[],a=0,d=0;d<e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return i.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
|
11421
WebKit/scripts/lib/vendor/require-jquery.js
vendored
Normal file
1981
WebKit/scripts/lib/vendor/require.js
vendored
Normal file
68
WebKit/scripts/main.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
var tentia_instance;
|
||||
var tentia_cache = {};
|
||||
var OS_TYPE = "mac";
|
||||
|
||||
requirejs.config({
|
||||
baseUrl: 'scripts'
|
||||
});
|
||||
|
||||
function start(view) {
|
||||
|
||||
if (view == "oauth") {
|
||||
|
||||
require(["controller/Oauth"], function(Oauth) {
|
||||
|
||||
tentia_instance = new Oauth();
|
||||
tentia_instance.authenticate();
|
||||
|
||||
});
|
||||
|
||||
} else if (view == "timeline") {
|
||||
|
||||
require(["controller/Timeline"], function(Timeline) {
|
||||
|
||||
tentia_instance = new Timeline();
|
||||
|
||||
});
|
||||
|
||||
} else if (view == "mentions") {
|
||||
|
||||
require(["controller/Mentions"], function(Mentions) {
|
||||
|
||||
tentia_instance = new Mentions();
|
||||
|
||||
});
|
||||
|
||||
} else if (view == "profile") {
|
||||
|
||||
} else if (view == "follow") {
|
||||
|
||||
} else if (view == "conversation") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String.prototype.startsWith = function(prefix) {
|
||||
return this.indexOf(prefix) === 0;
|
||||
}
|
||||
|
||||
String.prototype.endsWith = function(suffix) {
|
||||
return this.match(suffix+"$") == suffix;
|
||||
};
|
||||
|
||||
function loadPlugin(url) {
|
||||
var plugin = document.createElement("script");
|
||||
plugin.type = "text/javascript";
|
||||
plugin.src = url;
|
||||
document.getElementsByTagName("head")[0].appendChild(plugin);
|
||||
}
|
||||
|
||||
function debug(string) {
|
||||
if (typeof string != "string") {
|
||||
string = JSON.stringify(string);
|
||||
}
|
||||
alert("DEBUG: " + string);
|
||||
}
|
||||
|
||||
setTimeout(HostAppGo, 1000);
|