use selected text as quote when sharing from Safari

This commit is contained in:
Sami Samhuri 2015-05-21 10:32:37 -04:00
parent ba08fdcfe4
commit 477a7939ed
8 changed files with 180 additions and 47 deletions

View file

@ -57,6 +57,9 @@
7BE3A0351AE461E700E45CCB /* PreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BE3A0341AE461E700E45CCB /* PreviewViewController.m */; };
7BF029331A27117200E42EDE /* ModelStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BF029321A27117200E42EDE /* ModelStore.m */; };
7BF029381A280CB200E42EDE /* BlogController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BF029371A280CB200E42EDE /* BlogController.m */; };
7BF960261B0D802900A19A2B /* GrabSelectedText.js in Resources */ = {isa = PBXBuildFile; fileRef = 7BF960251B0D802900A19A2B /* GrabSelectedText.js */; };
7BF960271B0E1B1000A19A2B /* SharedContent.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BCFCC904E1195A3DA84D276 /* SharedContent.m */; };
7BF960281B0E1B1500A19A2B /* ExtensionItemProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BCFC0549FB7723D8893A91C /* ExtensionItemProcessor.m */; };
848D1BC47C9F03BB91A24EB4 /* libPods-samhuri.net.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D351844AAA829A65456E283E /* libPods-samhuri.net.a */; };
B8B8958B2AA40812EFE04FEF /* libPods-BlogTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C36BC848680D4F831E4DE23 /* libPods-BlogTests.a */; };
/* End PBXBuildFile section */
@ -94,8 +97,11 @@
/* Begin PBXFileReference section */
1613DC56A86AFA7E50460A37 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
1BCFC0549FB7723D8893A91C /* ExtensionItemProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExtensionItemProcessor.m; sourceTree = "<group>"; };
1BCFC23988387A5CAE551C90 /* UIColor+Hex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Hex.m"; sourceTree = "<group>"; };
1BCFC27DA8A32D0484C273A0 /* ExtensionItemProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExtensionItemProcessor.h; sourceTree = "<group>"; };
1BCFC32EC1E5AE196D194D21 /* CommonUI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommonUI.m; sourceTree = "<group>"; };
1BCFC384864D31C679683404 /* SharedContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharedContent.h; sourceTree = "<group>"; };
1BCFC3B62AA92DB07923F7C1 /* PostCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostCollection.m; sourceTree = "<group>"; };
1BCFC40D1BFCFBF04BC79A35 /* MuseoSans-900.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "MuseoSans-900.otf"; sourceTree = "<group>"; };
1BCFC462F5540C67B9D97299 /* MuseoSans-700.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "MuseoSans-700.otf"; sourceTree = "<group>"; };
@ -106,6 +112,7 @@
1BCFCB67571197A762B88624 /* MuseoSans-100-Italic.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "MuseoSans-100-Italic.otf"; sourceTree = "<group>"; };
1BCFCB89F8C8583AE061757E /* MuseoSans-500.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "MuseoSans-500.otf"; sourceTree = "<group>"; };
1BCFCC3154DB1D3B3C025211 /* MuseoSans-100.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "MuseoSans-100.otf"; sourceTree = "<group>"; };
1BCFCC904E1195A3DA84D276 /* SharedContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharedContent.m; sourceTree = "<group>"; };
1BCFCCF30594E0E2DCC32116 /* PostCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostCell.h; sourceTree = "<group>"; };
1BCFCD0E9504E1E8AB8C0275 /* MuseoSans-300-Italic.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "MuseoSans-300-Italic.otf"; sourceTree = "<group>"; };
1BCFCDD73D4AE8F16A9C9E3D /* ChangeTitleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChangeTitleViewController.h; sourceTree = "<group>"; };
@ -162,6 +169,7 @@
7BF029321A27117200E42EDE /* ModelStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModelStore.m; sourceTree = "<group>"; };
7BF029361A280CB200E42EDE /* BlogController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlogController.h; sourceTree = "<group>"; };
7BF029371A280CB200E42EDE /* BlogController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlogController.m; sourceTree = "<group>"; };
7BF960251B0D802900A19A2B /* GrabSelectedText.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = GrabSelectedText.js; sourceTree = "<group>"; };
9C36BC848680D4F831E4DE23 /* libPods-BlogTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BlogTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A12E4260DF3BBC175C358446 /* Pods-samhuri.net.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-samhuri.net.debug.xcconfig"; path = "Pods/Target Support Files/Pods-samhuri.net/Pods-samhuri.net.debug.xcconfig"; sourceTree = "<group>"; };
A2EB178BEF4356711B2710AE /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
@ -236,10 +244,15 @@
7B08B3E81AF9CAD300435579 /* samhuri.net */ = {
isa = PBXGroup;
children = (
7B08B3EE1AF9CAD300435579 /* MainInterface.storyboard */,
7B08B3EB1AF9CAD300435579 /* ShareViewController.h */,
7B08B3EC1AF9CAD300435579 /* ShareViewController.m */,
7B08B3EE1AF9CAD300435579 /* MainInterface.storyboard */,
7BF960251B0D802900A19A2B /* GrabSelectedText.js */,
7B08B3E91AF9CAD300435579 /* Supporting Files */,
1BCFCC904E1195A3DA84D276 /* SharedContent.m */,
1BCFC384864D31C679683404 /* SharedContent.h */,
1BCFC0549FB7723D8893A91C /* ExtensionItemProcessor.m */,
1BCFC27DA8A32D0484C273A0 /* ExtensionItemProcessor.h */,
);
path = samhuri.net;
sourceTree = "<group>";
@ -500,6 +513,7 @@
files = (
7B08B3EF1AF9CAD300435579 /* MainInterface.storyboard in Resources */,
7B010BAF1AFB3EA900351D18 /* auth.json in Resources */,
7BF960261B0D802900A19A2B /* GrabSelectedText.js in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -639,9 +653,11 @@
7B08B3F71AF9CBBD00435579 /* NSDate+marshmallows.m in Sources */,
7B08B3FB1AF9CBC500435579 /* BlogService.m in Sources */,
7B08B3FD1AF9CBC500435579 /* ModelStore.m in Sources */,
7BF960281B0E1B1500A19A2B /* ExtensionItemProcessor.m in Sources */,
7B08B4021AF9D3EA00435579 /* SamhuriNet.m in Sources */,
7B08B3ED1AF9CAD300435579 /* ShareViewController.m in Sources */,
7B08B3F91AF9CBC500435579 /* BlogStatus.m in Sources */,
7BF960271B0E1B1000A19A2B /* SharedContent.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -0,0 +1,14 @@
//
// Created by Sami Samhuri on 15-05-20.
// Copyright (c) 2015 Guru Logic Inc. All rights reserved.
//
@import Foundation;
@class PMKPromise;
@interface ExtensionItemProcessor : NSObject
- (PMKPromise *)sharedContentForPListItem:(NSExtensionItem *)item;
- (PMKPromise *)sharedContentForURLItem:(NSExtensionItem *)item text:(NSString *)text;
@end

View file

@ -0,0 +1,54 @@
//
// Created by Sami Samhuri on 15-05-20.
// Copyright (c) 2015 Guru Logic Inc. All rights reserved.
//
#import <PromiseKit/Promise.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "ExtensionItemProcessor.h"
#import "SharedContent.h"
#import "NSString+marshmallows.h"
@implementation ExtensionItemProcessor
- (PMKPromise *)sharedContentForPListItem:(NSExtensionItem *)item {
return [PMKPromise new:^(PMKFulfiller fulfill, PMKRejecter reject) {
NSItemProvider *provider = [self providerForIdentifier:(NSString *)kUTTypePropertyList fromExtensionItem:item];
if (!provider) {
reject([NSError errorWithDomain:@"ShareViewControllerDomain" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Cannot find PList provider"}]);
return;
}
[provider loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *stuff, NSError *error) {
NSDictionary *results = stuff[NSExtensionJavaScriptPreprocessingResultsKey];
NSURL *url = [NSURL URLWithString:results[@"url"]];
NSString *quotedText = [results[@"selectedText"] mm_stringByTrimmingWhitespace];
NSString *quote = quotedText.length ? @"> " : @"";
NSString *text = [NSString stringWithFormat:@"%@\n\n%@%@", results[@"title"], quote, quotedText];
fulfill([SharedContent contentWithURL:url text:text]);
}];
}];
}
- (PMKPromise *)sharedContentForURLItem:(NSExtensionItem *)item text:(NSString *)text {
return [PMKPromise new:^(PMKFulfiller fulfill, PMKRejecter reject) {
NSItemProvider *provider = [self providerForIdentifier:(NSString *)kUTTypeURL fromExtensionItem:item];
if (!provider) {
reject([NSError errorWithDomain:@"ShareViewControllerDomain" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Cannot find URL provider"}]);
return;
}
[provider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error) {
// TODO: fetch title?
fulfill([SharedContent contentWithURL:url text:text]);
}];
}];
}
- (NSItemProvider *)providerForIdentifier:(NSString *)identifier fromExtensionItem:(NSExtensionItem *)item {
for (NSItemProvider *provider in item.attachments) {
if ([provider hasItemConformingToTypeIdentifier:identifier]) {
return provider;
}
}
return nil;
}
@end

View file

@ -0,0 +1,11 @@
var SelectedTextPreprocessor = function() {};
SelectedTextPreprocessor.prototype = {
run: function(args) {
args.completionFunction({
"url": document.URL,
"title": document.title,
"selectedText": window.getSelection().toString()
});
}
};
window.ExtensionPreprocessingJS = new SelectedTextPreprocessor();

View file

@ -26,6 +26,8 @@
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionJavaScriptPreprocessingFile</key>
<string>GrabSelectedText</string>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsText</key>

View file

@ -6,24 +6,48 @@
// Copyright (c) 2015 Guru Logic Inc. All rights reserved.
//
#import <PromiseKit/PromiseKit.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "ShareViewController.h"
#import "SamhuriNet.h"
#import "BlogController.h"
#import "Post.h"
#import "SharedContent.h"
#import "ExtensionItemProcessor.h"
@interface ShareViewController ()
@property (nonatomic, readonly, strong) NSItemProvider *URLProvider;
@property (nonatomic, assign) BOOL checkedForURLProvider;
@property(nonatomic, strong) SharedContent *content;
@end
@implementation ShareViewController
@synthesize URLProvider = _URLProvider;
- (void)viewDidLoad {
[super viewDidLoad];
[self findSharedContent].then(^(SharedContent *content) {
self.textView.text = content.text;
self.content = content;
}).catch(^(NSError *error) {
[self displayError:error];
[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
});
}
- (PMKPromise *)findSharedContent {
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *provider in item.attachments) {
if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePropertyList]) {
return [[ExtensionItemProcessor new] sharedContentForPListItem:item];
}
if ([provider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]) {
return [[ExtensionItemProcessor new] sharedContentForURLItem:item text:self.contentText];
}
}
}
NSDictionary *info = @{NSLocalizedDescriptionKey: @"Cannot find PList or URL extension item to share."};
return [PMKPromise promiseWithValue:[NSError errorWithDomain:@"SharedViewControllerDomain" code:1 userInfo:info]];
}
- (BOOL)isContentValid {
return self.URLProvider != nil;
return self.textView.text.length > 0;
}
- (UIView *)loadPreviewView {
@ -38,48 +62,14 @@
NSRange titleEndRange = [self.contentText rangeOfString:@"\n\n"];
NSString *title = titleEndRange.location == NSNotFound ? self.contentText : [self.contentText substringToIndex:titleEndRange.location];
NSString *body = titleEndRange.location == NSNotFound ? @"" : [self.contentText substringFromIndex:titleEndRange.location + titleEndRange.length];
NSLog(@"title = %@", title);
NSLog(@"body = %@", body);
NSItemProvider *urlProvider = [self firstURLProvider];
BOOL reallyPost = YES;
[urlProvider loadItemForTypeIdentifier:@"public.url" options:nil completionHandler:^(NSURL *url, NSError *error) {
// TODO: image
NSLog(@"url = %@", url);
if (reallyPost) {
Post *post = [Post newDraftWithTitle:title body:body url:url];
[blogController requestCreateDraft:post publishImmediatelyToEnvironment:@"production" waitForCompilation:NO].catch(^(NSError *error) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self dismissViewControllerAnimated:YES completion:nil];
}]];
[self presentViewController:alert animated:YES completion:nil];
});
}
[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}];
}
- (NSItemProvider *)firstURLProvider {
NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSLog(@"item = %@", item);
for (NSItemProvider *provider in item.attachments) {
NSLog(@"provider = %@", provider);
if ([provider hasItemConformingToTypeIdentifier:@"public.url"]) {
return provider;
}
if (reallyPost) {
Post *post = [Post newDraftWithTitle:title body:body url:self.content.url];
[blogController requestCreateDraft:post publishImmediatelyToEnvironment:@"production" waitForCompilation:NO].catch(^(NSError *error) {
[self displayError:error];
});
}
return nil;
}
- (NSItemProvider *)URLProvider {
if (!self.checkedForURLProvider) {
_URLProvider = [self firstURLProvider];
if (!_URLProvider) {
NSLog(@"ERROR: No URL provider found");
}
self.checkedForURLProvider = YES;
}
return _URLProvider;
[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}
- (NSArray *)configurationItems {
@ -87,4 +77,12 @@
return @[];
}
- (void)displayError:(NSError *)error {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self dismissViewControllerAnimated:YES completion:nil];
}]];
[self presentViewController:alert animated:YES completion:nil];
}
@end

View file

@ -0,0 +1,16 @@
//
// Created by Sami Samhuri on 15-05-20.
// Copyright (c) 2015 Guru Logic Inc. All rights reserved.
//
@import Foundation;
@interface SharedContent : NSObject
@property(nonatomic, readonly, copy) NSURL *url;
@property(nonatomic, readonly, copy) NSString *text;
+ (instancetype)contentWithURL:(NSURL *)url text:(NSString *)text;
- (instancetype)initWithURL:(NSURL *)url contentText:(NSString *)text;
@end

View file

@ -0,0 +1,22 @@
//
// Created by Sami Samhuri on 15-05-20.
// Copyright (c) 2015 Guru Logic Inc. All rights reserved.
//
#import "SharedContent.h"
@implementation SharedContent
+ (instancetype)contentWithURL:(NSURL *)url text:(NSString *)text {
return [[self alloc] initWithURL:url contentText:text];
}
- (instancetype)initWithURL:(NSURL *)url contentText:(NSString *)text {
self = [super init];
if (self) {
_url = [url copy];
_text = [text copy];
}
return self;
}
@end