diff --git a/Mac/Controller.h b/Mac/Controller.h index e6b518a..1fa902c 100644 --- a/Mac/Controller.h +++ b/Mac/Controller.h @@ -13,6 +13,8 @@ #import "Constants.h" #import "AccessToken.h" #import +#import "NSData+Base64.h" +#import "MimeType.h" @interface Controller : NSObject { IBOutlet WebView *timelineView; diff --git a/Mac/Controller.m b/Mac/Controller.m index 0958e26..6041a71 100644 --- a/Mac/Controller.m +++ b/Mac/Controller.m @@ -9,6 +9,7 @@ #import "Controller.h" #import "NewMessageWindow.h" #import "PostModel.h" +#import "NSData+Base64.h" @implementation Controller @synthesize loginViewWindow; @@ -259,7 +260,6 @@ return NO; } - - (IBAction)openNewMessageWindow:(id)sender { [NSApp activateIgnoringOtherApps:YES]; @@ -288,7 +288,6 @@ NewMessageWindow *newTweet = (NewMessageWindow *)[[NSDocumentController sharedDocumentController] openUntitledDocumentAndDisplay:YES error:nil]; [newTweet withString:aString]; } - } - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent @@ -307,11 +306,24 @@ if (post.location) { locationObject = [NSString stringWithFormat:@"[%f, %f]", post.location.coordinate.latitude, post.location.coordinate.longitude]; } - NSString *func = [NSString stringWithFormat:@"tentia_instance.sendNewMessage(\"%@\", \"%@\", \"%@\", %@)", + + NSString *imageFilePath = @"null"; + if (post.imageFilePath) { + NSError *error; + NSString *mimeType = [MimeType mimeTypeForFileAtPath:post.imageFilePath error:&error]; + NSData *data = [[NSData alloc] initWithContentsOfFile:post.imageFilePath]; + NSString *base64 = [data base64Encoding_xcd]; + [data release]; + imageFilePath = [NSString stringWithFormat:@"\"data:%@;base64,%@\"", mimeType, base64]; + } + + NSString *func = [NSString stringWithFormat:@"tentia_instance.sendNewMessage(\"%@\", \"%@\", \"%@\", %@, %@)", text, post.inReplyTostatusId, post.inReplyToEntity, - locationObject]; + locationObject, + imageFilePath]; + [timelineView stringByEvaluatingJavaScriptFromString:func]; } diff --git a/Mac/English.lproj/NewMessageWindow.xib b/Mac/English.lproj/NewMessageWindow.xib index a845fb8..def5ffb 100644 --- a/Mac/English.lproj/NewMessageWindow.xib +++ b/Mac/English.lproj/NewMessageWindow.xib @@ -184,15 +184,23 @@ Add current location 2147483647 - + NSImage NSMenuCheckmark - + NSImage NSMenuMixedState + + + Add image + + 2147483647 + + + @@ -263,6 +271,14 @@ 100054 + + + addImage: + + + + 100056 + delegate @@ -378,6 +394,7 @@ YES + @@ -386,6 +403,11 @@ + + 100055 + + + @@ -403,6 +425,7 @@ 100040.IBPluginDependency 100041.IBPluginDependency 100043.IBPluginDependency + 100055.IBPluginDependency 5.IBPluginDependency 5.IBWindowTemplateEditedContentRect 6.IBPluginDependency @@ -421,6 +444,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin {{127, 736}, {299, 113}} com.apple.InterfaceBuilder.CocoaPlugin @@ -437,7 +461,7 @@ - 100054 + 100056 diff --git a/Mac/MimeType.h b/Mac/MimeType.h new file mode 100644 index 0000000..50a40d6 --- /dev/null +++ b/Mac/MimeType.h @@ -0,0 +1,13 @@ +// +// MimeType.h +// Tentia +// +// Created by Jeena on 23/11/2012. +// +// + +#import + +@interface MimeType : NSObject ++(NSString *)mimeTypeForFileAtPath:(NSString *)path error:(NSError **)err; +@end diff --git a/Mac/MimeType.m b/Mac/MimeType.m new file mode 100644 index 0000000..75372f7 --- /dev/null +++ b/Mac/MimeType.m @@ -0,0 +1,27 @@ +// +// MimeType.m +// Tentia +// +// Created by Jeena on 23/11/2012. +// +// + +#import "MimeType.h" + +@implementation MimeType + ++(NSString *)mimeTypeForFileAtPath:(NSString *)path error:(NSError **)err { + NSString *uti, *mimeType = nil; + + if (!(uti = [[NSWorkspace sharedWorkspace] typeOfFile:path error:err])) + return nil; + if (err) + *err = nil; + + if ((mimeType = (NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)uti, kUTTagClassMIMEType))) + mimeType = NSMakeCollectable(mimeType); + + return mimeType; +} + +@end diff --git a/Mac/NSData+Base64.h b/Mac/NSData+Base64.h new file mode 100644 index 0000000..24d0984 --- /dev/null +++ b/Mac/NSData+Base64.h @@ -0,0 +1,46 @@ +// +// Created by Cédric Luthi on 2012-02-24. +// Copyright (c) 2012 Cédric Luthi. All rights reserved. +// + +#import "NSData+Base64.h" + +#ifndef __has_feature +#define __has_feature(x) 0 +#endif + +@implementation NSData (Base64) + ++ (id) dataWithBase64Encoding_xcd:(NSString *)base64Encoding +{ + if ([base64Encoding length] % 4 != 0) + return nil; + + NSString *plist = [NSString stringWithFormat:@"%@", base64Encoding]; + return [NSPropertyListSerialization propertyListWithData:[plist dataUsingEncoding:NSASCIIStringEncoding] options:0 format:NULL error:NULL]; +} + +- (NSString *) base64Encoding_xcd +{ + NSData *plist = [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL]; + NSRange fullRange = NSMakeRange(0, [plist length]); + NSRange startRange = [plist rangeOfData:[@"" dataUsingEncoding:NSASCIIStringEncoding] options:0 range:fullRange]; + NSRange endRange = [plist rangeOfData:[@"" dataUsingEncoding:NSASCIIStringEncoding] options:NSDataSearchBackwards range:fullRange]; + if (startRange.location == NSNotFound || endRange.location == NSNotFound) + return nil; + + NSUInteger base64Location = startRange.location + startRange.length; + NSUInteger base64length = endRange.location - base64Location; + NSData *base64Data = [NSData dataWithBytesNoCopy:(void *)((uintptr_t)base64Location + (uintptr_t)[plist bytes]) length:base64length freeWhenDone:NO]; + NSString *base64Encoding = [[NSString alloc] initWithData:base64Data encoding:NSASCIIStringEncoding]; + base64Encoding = [base64Encoding stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + base64Encoding = [base64Encoding stringByReplacingOccurrencesOfString:@"\n" withString:@""]; + +#if __has_feature(objc_arc) + return base64Encoding; +#else + return [base64Encoding autorelease]; +#endif +} + +@end \ No newline at end of file diff --git a/Mac/NSData+Base64.m b/Mac/NSData+Base64.m new file mode 100644 index 0000000..1a71914 --- /dev/null +++ b/Mac/NSData+Base64.m @@ -0,0 +1,13 @@ +// +// Created by Cédric Luthi on 2012-02-24. +// Copyright (c) 2012 Cédric Luthi. All rights reserved. +// + +#import + +@interface NSData (Base64) + ++ (id) dataWithBase64Encoding_xcd:(NSString *)base64String; +- (NSString *) base64Encoding_xcd; + +@end \ No newline at end of file diff --git a/Mac/NewMessageWindow.h b/Mac/NewMessageWindow.h index 6f49e95..3c17540 100644 --- a/Mac/NewMessageWindow.h +++ b/Mac/NewMessageWindow.h @@ -11,7 +11,7 @@ #import -@interface NewMessageWindow : NSDocument +@interface NewMessageWindow : NSDocument { IBOutlet NSTextField *textField; IBOutlet NSTextField *counter; @@ -22,6 +22,7 @@ NSMenuItem *addImage; CLLocationManager *locationManager; CLLocation *currentLocation; + NSString *imageFilePath; } @property (nonatomic, retain) IBOutlet NSTextField *textField; @@ -30,6 +31,7 @@ @property (assign) IBOutlet NSButton *addMenuButton; @property (retain, nonatomic) CLLocationManager *locationManager; @property (retain, nonatomic) CLLocation *currentLocation; +@property (retain, nonatomic) NSString *imageFilePath; - (IBAction)sendTweet:(NSControl *)control; - (void)inReplyTo:(NSString *)userName statusId:(NSString *)statusId withString:(NSString *)string; diff --git a/Mac/NewMessageWindow.m b/Mac/NewMessageWindow.m index 5806f89..50073a5 100644 --- a/Mac/NewMessageWindow.m +++ b/Mac/NewMessageWindow.m @@ -22,12 +22,14 @@ @synthesize addMenuButton; @synthesize textField, counter; @synthesize locationManager, currentLocation; +@synthesize imageFilePath; - (void)dealloc { [locationManager stopUpdatingLocation]; [locationManager release]; [currentLocation release]; + [imageFilePath release]; [super dealloc]; } @@ -139,10 +141,6 @@ } } -- (IBAction)addImage:(id)sender -{ -} - - (IBAction)openAddMenu:(id)sender { NSRect frame = [(NSButton *)sender frame]; @@ -203,6 +201,7 @@ post.inReplyTostatusId = inReplyTostatusId; post.inReplyToEntity = inReplyToEntity; post.location = self.currentLocation; + post.imageFilePath = self.imageFilePath; [[NSNotificationCenter defaultCenter] postNotificationName:@"sendTweet" object:post]; [self close]; } else { @@ -246,4 +245,66 @@ return retval; } +#pragma mark Add images + +- (IBAction)addImage:(id)sender +{ + NSMenuItem *menuItem = (NSMenuItem *)sender; + + if (!self.imageFilePath) + { + [menuItem setTitle:@"Remove image"]; + + NSOpenPanel* openDlg = [NSOpenPanel openPanel]; + [openDlg setPrompt:@"Select"]; + [openDlg setDelegate:self]; + + // Enable the selection of files in the dialog. + [openDlg setCanChooseFiles:YES]; + + // Enable the selection of directories in the dialog. + [openDlg setCanChooseDirectories:NO]; + + // Display the dialog. If the OK button was pressed, + // process the files. + if ( [openDlg runModalForDirectory:nil file:nil] == NSOKButton ) + { + // Get an array containing the full filenames of all + // files and directories selected. + NSArray* files = [openDlg filenames]; + + // Loop through all the files and process them. + for( int i = 0; i < [files count]; i++ ) + { + self.imageFilePath = [files objectAtIndex:i]; + } + } + } + else + { + self.imageFilePath = nil; + [menuItem setTitle:@"Add image"]; + } +} + +-(BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename +{ + NSString* ext = [filename pathExtension]; + if (ext == @"" || ext == @"/" || ext == nil || ext == NULL || [ext length] < 1) { + return YES; + } + + NSEnumerator* tagEnumerator = [[NSArray arrayWithObjects:@"png", @"jpg", @"gif", @"jpeg", nil] objectEnumerator]; + NSString* allowedExt; + while ((allowedExt = [tagEnumerator nextObject])) + { + if ([ext caseInsensitiveCompare:allowedExt] == NSOrderedSame) + { + return YES; + } + } + + return NO; +} + @end diff --git a/Mac/PostModel.h b/Mac/PostModel.h index 5130c1b..426a539 100644 --- a/Mac/PostModel.h +++ b/Mac/PostModel.h @@ -14,13 +14,13 @@ NSString *inReplyTostatusId; NSString *inReplyToEntity; CLLocation *location; - NSImage *image; + NSString *imageFilePath; } @property (nonatomic, retain) NSString *text; @property (nonatomic, retain) NSString *inReplyTostatusId; @property (nonatomic, retain) NSString *inReplyToEntity; @property (nonatomic, retain) CLLocation *location; -@property (nonatomic, retain) NSImage *image; +@property (nonatomic, retain) NSString *imageFilePath; @end diff --git a/Mac/PostModel.m b/Mac/PostModel.m index ccebd0e..777c3ff 100644 --- a/Mac/PostModel.m +++ b/Mac/PostModel.m @@ -11,7 +11,7 @@ @implementation PostModel -@synthesize text, inReplyTostatusId, inReplyToEntity, location, image; +@synthesize text, inReplyTostatusId, inReplyToEntity, location, imageFilePath; - (void)dealloc { @@ -19,7 +19,7 @@ [inReplyTostatusId release]; [inReplyToEntity release]; [location release]; - [image release]; + [imageFilePath release]; [super dealloc]; } diff --git a/Mac/Tentia.xcodeproj/project.pbxproj b/Mac/Tentia.xcodeproj/project.pbxproj index 2c32eee..3561294 100644 --- a/Mac/Tentia.xcodeproj/project.pbxproj +++ b/Mac/Tentia.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 1F618ECA12DB5E6100E500D9 /* PostModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F618EC912DB5E6100E500D9 /* PostModel.m */; }; 1F70619F1178FBB300C85707 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F70619E1178FBB300C85707 /* Carbon.framework */; }; 1F77DB47118C5F1C007C7F1E /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F77DB46118C5F1C007C7F1E /* Constants.m */; }; + 1F880B6B165EE0F60022A84D /* NSData+Base64.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F880B6A165EE0F60022A84D /* NSData+Base64.m */; }; + 1F880B6E165FE8890022A84D /* MimeType.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F880B6D165FE8890022A84D /* MimeType.m */; }; 1FA09847144602530079E258 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FA09846144602530079E258 /* libicucore.dylib */; }; 1FC254A01427DFAD0035D84B /* AccessToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FC2549B1427D9930035D84B /* AccessToken.m */; }; 1FDEF722164EFE9100F927F3 /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FDEF721164EFE9100F927F3 /* Growl.framework */; }; @@ -65,6 +67,10 @@ 1F70619E1178FBB300C85707 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 1F77DB45118C5F1C007C7F1E /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Constants.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 1F77DB46118C5F1C007C7F1E /* Constants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = Constants.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 1F880B69165EE0F60022A84D /* NSData+Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Base64.h"; sourceTree = ""; }; + 1F880B6A165EE0F60022A84D /* NSData+Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Base64.m"; sourceTree = ""; }; + 1F880B6C165FE8890022A84D /* MimeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MimeType.h; sourceTree = ""; }; + 1F880B6D165FE8890022A84D /* MimeType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MimeType.m; sourceTree = ""; }; 1FA09846144602530079E258 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; }; 1FC2549A1427D9930035D84B /* AccessToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = AccessToken.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 1FC2549B1427D9930035D84B /* AccessToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AccessToken.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -169,6 +175,10 @@ 1F618EC912DB5E6100E500D9 /* PostModel.m */, 1FC2549A1427D9930035D84B /* AccessToken.h */, 1FC2549B1427D9930035D84B /* AccessToken.m */, + 1F880B69165EE0F60022A84D /* NSData+Base64.h */, + 1F880B6A165EE0F60022A84D /* NSData+Base64.m */, + 1F880B6C165FE8890022A84D /* MimeType.h */, + 1F880B6D165FE8890022A84D /* MimeType.m */, ); name = Classes; sourceTree = ""; @@ -286,6 +296,8 @@ 1FFA36D81177D879006C8562 /* ViewDelegate.m in Sources */, 1F77DB47118C5F1C007C7F1E /* Constants.m in Sources */, 1F618ECA12DB5E6100E500D9 /* PostModel.m in Sources */, + 1F880B6B165EE0F60022A84D /* NSData+Base64.m in Sources */, + 1F880B6E165FE8890022A84D /* MimeType.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Mac/publish/ReleaseNotes.html b/Mac/publish/ReleaseNotes.html index 87c5776..1a0a3c8 100644 --- a/Mac/publish/ReleaseNotes.html +++ b/Mac/publish/ReleaseNotes.html @@ -27,6 +27,7 @@

