diff --git a/Mac/Controller.h b/Mac/Controller.h index 8c4f381..4a91b57 100644 --- a/Mac/Controller.h +++ b/Mac/Controller.h @@ -19,6 +19,8 @@ IBOutlet NSWindow *timelineViewWindow; IBOutlet WebView *mentionsView; IBOutlet NSWindow *mentionsViewWindow; + IBOutlet WebView *conversationView; + IBOutlet NSWindow *conversationViewWindow; NSWindow *loginViewWindow; NSProgressIndicator *loginActivityIndicator; IBOutlet NSMenuItem *globalHotkeyMenuItem; @@ -32,6 +34,8 @@ @property (retain, nonatomic) IBOutlet NSWindow *timelineViewWindow; @property (retain, nonatomic) IBOutlet WebView *mentionsView; @property (retain, nonatomic) IBOutlet NSWindow *mentionsViewWindow; +@property (retain, nonatomic) IBOutlet WebView *conversationView; +@property (retain, nonatomic) IBOutlet NSWindow *conversationViewWindow; @property (assign) IBOutlet NSWindow *loginViewWindow; @property (assign) IBOutlet NSProgressIndicator *loginActivityIndicator; @property (retain, nonatomic) IBOutlet NSMenuItem *globalHotkeyMenuItem; @@ -58,6 +62,8 @@ - (IBAction)login:(id)sender; - (IBAction)logout:(id)sender; +- (IBAction)showConversationForPostId:(NSString *)postId andEntity:(NSString *)entity; + OSStatus handler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData); diff --git a/Mac/Controller.m b/Mac/Controller.m index 7c07b4a..01766d3 100644 --- a/Mac/Controller.m +++ b/Mac/Controller.m @@ -15,7 +15,8 @@ @synthesize loginViewWindow; @synthesize loginActivityIndicator; -@synthesize timelineView, timelineViewWindow, mentionsView, mentionsViewWindow, globalHotkeyMenuItem, viewDelegate; +@synthesize timelineView, timelineViewWindow, mentionsView, mentionsViewWindow, conversationView, conversationViewWindow; +@synthesize globalHotkeyMenuItem, viewDelegate; @synthesize logoLayer; @synthesize oauthView, accessToken; @@ -53,6 +54,10 @@ accessToken = [[AccessToken alloc] init]; //[accessToken setString:nil forKey:@"user_access_token"]; + if (![accessToken stringForKey:@"version-0.2.0-new-login"]) { + [self logout:self]; + [accessToken setString:@"yes" forKey:@"version-0.2.0-new-login"]; + } if (![accessToken stringForKey:@"user_access_token"]) { [timelineViewWindow performClose:self]; @@ -103,6 +108,14 @@ [mentionsView setPolicyDelegate:viewDelegate]; [mentionsView setUIDelegate:viewDelegate]; [[mentionsView windowScriptObject] setValue:self forKey:@"controller"]; + + + viewDelegate.conversationView = conversationView; + [[conversationView mainFrame] loadHTMLString:index_string baseURL:url]; + [conversationView setFrameLoadDelegate:viewDelegate]; + [conversationView setPolicyDelegate:viewDelegate]; + [conversationView setUIDelegate:viewDelegate]; + [[conversationView windowScriptObject] setValue:self forKey:@"controller"]; // FIXME: show timelineView after authentification } @@ -305,6 +318,12 @@ [mentionsView stringByEvaluatingJavaScriptFromString:@"tentia_instance.getNewData(true)"]; } +- (IBAction)showConversationForPostId:(NSString *)postId andEntity:(NSString *)entity +{ + NSString *js = [NSString stringWithFormat:@"tentia_instance.showStatus('%@', '%@');", postId, entity]; + [conversationView stringByEvaluatingJavaScriptFromString:js]; + [conversationViewWindow makeKeyAndOrderFront:self]; +} /* CARBON */ diff --git a/Mac/English.lproj/MainMenu.xib b/Mac/English.lproj/MainMenu.xib index 930c402..23ae427 100644 --- a/Mac/English.lproj/MainMenu.xib +++ b/Mac/English.lproj/MainMenu.xib @@ -3,7 +3,7 @@ 1080 12C60 - 2843 + 2844 1187.34 625.00 @@ -15,7 +15,7 @@ YES - 2843 + 2844 1810 @@ -920,7 +920,7 @@ - + 12 YES @@ -935,6 +935,66 @@ {376, 581} + + + {{0, 0}, {2560, 1418}} + {10000000000000, 10000000000000} + mentions + YES + + + 15 + 2 + {{1292, 328}, {376, 581}} + 1685586944 + Conversation + NSWindow + + + + + 256 + + YES + + + 274 + + YES + + YES + Apple HTML pasteboard type + Apple PDF pasteboard type + Apple PICT pasteboard type + Apple URL pasteboard type + Apple Web Archive pasteboard type + NSColor pasteboard type + NSFilenamesPboardType + NSStringPboardType + NeXT RTFD pasteboard type + NeXT Rich Text Format v1.0 pasteboard type + NeXT TIFF v4.0 pasteboard type + WebURLsWithTitlesPboardType + public.png + public.url + public.url-name + + + {376, 581} + + + + + + + NO + YES + + + {376, 581} + + + {{0, 0}, {2560, 1418}} {10000000000000, 10000000000000} @@ -1561,6 +1621,22 @@ 622 + + + conversationView + + + + 632 + + + + conversationViewWindow + + + + 633 + makeKeyAndOrderFront: @@ -1617,6 +1693,14 @@ 619 + + + delegate + + + + 631 + @@ -2366,6 +2450,30 @@ + + 628 + + + YES + + + + Mentions + + + 629 + + + YES + + + + + + 630 + + + @@ -2477,6 +2585,11 @@ 610.IBPluginDependency 613.IBPluginDependency 620.IBPluginDependency + 628.IBPluginDependency + 628.IBWindowTemplateEditedContentRect + 628.NSWindowTemplate.visibleAtLaunch + 629.IBPluginDependency + 630.IBPluginDependency 72.IBPluginDependency 73.IBPluginDependency 79.IBPluginDependency @@ -2593,6 +2706,11 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + {{344, 175}, {376, 581}} + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.WebKitIBPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -2613,7 +2731,7 @@ - 627 + 633 @@ -2627,11 +2745,15 @@ YES login: logout: + openNewMessageWindow: + sendTweet: YES id id + id + id @@ -2640,6 +2762,8 @@ YES login: logout: + openNewMessageWindow: + sendTweet: YES @@ -2651,12 +2775,22 @@ logout: id + + openNewMessageWindow: + id + + + sendTweet: + id + YES YES + conversationView + conversationViewWindow globalHotkeyMenuItem loginActivityIndicator loginViewWindow @@ -2669,6 +2803,8 @@ YES + WebView + NSWindow NSMenuItem NSProgressIndicator NSWindow @@ -2684,6 +2820,8 @@ YES YES + conversationView + conversationViewWindow globalHotkeyMenuItem loginActivityIndicator loginViewWindow @@ -2696,6 +2834,14 @@ YES + + conversationView + WebView + + + conversationViewWindow + NSWindow + globalHotkeyMenuItem NSMenuItem diff --git a/Mac/ViewDelegate.h b/Mac/ViewDelegate.h index 8fc4f46..5001c65 100644 --- a/Mac/ViewDelegate.h +++ b/Mac/ViewDelegate.h @@ -13,11 +13,13 @@ @interface ViewDelegate : NSObject { WebView *timelineView; WebView *mentionsView; + WebView *conversationView; WebView *oauthView; } @property (nonatomic, assign) WebView *timelineView; @property (nonatomic, assign) WebView *mentionsView; +@property (nonatomic, assign) WebView *conversationView; @property (nonatomic, assign) WebView *oauthView; @end diff --git a/Mac/ViewDelegate.m b/Mac/ViewDelegate.m index 6ec5761..2d3347f 100644 --- a/Mac/ViewDelegate.m +++ b/Mac/ViewDelegate.m @@ -11,7 +11,7 @@ @implementation ViewDelegate -@synthesize timelineView, mentionsView, oauthView; +@synthesize timelineView, mentionsView, conversationView, oauthView; - (void)webView:(WebView *)sender addMessageToConsole:(NSDictionary *)message;{ @@ -19,6 +19,7 @@ NSString *viewName = @"TimelineView"; if (sender == mentionsView) viewName = @"MentionsView"; + if (sender == conversationView) viewName = @"ConversationView"; if (sender == oauthView) viewName = @"OauthView"; NSLog(@"js<%@>: %@:%@: %@", @@ -32,6 +33,7 @@ - (void)webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame { NSString *viewName = @"TimelineView"; if (sender == mentionsView) viewName = @"MentionsView"; + if (sender == conversationView) viewName = @"ConversationView"; if (sender == oauthView) viewName = @"OauthView"; NSLog(@"jsa<%@>: %@", viewName, message); @@ -47,7 +49,11 @@ if (sender == oauthView) { [oauthView stringByEvaluatingJavaScriptFromString:@"function HostAppGo() { start('oauth') }"]; - + + } else if(sender == conversationView) { + + [conversationView stringByEvaluatingJavaScriptFromString:@"function HostAppGo() { start('conversation') }"]; + } else { NSString *action = @"timeline"; diff --git a/WebKit/css/default.css b/WebKit/css/default.css index a5cb424..1b3b367 100644 --- a/WebKit/css/default.css +++ b/WebKit/css/default.css @@ -62,6 +62,10 @@ li:after { height: 0; } +.highlight { + border-right: 5px solid #f17779; +} + h1 { font-size: 1em; font-weight: bold; diff --git a/WebKit/scripts/controller/Conversation.js b/WebKit/scripts/controller/Conversation.js index e69de29..7441e9d 100644 --- a/WebKit/scripts/controller/Conversation.js +++ b/WebKit/scripts/controller/Conversation.js @@ -0,0 +1,126 @@ +define([ + "helper/HostApp", + "helper/Core", + "helper/Paths", + "lib/URI" +], + +function(HostApp, Core, Paths, URI, Followings) { + + + function Conversation() { + + Core.call(this); + + this.action = "conversation"; + + this.body = document.createElement("ol"); + this.body.className = this.action; + document.body.appendChild(this.body); + } + + Conversation.prototype = Object.create(Core.prototype); + + + Conversation.addStatus = function(status) { + + this.body.appendChild(this.getStatusDOMElement(status)); + } + + + Conversation.prototype.showStatus = function(id, entity) { + + this.body.innerHTML = ""; + this.append(id, entity); + } + + Conversation.prototype.append = function(id, entity, node, add_after) { + + var _this = this; + + var callback = function(resp) { + + var status = JSON.parse(resp.responseText); + + var dom_element = _this.getStatusDOMElement(status); + + if (node) { + + node.parentNode.insertBefore(dom_element, node); + + } else { + dom_element.className = "highlight"; + _this.body.appendChild(dom_element); + + _this.appendMentioned(id, entity, dom_element); + } + + + if (status.mentions && status.mentions.length > 0 && status.mentions[0].post) { + _this.append(status.mentions[0].post, status.mentions[0].entity, dom_element); + } + } + + function getRemoteStatus(profile) { + var server = profile["https://tent.io/types/info/core/v0.1.0"].servers[0]; + Paths.getURL(URI(server + "/posts/" + id).toString(), "GET", callback, null, false); + } + + + if (entity == HostApp.stringForKey("entity")) { + + var url = URI(Paths.mkApiRootPath("/posts/" + id)); + Paths.getURL(url.toString(), "GET", callback, null); + + } else if(this.followings.followings[entity]) { + + getRemoteStatus(this.followings.followings[entity].profile); + + } else { + + Paths.findProfileURL(entity, function(profile_url) { + + if (profile_url) { + + Paths.getURL(profile_url, "GET", function(resp) { + + getRemoteStatus(JSON.parse(resp.responseText)); + + }, null, false); // do not send auth-headers + } + }); + } + } + + Conversation.prototype.appendMentioned = function(id, entity, node) { + + var url = URI(Paths.mkApiRootPath("/posts")); + url.addSearch("mentioned_post", id); + url.addSearch("post_types", "https%3A%2F%2Ftent.io%2Ftypes%2Fpost%2Fstatus%2Fv0.1.0"); + + var _this = this; + var callback = function(resp) { + + var statuses = JSON.parse(resp.responseText); + + for (var i = 0; i < statuses.length; i++) { + + var status = statuses[i]; + var dom_element = _this.getStatusDOMElement(status); + _this.body.appendChild(dom_element); + + _this.appendMentioned(status.id, status.entity, dom_element); + } + } + + Paths.getURL(url.toString(), "GET", callback); + + } + + // /posts?limit=10&mentioned_post=gnqqyt&post_types=https%3A%2F%2Ftent.io%2Ftypes%2Fpost%2Fstatus%2Fv0.1.0,https%3A%2F%2Ftent.io%2Ftypes%2Fpost%2Frepost%2Fv0.1.0 HTTP/1.1" 200 - 0.0582 + + + + return Conversation; + +}); \ No newline at end of file diff --git a/WebKit/scripts/controller/Oauth.js b/WebKit/scripts/controller/Oauth.js index 983eaa4..9f29a6c 100644 --- a/WebKit/scripts/controller/Oauth.js +++ b/WebKit/scripts/controller/Oauth.js @@ -18,7 +18,13 @@ function(HostApp, Paths, Hmac) { ], "scopes": { "read_posts": "Uses posts to show them in a list", - "write_posts": "Posts on users behalf" + "write_posts": "Posts on users behalf", + "read_profile": "Displays your own profile", + "write_profile": "Updating profile and mentions pointer", + "read_followers": "Display a list of people who follow you", + "write_followers": "Be able to block people who follow you", + "read_followings": "Display following list and their older posts in conversations", + "write_followings": "Follow ne entities" } }; @@ -74,7 +80,7 @@ function(HostApp, Paths, Hmac) { + "&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"); + + "&tent_post_types=all"; HostApp.openURL(this.apiRoot() + auth); } diff --git a/WebKit/scripts/helper/Core.js b/WebKit/scripts/helper/Core.js index df5737c..b94b5f0 100644 --- a/WebKit/scripts/helper/Core.js +++ b/WebKit/scripts/helper/Core.js @@ -3,13 +3,16 @@ define([ "helper/Paths", "lib/URI", "helper/HostApp", + "helper/Followings", "lib/vendor/jquery.plugins" ], -function(jQuery, Paths, URI, HostApp) { +function(jQuery, Paths, URI, HostApp, Followings) { function Core() { + this.followings = new Followings(); + } Core.prototype.getTemplate = function() { @@ -121,7 +124,6 @@ function(jQuery, Paths, URI, HostApp) { var template = this.getTemplate(); - template.reply_to.onclick = function() { var mentions = []; @@ -140,25 +142,39 @@ function(jQuery, Paths, URI, HostApp) { 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"]; + var profile = function(profile) { - 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 + 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; + } } - }); + + } + + if (this.followings.followings[status.entity]) { + + profile(this.followings.followings[status.entity].profile); + + } else { + + Paths.findProfileURL(status.entity, function(profile_url) { + if (profile_url) { + Paths.getURL(profile_url, "GET", function(resp) { + var p = JSON.parse(resp.responseText); + profile(p) + }, null, false); // do not send auth-headers + } + }); + } + template.in_reply.parentNode.className = "hidden"; @@ -174,6 +190,12 @@ function(jQuery, Paths, URI, HostApp) { time.className = "timeago"; jQuery(time).timeago(); template.ago.appendChild(time); + + template.ago.href = "#" + template.ago.onclick = function() { + HostApp.showConversation(status.id, status.entity); + return false; + } // {"type":"Point","coordinates":[57.10803113,12.25854746]} if (status.content && status.content.location && status.content.location.type == "Point") { @@ -272,30 +294,44 @@ function(jQuery, Paths, URI, HostApp) { } } + var _this = this; 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, - "" - + basic.name - + "" - ); - node.innerHTML = new_text; - } - } - }, null, false); // do not send auth-headers + var profile = function(profile) { + 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, + "" + + basic.name + + "" + ); + node.innerHTML = new_text; + } } - }); + } + + if (_this.followings.followings[mention.entity]) { + + profile(_this.followings.followings[mention.entity].profile) + + } else { + + Paths.findProfileURL(mention.entity, function(profile_url) { + if (profile_url) { + Paths.getURL(profile_url, "GET", function(resp) { + var p = JSON.parse(resp.responseText); + profile(p) + }, null, false); // do not send auth-headers + } + }); + } + })(mention); } } diff --git a/WebKit/scripts/helper/Followings.js b/WebKit/scripts/helper/Followings.js new file mode 100644 index 0000000..0979020 --- /dev/null +++ b/WebKit/scripts/helper/Followings.js @@ -0,0 +1,50 @@ +define([ + "helper/Paths", + "lib/URI" +], + +function(Paths, URI) { + + function Followings() { + + this.timeout = 2 * 60 * 1000; + this.followings = {}; + this.before_id = null; + + var _this = this; + this.intervall = setInterval(function() { _this.getAllFollowings(); }, this.timeout); + + this.getAllFollowings(); + } + + Followings.prototype.getAllFollowings = function() { + + var _this = this; + + var callback = function(resp) { + + var fs = JSON.parse(resp.responseText) + + if (fs.length < 1) return; + + for (var i = 0; i < fs.length; i++) { + + var following = fs[i]; + _this.before_id = following.id; + _this.followings[following.entity] = following; + } + + _this.getAllFollowings(); + } + + var url = URI(Paths.mkApiRootPath("/followings")); + if (this.before_id) { + url.addSearch("before_id", this.before_id); + } + + Paths.getURL(url, "GET", callback); + } + + return Followings; + +}); \ No newline at end of file diff --git a/WebKit/scripts/helper/HostApp.js b/WebKit/scripts/helper/HostApp.js index 0403b72..6ba6275 100644 --- a/WebKit/scripts/helper/HostApp.js +++ b/WebKit/scripts/helper/HostApp.js @@ -61,6 +61,15 @@ define(function() { } } + HostApp.showConversation = function(id, entity) { + + if (OS_TYPE == "mac") { + controller.showConversationForPostId_andEntity_(id, entity); + } else { + controller.showConversationForPostIdandEntity(id, entity); + } + } + return HostApp; }); \ No newline at end of file diff --git a/WebKit/scripts/helper/Paths.js b/WebKit/scripts/helper/Paths.js index 43b27e7..c2a6516 100644 --- a/WebKit/scripts/helper/Paths.js +++ b/WebKit/scripts/helper/Paths.js @@ -21,21 +21,28 @@ function(jQuery, HostApp, Hmac) { } 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); } } @@ -54,6 +61,7 @@ function(jQuery, HostApp, Hmac) { } Paths.findProfileURL = function(entity, callback) { + jQuery.ajax({ url: entity, type: "HEAD", diff --git a/WebKit/scripts/main.js b/WebKit/scripts/main.js index de3f063..4c5947f 100644 --- a/WebKit/scripts/main.js +++ b/WebKit/scripts/main.js @@ -39,6 +39,12 @@ function start(view) { } else if (view == "conversation") { + require(["controller/Conversation"], function(Conversation) { + + tentia_instance = new Conversation(); + + }); + } } @@ -59,10 +65,12 @@ function loadPlugin(url) { } function debug(string) { + if (typeof string != "string") { string = JSON.stringify(string); } + alert("DEBUG: " + string); } -setTimeout(HostAppGo, 1000); +setTimeout(HostAppGo, 2000);