implement creating new drafts

This commit is contained in:
Sami Samhuri 2015-04-22 00:53:06 -07:00
parent c702143073
commit ea426550f3
9 changed files with 115 additions and 74 deletions

View file

@ -103,9 +103,14 @@
#pragma mark - UISplitViewDelegate methods #pragma mark - UISplitViewDelegate methods
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController { - (BOOL) splitViewController:(UISplitViewController *)splitViewController
UINavigationController *navigationController = [secondaryViewController isKindOfClass:[UINavigationController class]] ? (UINavigationController *)secondaryViewController : nil; collapseSecondaryViewController:(UIViewController *)secondaryViewController
if ([navigationController.topViewController isKindOfClass:[EditorViewController class]] && ([(EditorViewController *)navigationController.topViewController post] == nil)) { ontoPrimaryViewController:(UIViewController *)primaryViewController {
UINavigationController *navigationController = [secondaryViewController isKindOfClass:[UINavigationController class]]
? (UINavigationController *)secondaryViewController : nil;
if ([navigationController
.topViewController isKindOfClass:[EditorViewController class]] && ([(EditorViewController *)navigationController
.topViewController post] == nil)) {
// Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. // Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return YES; return YES;
} }

View file

@ -21,6 +21,7 @@ extern NSString *BlogPostDeletedNotification;
@class PMKPromise; @class PMKPromise;
@class ModelStore; @class ModelStore;
@class BlogService; @class BlogService;
@class Post;
@interface BlogController : NSObject @interface BlogController : NSObject
@ -36,8 +37,8 @@ extern NSString *BlogPostDeletedNotification;
- (PMKPromise *)requestAllPostsWithCaching:(BOOL)useCache; - (PMKPromise *)requestAllPostsWithCaching:(BOOL)useCache;
- (PMKPromise *)requestPostWithPath:(NSString *)path; - (PMKPromise *)requestPostWithPath:(NSString *)path;
- (PMKPromise *)requestCreateDraftWithID:(NSString *)draftID title:(NSString *)title body:(NSString *)body link:(NSString *)link; - (PMKPromise *)requestCreateDraft:(Post *)draft;
- (PMKPromise *)requestUpdatePostWithPath:(NSString *)path title:(NSString *)title body:(NSString *)body link:(NSString *)link; - (PMKPromise *)requestUpdatePost:(Post *)post;
- (PMKPromise *)requestPublishDraftWithPath:(NSString *)path; - (PMKPromise *)requestPublishDraftWithPath:(NSString *)path;
- (PMKPromise *)requestUnpublishPostWithPath:(NSString *)path; - (PMKPromise *)requestUnpublishPostWithPath:(NSString *)path;
- (PMKPromise *)requestDeletePostWithPath:(NSString *)path; - (PMKPromise *)requestDeletePostWithPath:(NSString *)path;

View file

@ -100,24 +100,20 @@ NSString *BlogPostDeletedNotification = @"BlogPostDeletedNotification";
} }
} }
- (PMKPromise *)requestCreateDraftWithID:(NSString *)draftID title:(NSString *)title body:(NSString *)body link:(NSString *)link { - (PMKPromise *)requestCreateDraft:(Post *)draft {
Post *post = [[Post alloc] initWithDictionary:@{@"objectID": draftID ?: [NSNull null], return [_service requestCreateDraftWithID:draft.objectID title:draft.title body:draft.body
@"title": title ?: [NSNull null], link:draft.url.absoluteString].then(^(Post *post) {
@"body": body ?: [NSNull null],
@"link": link ?: [NSNull null],
@"draft": @YES,
} error:nil];
return [_service requestCreateDraftWithID:draftID title:title body:body link:link].then(^(Post *post) {
[_store addDraft:post]; [_store addDraft:post];
return post; return post;
}); });
} }
- (PMKPromise *)requestUpdatePostWithPath:(NSString *)path title:(NSString *)title body:(NSString *)body link:(NSString *)link { - (PMKPromise *)requestUpdatePost:(Post *)post {
return [_service requestUpdatePostWithPath:path title:title body:body link:link].then(^(Post *post) { return [_service requestUpdatePostWithPath:post.path title:post.title body:post.body link:post.url.absoluteString]
[_store savePost:post]; .then(^(Post *post) {
return post; [_store savePost:post];
}); return post;
});
} }
- (PMKPromise *)requestPublishDraftWithPath:(NSString *)path { - (PMKPromise *)requestPublishDraftWithPath:(NSString *)path {

View file

@ -92,7 +92,7 @@ NSString *const BlogServiceErrorDomain = @"BlogServiceErrorDomain";
- (PMKPromise *)requestCreateDraftWithID:(NSString *)draftID title:(NSString *)title body:(NSString *)body link:(NSString *)link { - (PMKPromise *)requestCreateDraftWithID:(NSString *)draftID title:(NSString *)title body:(NSString *)body link:(NSString *)link {
NSDictionary *fields = @{ NSDictionary *fields = @{
@"id" : draftID, @"id" : draftID,
@"title" : title, @"title" : title ?: [NSNull null],
@"body" : body, @"body" : body,
@"link" : link ?: [NSNull null], @"link" : link ?: [NSNull null],
}; };
@ -109,7 +109,7 @@ NSString *const BlogServiceErrorDomain = @"BlogServiceErrorDomain";
- (PMKPromise *)requestUpdatePostWithPath:(NSString *)path title:(NSString *)title body:(NSString *)body link:(NSString *)link { - (PMKPromise *)requestUpdatePostWithPath:(NSString *)path title:(NSString *)title body:(NSString *)body link:(NSString *)link {
NSDictionary *fields = @{ NSDictionary *fields = @{
@"title" : title, @"title" : title ?: [NSNull null],
@"body" : body, @"body" : body,
@"link" : link ?: [NSNull null], @"link" : link ?: [NSNull null],
}; };

View file

@ -15,5 +15,6 @@
@property (strong, nonatomic) BlogController *blogController; @property (strong, nonatomic) BlogController *blogController;
@property (strong, nonatomic) Post *post; @property (strong, nonatomic) Post *post;
@property (copy, nonatomic) void (^postUpdatedBlock)(Post *post);
@end @end

View file

@ -36,7 +36,7 @@
CGPoint scrollOffset = CGPointZero; CGPoint scrollOffset = CGPointZero;
if (self.post) { if (self.post) {
// FIXME: date, status (draft, published) // FIXME: date, status (draft, published)
title = self.post.title ?: @"Untitled"; title = self.post.title.length ? self.post.title : @"Untitled";
text = self.post.body; text = self.post.body;
// TODO: restore scroll offset for this post ... user defaults? // TODO: restore scroll offset for this post ... user defaults?
} }
@ -67,20 +67,44 @@
} }
- (PMKPromise *)savePostBody { - (PMKPromise *)savePostBody {
if (!self.post || !self.textView) { return [PMKPromise promiseWithValue:nil]; } NSString *body = self.textView.text;
if (!self.post || !body.length) {
Post *newPost = [self.post copyWithBody:self.textView.text]; return [PMKPromise promiseWithValue:nil];
if (![self.post isEqual:newPost])
{
self.post = newPost;
return [self.blogController requestUpdatePostWithPath:self.post.path title:self.post.title body:self.post.body link:self.post.url.absoluteString]
.then(^(Post *post) {
NSLog(@"saved post at path %@", self.post.path);
}).catch(^(NSError *error) {
NSLog(@"Error saving post at path %@: %@ %@", self.post.path, error.localizedDescription, error.userInfo);
});
} }
return [PMKPromise promiseWithValue:self.post];
Post *newPost = [self.post copyWithBody:body];
if ([newPost isEqual:self.post]) {
return [PMKPromise promiseWithValue:self.post];
}
self.post = newPost;
NSString *path = self.post.path;
PMKPromise *savePromise;
NSString *verb;
if (self.post.new) {
verb = @"create";
savePromise = [self.blogController requestCreateDraft:self.post];
}
else {
verb = @"update";
savePromise = [self.blogController requestUpdatePost:self.post];
}
return savePromise.then(^(Post *post) {
NSLog(@"%@ post at path %@", verb, path);
// TODO: something better than this
// update our post because "new" may have changed, which is essential to correct operation
self.post = post;
[self configureView];
if (self.postUpdatedBlock) {
self.postUpdatedBlock(self.post);
}
return post;
}).catch(^(NSError *error) {
NSLog(@"Falied to %@ post at path %@: %@ %@", verb, path, error.localizedDescription, error.userInfo);
return error;
});
} }
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

View file

@ -22,8 +22,10 @@
@property (nonatomic, readonly, strong) NSURL *url; @property (nonatomic, readonly, strong) NSURL *url;
@property (nonatomic, readonly, getter=isDraft) BOOL draft; @property (nonatomic, readonly, getter=isDraft) BOOL draft;
@property (nonatomic, readonly, getter=isLink) BOOL link; @property (nonatomic, readonly, getter=isLink) BOOL link;
@property (nonatomic, readonly, getter=isNew) BOOL new;
@property (nonatomic, readonly) NSString *formattedDate; @property (nonatomic, readonly) NSString *formattedDate;
+ (instancetype)newDraftWithTitle:(NSString *)title body:(NSString *)body url:(NSURL *)url;
- (instancetype)copyWithBody:(NSString *)body; - (instancetype)copyWithBody:(NSString *)body;
- (instancetype)copyWithTitle:(NSString *)title; - (instancetype)copyWithTitle:(NSString *)title;
- (instancetype)copyWithURL:(NSURL *)url; - (instancetype)copyWithURL:(NSURL *)url;

View file

@ -41,56 +41,56 @@
}]; }];
} }
+ (instancetype)newDraftWithTitle:(NSString *)title body:(NSString *)body url:(NSURL *)url {
NSDictionary *fields = @{
@"new" : @(YES),
@"draft" : @(YES),
@"title" : title ?: @"",
@"body" : body ?: @"",
@"url" : url ?: [NSNull null],
};
return [[self alloc] initWithDictionary:fields error:nil];
}
- (id)copyWithZone:(NSZone *)zone { - (id)copyWithZone:(NSZone *)zone {
return self; return self;
} }
- (instancetype)copyWithBody:(NSString *)body { - (instancetype)copyWithBody:(NSString *)body {
return [[Post alloc] initWithDictionary:@{@"objectID": self.objectID ?: [NSNull null], return [self copyWithTitle:self.title body:body url:self.url];
@"slug": self.slug ?: [NSNull null],
@"author": self.author ?: [NSNull null],
@"title": self.title ?: [NSNull null],
@"date": self.date ?: [NSNull null],
@"body": body ?: [NSNull null],
@"path": self.path ?: [NSNull null],
@"url": self.url ?: [NSNull null],
@"draft": @(self.draft),
} error:nil];
} }
- (instancetype)copyWithTitle:(NSString *)title { - (instancetype)copyWithTitle:(NSString *)title {
return [[Post alloc] initWithDictionary:@{@"objectID": self.objectID ?: [NSNull null], return [self copyWithTitle:title body:self.body url:self.url];
@"slug": self.slug ?: [NSNull null],
@"author": self.author ?: [NSNull null],
@"title": title ?: [NSNull null],
@"date": self.date ?: [NSNull null],
@"body": self.body ?: [NSNull null],
@"path": self.path ?: [NSNull null],
@"url": self.url ?: [NSNull null],
@"draft": @(self.draft),
} error:nil];
} }
- (instancetype)copyWithURL:(NSURL *)url { - (instancetype)copyWithURL:(NSURL *)url {
return [[Post alloc] initWithDictionary:@{@"objectID": self.objectID ?: [NSNull null], return [self copyWithTitle:self.title body:self.body url:url];
@"slug": self.slug ?: [NSNull null], }
@"author": self.author ?: [NSNull null],
@"title": self.title ?: [NSNull null], - (instancetype)copyWithTitle:(NSString *)title body:(NSString *)body url:(NSURL *)url {
@"date": self.date ?: [NSNull null], return [[Post alloc] initWithDictionary:@{
@"body": self.body ?: [NSNull null], @"objectID" : self.objectID ?: [NSNull null],
@"path": self.path ?: [NSNull null], @"slug" : self.slug ?: [NSNull null],
@"url": url ?: [NSNull null], @"author" : self.author ?: [NSNull null],
@"draft": @(self.draft), @"title" : title ?: [NSNull null],
} error:nil]; @"date" : self.date ?: [NSNull null],
@"body" : body ?: [NSNull null],
@"path" : self.path ?: [NSNull null],
@"url" : url ?: [NSNull null],
@"draft" : @(self.draft),
@"new" : @(self.new),
} error:nil];
} }
- (BOOL)isEqualToPost:(Post *)other { - (BOOL)isEqualToPost:(Post *)other {
return [self.objectID isEqualToString:other.objectID] return [self.objectID isEqualToString:other.objectID]
&& [self.path isEqualToString:other.path] && [self.path isEqualToString:other.path]
&& [self.title isEqualToString:other.title] && ((!self.title && !other.title) || [self.title isEqual:other.title])
&& [self.body isEqualToString:other.body] && ((!self.body && !other.body) || [self.body isEqual:other.body])
&& self.draft == other.draft && self.draft == other.draft
&& ((!self.url && !other.url) || [self.url isEqual:other.url]); && ((!self.url && !other.url) || [self.url isEqual:other.url]);
// include "new" here too?
} }
- (BOOL)isEqual:(id)object { - (BOOL)isEqual:(id)object {
@ -154,7 +154,7 @@
} }
else { else {
NSAssert(self.slug && self.date, @"slug and date are required"); NSAssert(self.slug && self.date, @"slug and date are required");
NSString *paddedMonth = [self paddedMonthForDate:self.date]; NSString *paddedMonth = [self paddedMonthForDate:self.time];
_path = [NSString stringWithFormat:@"/posts/%ld/%@/%@", (long)self.time.mm_year, paddedMonth, self.slug]; _path = [NSString stringWithFormat:@"/posts/%ld/%@/%@", (long)self.time.mm_year, paddedMonth, self.slug];
} }
} }