Implemented remove deleted posts

Added a JS and CSS Plugin API

Implemented adding location to post

+

Implemented blue right border if you're mentioned in a post

Bugfixes


diff --git a/WebKit/css/default.css b/WebKit/css/default.css index c14c32b..acfa130 100644 --- a/WebKit/css/default.css +++ b/WebKit/css/default.css @@ -256,3 +256,9 @@ li.mentioned { .mentions li.mentioned { border-right: 0; } + +.photo { + margin-top: 10px; + max-width: 100%; + border-radius: 3px; +} \ No newline at end of file diff --git a/WebKit/scripts/controller/Oauth.js b/WebKit/scripts/controller/Oauth.js index c790caa..70bb90a 100644 --- a/WebKit/scripts/controller/Oauth.js +++ b/WebKit/scripts/controller/Oauth.js @@ -98,7 +98,7 @@ function(HostApp, Paths, Hmac) { this.state = Hmac.makeid(19); var auth = "/oauth/authorize?client_id=" + register_data["id"] - + "&redirect_uri=" + this.app_info["redirect_uris"][0] // Check if this still works on mac + + "&redirect_uri=" + this.app_info["redirect_uris"][0] + "&scope=" + Object.keys(this.app_info["scopes"]).join(",") + "&state=" + this.state + "&tent_post_types=all"; diff --git a/WebKit/scripts/controller/Timeline.js b/WebKit/scripts/controller/Timeline.js index 1892422..55d8785 100644 --- a/WebKit/scripts/controller/Timeline.js +++ b/WebKit/scripts/controller/Timeline.js @@ -42,15 +42,22 @@ function(Core, Paths, HostApp, URI) { this.since_id = status.id; this.since_id_entity = status.entity; - if (status.type == "https://tent.io/types/post/status/v0.1.0") { + if (status.type == "https://tent.io/types/post/status/v0.1.0" || status.type == "https://tent.io/types/post/photo/v0.1.0") { + + var new_node = this.getStatusDOMElement(status); 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); + + this.body.insertBefore(new_node, this.body.firstChild); + } else { - this.body.appendChild(this.getStatusDOMElement(status)); + + this.body.appendChild(new_node); } } else if (status.type == "https://tent.io/types/post/delete/v0.1.0") { @@ -78,7 +85,8 @@ function(Core, Paths, HostApp, URI) { var post_types = [ "https://tent.io/types/post/repost/v0.1.0", "https://tent.io/types/post/status/v0.1.0", - "https://tent.io/types/post/delete/v0.1.0" + "https://tent.io/types/post/delete/v0.1.0", + "https://tent.io/types/post/photo/v0.1.0" ]; url.addSearch("post_types", post_types.join(",")); @@ -117,10 +125,10 @@ function(Core, Paths, HostApp, URI) { } } - Timeline.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity, location) { + Timeline.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity, location, image_data_uri) { var _this = this; var callback = function(data) { _this.getNewData(); } - Core.prototype.sendNewMessage.call(this, content, in_reply_to_status_id, in_reply_to_entity, location, callback); + Core.prototype.sendNewMessage.call(this, content, in_reply_to_status_id, in_reply_to_entity, location, image_data_uri, callback); } Timeline.prototype.remove = function(id) { diff --git a/WebKit/scripts/helper/Core.js b/WebKit/scripts/helper/Core.js index 614c5e4..cacfb8d 100644 --- a/WebKit/scripts/helper/Core.js +++ b/WebKit/scripts/helper/Core.js @@ -211,7 +211,15 @@ function(jQuery, Paths, URI, HostApp, Followings) { template.in_reply.parentNode.className = "hidden"; - var text = status.content.text.escapeHTML().replace(/\n/g, "
"); + var text = ""; + + if (status.type == "https://tent.io/types/post/photo/v0.1.0") { + text = status.content.caption; + } else { + text = status.content.text; + } + + text = text.escapeHTML().replace(/\n/g, "
"); var entities = [status.entity]; status.mentions.map(function (mention) { @@ -222,6 +230,25 @@ function(jQuery, Paths, URI, HostApp, Followings) { this.replaceURLWithHTMLLinks(text, entities, template.message) ); + if (status.type == "https://tent.io/types/post/photo/v0.1.0") { + + for (var i = 0; i < status.attachments.length; i++) { + + var attachment = status.attachments[i]; + var img = new Image(); + img.className = "photo"; + template.message.parentNode.insertBefore(img, template.message.nextSibling); + + var url = Paths.mkApiRootPath("/posts/" + status.id + "/attachments/" + attachment.name); + + var callback = function(resp) { + img.src = "data:image/png;base64," + resp.responseText; + } + + Paths.getURL(url.toString(), "GET", callback, null, null, attachment.type); + }; + } + this.findMentions(template.message, status.mentions); for (var i = 0; i < status.mentions.length; i++) { @@ -258,20 +285,54 @@ function(jQuery, Paths, URI, HostApp, Followings) { return template.item; } - Core.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity, location, callback) { + Core.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity, location, image_file_path, callback) { + + if (image_file_path) { + + this.sendNewMessageWithImage(content, in_reply_to_status_id, in_reply_to_entity, location, image_file_path, callback); + + } else { + + var url = URI(Paths.mkApiRootPath("/posts")); + + var http_method = "POST"; + + var data = { + "type": "https://tent.io/types/post/status/v0.1.0", + "published_at": parseInt(new Date().getTime() / 1000, 10), + "permissions": { + "public": true + }, + "content": { + "text": content, + }, + }; + + if (location) { + data["content"]["location"] = { "type": "Point", "coordinates": location } + } + + 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)); + } + } + + Core.prototype.sendNewMessageWithImage = function(content, in_reply_to_status_id, in_reply_to_entity, location, image_data_uri, 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), + "type": "https://tent.io/types/post/photo/v0.1.0", + "published_at": parseInt(new Date().getTime() / 1000, 10), "permissions": { "public": true }, "content": { - "text": content, + "caption": content, }, }; @@ -280,12 +341,46 @@ function(jQuery, Paths, URI, HostApp, Followings) { } 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)); + var data_string = JSON.stringify(data); + + var boundary = "TentAttachment----------TentAttachment"; + var post = "--" + boundary + "\r\n"; + + post += 'Content-Disposition: form-data; name="post"; filename="post.json"\r\n'; + post += 'Content-Length: ' + data_string.length + '\r\n'; + post += 'Content-Type: application/vnd.tent.v0+json\r\n'; + post += 'Content-Transfer-Encoding: binary\r\n\r\n'; + post += data_string; + + post += "\r\n--" + boundary + "\r\n"; + + var binary_data = this.dataURItoBlob(image_data_uri); + var ext = "png"; + if (binary_data.mime_type == "image/jpeg") { + ext = "jpeg"; + } else if (binary_data.mime_type == "image/gif") { + ext = "gif"; + } + + var reader = new FileReader(); + reader.onload = function(e) { + + var blob_string = e.target.result; + post += 'Content-Disposition: form-data; name="photos[0]"; filename="photo.' + ext + '"\r\n'; + post += 'Content-Length: ' + blob_string.length + "\r\n"; + post += 'Content-Type: ' + binary_data.mime_type + "\r\n"; + post += 'Content-Transfer-Encoding: base64\r\n\r\n'; + post += image_data_uri.split(',')[1]; + post += "\r\n--" + boundary + "--\r\n"; + + Paths.postMultipart(url.toString(), callback, post, boundary); + } + + reader.readAsBinaryString(binary_data.blob) } Core.prototype.remove = function(id, callback) { @@ -475,6 +570,30 @@ function(jQuery, Paths, URI, HostApp, Followings) { HostApp.openNewMessageWidow(entity, status_id, string); } + Core.prototype.dataURItoBlob = function(dataURI) { + // convert base64 to raw binary data held in a string + // doesn't handle URLEncoded DataURIs + var byteString = atob(dataURI.split(',')[1]); + + // separate out the mime component + var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + + // write the bytes of the string to an ArrayBuffer + var ab = new ArrayBuffer(byteString.length); + var ia = new Uint8Array(ab); + for (var i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + // write the ArrayBuffer to a blob, and you're done + var blob = new Blob([ab], {type: mimeString}); + return { + mime_type: mimeString, + blob: blob, + base64: byteString + } + } + return Core; }); \ No newline at end of file diff --git a/WebKit/scripts/helper/Paths.js b/WebKit/scripts/helper/Paths.js index 222e429..92a0c32 100644 --- a/WebKit/scripts/helper/Paths.js +++ b/WebKit/scripts/helper/Paths.js @@ -20,11 +20,16 @@ function(jQuery, HostApp, Hmac) { return vars; } - Paths.getURL = function(url, http_method, callback, data, auth_header) { + Paths.getURL = function(url, http_method, callback, data, auth_header, accepts) { + + accepts = accepts || "application/vnd.tent.v0+json"; jQuery.ajax({ beforeSend: function(xhr) { + + xhr.setRequestHeader("Accept", accepts); + if (data) xhr.setRequestHeader("Content-Length", data.length); if (auth_header) { // if is_set? auth_header @@ -40,7 +45,6 @@ function(jQuery, HostApp, Hmac) { auth_header = Hmac.makeAuthHeader( url, http_method, - //HostApp.stringForKey("user_mac_key"), HostApp.secret(), user_access_token ); @@ -49,7 +53,6 @@ function(jQuery, HostApp, Hmac) { } }, url: url, - accepts: "application/vnd.tent.v0+json", contentType: "application/vnd.tent.v0+json", type: http_method, complete: callback, @@ -61,6 +64,42 @@ function(jQuery, HostApp, Hmac) { }); } + Paths.postMultipart = function(url, callback, data, boundary) { + + jQuery.ajax({ + + beforeSend: function(xhr) { + + if (data) xhr.setRequestHeader("Content-Length", data.length); + debug("Content-Length: " + data.length); + + var user_access_token = HostApp.stringForKey("user_access_token"); + + if (user_access_token) { + + auth_header = Hmac.makeAuthHeader( + url, + "POST", + HostApp.secret(), + user_access_token + ); + debug(auth_header) + xhr.setRequestHeader("Authorization", auth_header); + } + }, + url: url, + accepts: "application/vnd.tent.v0+json", + contentType: "multipart/form-data;boundary=" + boundary, + type: "POST", + complete: callback, + data: data, + processData: false, + error: function(xhr, ajaxOptions, thrownError) { + console.error("postMultipart " + xhr.statusText + " (" + url + "): '" + xhr.responseText + "'"); + } + }); + } + Paths.findProfileURL = function(entity, callback, errorCallback) { jQuery.ajax({