mirror of
https://github.com/samsonjs/samhuri.net-ios.git
synced 2026-04-14 12:46:02 +00:00
implement creating new drafts
This commit is contained in:
parent
c702143073
commit
ea426550f3
9 changed files with 115 additions and 74 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,5 +15,6 @@
|
|||
|
||||
@property (strong, nonatomic) BlogController *blogController;
|
||||
@property (strong, nonatomic) Post *post;
|
||||
@property (copy, nonatomic) void (^postUpdatedBlock)(Post *post);
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
66
Blog/Post.m
66
Blog/Post.m
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue