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
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
UINavigationController *navigationController = [secondaryViewController isKindOfClass:[UINavigationController class]] ? (UINavigationController *)secondaryViewController : nil;
if ([navigationController.topViewController isKindOfClass:[EditorViewController class]] && ([(EditorViewController *)navigationController.topViewController post] == nil)) {
- (BOOL) splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
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;
}

View file

@ -21,6 +21,7 @@ extern NSString *BlogPostDeletedNotification;
@class PMKPromise;
@class ModelStore;
@class BlogService;
@class Post;
@interface BlogController : NSObject
@ -36,8 +37,8 @@ extern NSString *BlogPostDeletedNotification;
- (PMKPromise *)requestAllPostsWithCaching:(BOOL)useCache;
- (PMKPromise *)requestPostWithPath:(NSString *)path;
- (PMKPromise *)requestCreateDraftWithID:(NSString *)draftID title:(NSString *)title body:(NSString *)body link:(NSString *)link;
- (PMKPromise *)requestUpdatePostWithPath:(NSString *)path title:(NSString *)title body:(NSString *)body link:(NSString *)link;
- (PMKPromise *)requestCreateDraft:(Post *)draft;
- (PMKPromise *)requestUpdatePost:(Post *)post;
- (PMKPromise *)requestPublishDraftWithPath:(NSString *)path;
- (PMKPromise *)requestUnpublishPostWithPath:(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 {
Post *post = [[Post alloc] initWithDictionary:@{@"objectID": draftID ?: [NSNull null],
@"title": title ?: [NSNull null],
@"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) {
- (PMKPromise *)requestCreateDraft:(Post *)draft {
return [_service requestCreateDraftWithID:draft.objectID title:draft.title body:draft.body
link:draft.url.absoluteString].then(^(Post *post) {
[_store addDraft:post];
return post;
});
}
- (PMKPromise *)requestUpdatePostWithPath:(NSString *)path title:(NSString *)title body:(NSString *)body link:(NSString *)link {
return [_service requestUpdatePostWithPath:path title:title body:body link:link].then(^(Post *post) {
[_store savePost:post];
return post;
});
- (PMKPromise *)requestUpdatePost:(Post *)post {
return [_service requestUpdatePostWithPath:post.path title:post.title body:post.body link:post.url.absoluteString]
.then(^(Post *post) {
[_store savePost:post];
return post;
});
}
- (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 {
NSDictionary *fields = @{
@"id" : draftID,
@"title" : title,
@"title" : title ?: [NSNull null],
@"body" : body,
@"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 {
NSDictionary *fields = @{
@"title" : title,
@"title" : title ?: [NSNull null],
@"body" : body,
@"link" : link ?: [NSNull null],
};

View file

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

View file

@ -36,7 +36,7 @@
CGPoint scrollOffset = CGPointZero;
if (self.post) {
// FIXME: date, status (draft, published)
title = self.post.title ?: @"Untitled";
title = self.post.title.length ? self.post.title : @"Untitled";
text = self.post.body;
// TODO: restore scroll offset for this post ... user defaults?
}
@ -67,20 +67,44 @@
}
- (PMKPromise *)savePostBody {
if (!self.post || !self.textView) { return [PMKPromise promiseWithValue:nil]; }
Post *newPost = [self.post copyWithBody:self.textView.text];
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);
});
NSString *body = self.textView.text;
if (!self.post || !body.length) {
return [PMKPromise promiseWithValue:nil];
}
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 {

View file

@ -22,8 +22,10 @@
@property (nonatomic, readonly, strong) NSURL *url;
@property (nonatomic, readonly, getter=isDraft) BOOL draft;
@property (nonatomic, readonly, getter=isLink) BOOL link;
@property (nonatomic, readonly, getter=isNew) BOOL new;
@property (nonatomic, readonly) NSString *formattedDate;
+ (instancetype)newDraftWithTitle:(NSString *)title body:(NSString *)body url:(NSURL *)url;
- (instancetype)copyWithBody:(NSString *)body;
- (instancetype)copyWithTitle:(NSString *)title;
- (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 {
return self;
}
- (instancetype)copyWithBody:(NSString *)body {
return [[Post alloc] initWithDictionary:@{@"objectID": self.objectID ?: [NSNull null],
@"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];
return [self copyWithTitle:self.title body:body url:self.url];
}
- (instancetype)copyWithTitle:(NSString *)title {
return [[Post alloc] initWithDictionary:@{@"objectID": self.objectID ?: [NSNull null],
@"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];
return [self copyWithTitle:title body:self.body url:self.url];
}
- (instancetype)copyWithURL:(NSURL *)url {
return [[Post alloc] initWithDictionary:@{@"objectID": self.objectID ?: [NSNull null],
@"slug": self.slug ?: [NSNull null],
@"author": self.author ?: [NSNull null],
@"title": self.title ?: [NSNull null],
@"date": self.date ?: [NSNull null],
@"body": self.body ?: [NSNull null],
@"path": self.path ?: [NSNull null],
@"url": url ?: [NSNull null],
@"draft": @(self.draft),
} error:nil];
return [self copyWithTitle:self.title body:self.body url:url];
}
- (instancetype)copyWithTitle:(NSString *)title body:(NSString *)body url:(NSURL *)url {
return [[Post alloc] initWithDictionary:@{
@"objectID" : self.objectID ?: [NSNull null],
@"slug" : self.slug ?: [NSNull null],
@"author" : self.author ?: [NSNull null],
@"title" : title ?: [NSNull null],
@"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 {
return [self.objectID isEqualToString:other.objectID]
&& [self.path isEqualToString:other.path]
&& [self.title isEqualToString:other.title]
&& [self.body isEqualToString:other.body]
&& ((!self.title && !other.title) || [self.title isEqual:other.title])
&& ((!self.body && !other.body) || [self.body isEqual:other.body])
&& self.draft == other.draft
&& ((!self.url && !other.url) || [self.url isEqual:other.url]);
// include "new" here too?
}
- (BOOL)isEqual:(id)object {
@ -154,7 +154,7 @@
}
else {
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];
}
}

View file

@ -18,7 +18,6 @@
@interface PostsViewController ()
@property (strong, nonatomic) NSMutableArray *posts;
@property (strong, nonatomic) EditorViewController *editorViewController;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *publishButton;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *addButton;
@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 {
self.blogStatusTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(updateBlogStatus) userInfo:nil repeats:YES];
}
- (void)teardownBlogStatusTimer {
@ -84,7 +83,6 @@
- (void)viewDidLoad {
[super viewDidLoad];
UINavigationController *detailNavController = self.splitViewController.viewControllers.lastObject;
self.editorViewController = (EditorViewController *)detailNavController.topViewController;
}
- (void)updateStatusLabel:(NSString *)blogStatus {
@ -98,8 +96,7 @@
}
- (void)updateBlogStatus {
[self updateStatusLabel:[NSString stringWithFormat:@"%@ as of %@", self.blogStatusText,
[self.blogStatusDate mm_relativeToNow]]];
[self updateStatusLabel:[NSString stringWithFormat:@"%@ as of %@", self.blogStatusText, [self.blogStatusDate mm_relativeToNow]]];
}
- (void)viewWillAppear:(BOOL)animated {
@ -161,10 +158,15 @@
}
- (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];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
[self performSegueWithIdentifier:@"showDetail" sender:sender];
}
- (IBAction)publish:(id)sender {
@ -182,6 +184,16 @@
[controller setPost:post];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
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];
// FIXME: unique title
NSString *title = post.title ?: @"Untitled";
NSString *title = post.title.length ? post.title : @"Untitled";
NSString *date = post.draft ? @"Draft" : post.formattedDate;
[cell configureWithTitle:title date:date];
return cell;