View file

@ -18,7 +18,6 @@
@interface PostsViewController () @interface PostsViewController ()
@property (strong, nonatomic) NSMutableArray *posts; @property (strong, nonatomic) NSMutableArray *posts;
@property (strong, nonatomic) EditorViewController *editorViewController;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *publishButton; @property (strong, nonatomic) IBOutlet UIBarButtonItem *publishButton;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *addButton; @property (strong, nonatomic) IBOutlet UIBarButtonItem *addButton;
@property (weak, nonatomic) UILabel *titleLabel; @property (weak, nonatomic) UILabel *titleLabel;
@ -72,8 +71,8 @@
}]; }];
} }
self.blogStatusTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(updateBlogStatus) userInfo:nil repeats:YES];
- (void)setupBlogStatusTimer { - (void)setupBlogStatusTimer {
self.blogStatusTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(updateBlogStatus) userInfo:nil repeats:YES];
} }
- (void)teardownBlogStatusTimer { - (void)teardownBlogStatusTimer {
@ -84,7 +83,6 @@
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
UINavigationController *detailNavController = self.splitViewController.viewControllers.lastObject; UINavigationController *detailNavController = self.splitViewController.viewControllers.lastObject;
self.editorViewController = (EditorViewController *)detailNavController.topViewController;
} }
- (void)updateStatusLabel:(NSString *)blogStatus { - (void)updateStatusLabel:(NSString *)blogStatus {
@ -98,8 +96,7 @@
} }
- (void)updateBlogStatus { - (void)updateBlogStatus {
[self updateStatusLabel:[NSString stringWithFormat:@"%@ as of %@", self.blogStatusText, [self updateStatusLabel:[NSString stringWithFormat:@"%@ as of %@", self.blogStatusText, [self.blogStatusDate mm_relativeToNow]]];
[self.blogStatusDate mm_relativeToNow]]];
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
@ -161,10 +158,15 @@
} }
- (IBAction)insertNewObject:(id)sender { - (IBAction)insertNewObject:(id)sender {
Post *post = [[Post alloc] initWithDictionary:@{@"draft": @(YES)} error:nil]; NSString *title = [UIPasteboard generalPasteboard].string;
NSURL *url = [UIPasteboard generalPasteboard].URL;
// TODO: image, anything else interesting
Post *post = [Post newDraftWithTitle:title body:nil url:url];
[self.posts insertObject:post atIndex:0]; [self.posts insertObject:post atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
[self performSegueWithIdentifier:@"showDetail" sender:sender];
} }
- (IBAction)publish:(id)sender { - (IBAction)publish:(id)sender {
@ -182,6 +184,16 @@
[controller setPost:post]; [controller setPost:post];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES; controller.navigationItem.leftItemsSupplementBackButton = YES;
controller.postUpdatedBlock = ^(Post *post) {
NSUInteger row = [self.posts indexOfObjectPassingTest:^BOOL(Post *p, NSUInteger idx, BOOL *stop) {
return [p.objectID isEqualToString:post.objectID];
}];
if (row != NSNotFound) {
[self.posts replaceObjectAtIndex:row withObject:post];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
};
} }
} }
@ -200,7 +212,7 @@
Post *post = self.posts[indexPath.row]; Post *post = self.posts[indexPath.row];
// FIXME: unique title // FIXME: unique title
NSString *title = post.title ?: @"Untitled"; NSString *title = post.title.length ? post.title : @"Untitled";
NSString *date = post.draft ? @"Draft" : post.formattedDate; NSString *date = post.draft ? @"Draft" : post.formattedDate;
[cell configureWithTitle:title date:date]; [cell configureWithTitle:title date:date];
return cell; return cell;