Skip to content

Commit

Permalink
Merge pull request #12947 from guarani/issue/12141-restore-autosave
Browse files Browse the repository at this point in the history
Restore autosave revision dialog
  • Loading branch information
shiki authored Dec 2, 2019
2 parents 8868628 + ff57ba6 commit 4408ad7
Show file tree
Hide file tree
Showing 20 changed files with 1,015 additions and 23 deletions.
7 changes: 7 additions & 0 deletions MIGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
This file documents changes in the data model. Please explain any changes to the
data model as well as any custom migrations.


## WordPress 94

@guarani 2019-11-28

- `AbstractPost` added `autosaveIdentifier` (`nullable` `NSNumber`) property.

## WordPress 93

@guarani 2019-10-27
Expand Down
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
13.8
-----
* When a post has an autosave, the autosave version can be loaded into the editor.
* Support: Fix issue that caused 'Message failed to send' error.
* WebView: Fix iOS 13 crash with popover.
* Fixed an issue where the Me screen would sometimes be blank.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ extension AbstractPost: ImageSourceInformation {
var isLocalDraft: Bool {
return self.isDraft() && !self.hasRemote()
}

/// An autosave revision may include post title, content and/or excerpt.
var hasAutosaveRevision: Bool {
guard let autosaveRevisionIdentifier = autosaveIdentifier?.intValue else {
return false
}
return autosaveRevisionIdentifier > 0
}
}
1 change: 1 addition & 0 deletions WordPress/Classes/Models/AbstractPost.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ typedef NS_ENUM(NSUInteger, AbstractPostRemoteStatus) {
@property (nonatomic, copy, nullable) NSString *autosaveExcerpt;
@property (nonatomic, copy, nullable) NSString *autosaveTitle;
@property (nonatomic, copy, nullable) NSDate *autosaveModifiedDate;
@property (nonatomic, copy, nullable) NSNumber *autosaveIdentifier;

// Revision management
- (AbstractPost *)createRevision;
Expand Down
1 change: 1 addition & 0 deletions WordPress/Classes/Models/AbstractPost.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ @implementation AbstractPost
@dynamic autosaveExcerpt;
@dynamic autosaveTitle;
@dynamic autosaveModifiedDate;
@dynamic autosaveIdentifier;

@synthesize restorableStatus;

Expand Down
1 change: 1 addition & 0 deletions WordPress/Classes/Services/PostService.m
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ - (void)updatePost:(AbstractPost *)post withRemotePost:(RemotePost *)remotePost
post.autosaveExcerpt = remotePost.autosave.excerpt;
post.autosaveContent = remotePost.autosave.content;
post.autosaveModifiedDate = remotePost.autosave.modifiedDate;
post.autosaveIdentifier = remotePost.autosave.identifier;

if ([post isKindOfClass:[Page class]]) {
Page *pagePost = (Page *)post;
Expand Down
10 changes: 5 additions & 5 deletions WordPress/Classes/Utility/Editor/EditorFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ class EditorFactory {

// MARK: - Editor: Instantiation

func instantiateEditor(for post: AbstractPost, replaceEditor: @escaping ReplaceEditorBlock) -> EditorViewController {
func instantiateEditor(for post: AbstractPost, loadAutosaveRevision: Bool = false, replaceEditor: @escaping ReplaceEditorBlock) -> EditorViewController {
if gutenbergSettings.mustUseGutenberg(for: post) {
return createGutenbergVC(with: post, replaceEditor: replaceEditor)
return createGutenbergVC(with: post, loadAutosaveRevision: loadAutosaveRevision, replaceEditor: replaceEditor)
} else {
return AztecPostViewController(post: post, replaceEditor: replaceEditor)
return AztecPostViewController(post: post, loadAutosaveRevision: loadAutosaveRevision, replaceEditor: replaceEditor)
}
}

private func createGutenbergVC(with post: AbstractPost, replaceEditor: @escaping ReplaceEditorBlock) -> GutenbergViewController {
let gutenbergVC = GutenbergViewController(post: post, replaceEditor: replaceEditor)
private func createGutenbergVC(with post: AbstractPost, loadAutosaveRevision: Bool, replaceEditor: @escaping ReplaceEditorBlock) -> GutenbergViewController {
let gutenbergVC = GutenbergViewController(post: post, loadAutosaveRevision: loadAutosaveRevision, replaceEditor: replaceEditor)

if gutenbergSettings.shouldAutoenableGutenberg(for: post) {
gutenbergSettings.setGutenbergEnabled(true, for: post.blog, source: .onBlockPostOpening)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ class AztecPostViewController: UIViewController, PostEditor {
}
}

/// If true, apply autosave content when the editor creates a revision.
///
private let loadAutosaveRevision: Bool

/// Active Downloads
///
fileprivate var activeMediaRequests = [ImageDownloader.Task]()
Expand Down Expand Up @@ -434,20 +438,16 @@ class AztecPostViewController: UIViewController, PostEditor {

// MARK: - Initializers

/// Initializer
///
/// - Parameters:
/// - post: the post to edit in this VC. Must be already assigned to a `ManagedObjectContext`
/// since that's necessary for the edits to be saved.
///
required init(
post: AbstractPost,
loadAutosaveRevision: Bool = false,
replaceEditor: @escaping (EditorViewController, EditorViewController) -> (),
editorSession: PostEditorAnalyticsSession? = nil) {

precondition(post.managedObjectContext != nil)

self.post = post
self.loadAutosaveRevision = loadAutosaveRevision
self.replaceEditor = replaceEditor
self.editorSession = editorSession ?? PostEditorAnalyticsSession(editor: .classic, post: post)

Expand Down Expand Up @@ -485,7 +485,7 @@ class AztecPostViewController: UIViewController, PostEditor {
WPFontManager.loadNotoFontFamily()

registerAttachmentImageProviders()
createRevisionOfPost()
createRevisionOfPost(loadAutosaveRevision: loadAutosaveRevision)

// Setup
configureNavigationBar()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ class GutenbergViewController: UIViewController, PostEditor {
}
}

/// If true, apply autosave content when the editor creates a revision.
///
private let loadAutosaveRevision: Bool

let navigationBarManager = PostEditorNavigationBarManager()

lazy var attachmentDelegate = AztecAttachmentDelegate(post: post)
Expand Down Expand Up @@ -218,10 +222,12 @@ class GutenbergViewController: UIViewController, PostEditor {
// MARK: - Initializers
required init(
post: AbstractPost,
loadAutosaveRevision: Bool = false,
replaceEditor: @escaping (EditorViewController, EditorViewController) -> (),
editorSession: PostEditorAnalyticsSession? = nil) {

self.post = post
self.loadAutosaveRevision = loadAutosaveRevision

self.replaceEditor = replaceEditor
verificationPromptHelper = AztecVerificationPromptHelper(account: self.post.blog.account)
Expand Down Expand Up @@ -249,8 +255,8 @@ class GutenbergViewController: UIViewController, PostEditor {

override func viewDidLoad() {
super.viewDidLoad()
createRevisionOfPost(loadAutosaveRevision: loadAutosaveRevision)
setupGutenbergView()
createRevisionOfPost()
configureNavigationBar()
refreshInterface()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class EditPostViewController: UIViewController {
@objc var openWithPostPost: Bool = false
/// appear with media pre-inserted into the post
var insertedMedia: [Media]? = nil
private let loadAutosaveRevision: Bool

@objc fileprivate(set) var post: Post?
fileprivate var hasShownEditor = false
Expand Down Expand Up @@ -42,8 +43,8 @@ class EditPostViewController: UIViewController {
/// Initialize as an editor with the provided post
///
/// - Parameter post: post to edit
@objc convenience init(post: Post) {
self.init(post: post, blog: post.blog)
@objc convenience init(post: Post, loadAutosaveRevision: Bool = false) {
self.init(post: post, blog: post.blog, loadAutosaveRevision: loadAutosaveRevision)
}


Expand All @@ -60,8 +61,9 @@ class EditPostViewController: UIViewController {
/// - post: the post to edit
/// - blog: the blog to create a post for, if post is nil
/// - Note: it's preferable to use one of the convenience initializers
fileprivate init(post: Post?, blog: Blog) {
fileprivate init(post: Post?, blog: Blog, loadAutosaveRevision: Bool = false) {
self.post = post
self.loadAutosaveRevision = loadAutosaveRevision
if let post = post {
if !post.originalIsDraft() {
editingExistingPost = true
Expand Down Expand Up @@ -125,6 +127,7 @@ class EditPostViewController: UIViewController {
fileprivate func showEditor() {
let editor = editorFactory.instantiateEditor(
for: postToEdit(),
loadAutosaveRevision: loadAutosaveRevision,
replaceEditor: { [weak self] (editor, replacement) in
self?.replaceEditor(editor: editor, replacement: replacement)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class PostCardStatusViewModel: NSObject {
return generateFailedStatusMessage()
} else if post.remoteStatus == .pushing {
return NSLocalizedString("Uploading post...", comment: "Message displayed on a post's card when the post has failed to upload")
} else if !post.hasLocalChanges() && post.hasAutosaveRevision {
return StatusMessages.hasUnsavedChanges
} else {
return post.statusForDisplay()
}
Expand Down Expand Up @@ -98,6 +100,10 @@ class PostCardStatusViewModel: NSObject {
return (autoUploadAction == .upload || post.wasAutoUploadCancelled) ? .warning : .error
}

if post.hasAutosaveRevision {
return .warning(.shade40)
}

switch status {
case .pending:
return .success
Expand Down Expand Up @@ -262,5 +268,7 @@ class PostCardStatusViewModel: NSObject {
comment: "Message displayed on a post's card when the post has failed to upload")
static let localChanges = NSLocalizedString("Local changes",
comment: "A status label for a post that only exists on the user's iOS device, and has not yet been published to their blog.")
static let hasUnsavedChanges = NSLocalizedString("You've made unsaved changes to this post",
comment: "Message displayed on a post's card when the post has unsaved changes")
}
}
9 changes: 8 additions & 1 deletion WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ extension PostEditor where Self: UIViewController {
}

// TODO: Rip this out and put it into the PostService
func createRevisionOfPost() {
func createRevisionOfPost(loadAutosaveRevision: Bool = false) {

if post.isLocalRevision, post.original?.postTitle == nil, post.original?.content == nil {
// Editing a locally made revision has bit of weirdness in how autosave and
Expand Down Expand Up @@ -440,6 +440,13 @@ extension PostEditor where Self: UIViewController {

managedObjectContext.performAndWait {
post = self.post.createRevision()

if loadAutosaveRevision {
post.postTitle = post.autosaveTitle
post.mt_excerpt = post.autosaveExcerpt
post.content = post.autosaveContent
}

ContextManager.sharedInstance().save(managedObjectContext)
}
}
Expand Down
14 changes: 14 additions & 0 deletions WordPress/Classes/ViewRelated/Post/PostEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ protocol PostEditor: class, UIViewControllerTransitioningDelegate {
///
var isOpenedDirectlyForPhotoPost: Bool { get set }

/// Initializer
///
/// - Parameters:
/// - post: the post to edit. Must be already assigned to a `ManagedObjectContext` since
/// that's necessary for the edits to be saved.
/// - loadAutosaveRevision: if true, apply autosave content when the editor creates a revision.
/// - replaceEditor: a closure that handles switching from one editor to another
/// - editorSession: post editor analytics session
init(
post: AbstractPost,
loadAutosaveRevision: Bool,
replaceEditor: @escaping (EditorViewController, EditorViewController) -> (),
editorSession: PostEditorAnalyticsSession?)

/// Media items to be inserted on the post after creation
///
/// - Parameter media: the media items to add
Expand Down
69 changes: 69 additions & 0 deletions WordPress/Classes/ViewRelated/Post/PostListEditorPresenter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Foundation

/// Handle a user tapping a post in the post list. If an autosave revision is available, give the
/// user the option through a dialog alert to load the autosave (or just load the regular post) into
/// the editor.
/// Analytics are also tracked.
struct PostListEditorPresenter {

static func handle(post: Post, in postListViewController: PostListViewController) {

// Autosaves are ignored for posts with local changes.
if !post.hasLocalChanges(), post.hasAutosaveRevision, let saveDate = post.dateModified, let autosaveDate = post.autosaveModifiedDate {
let autosaveViewController = autosaveOptionsViewController(forSaveDate: saveDate, autosaveDate: autosaveDate, didTapOption: { loadAutosaveRevision in
openEditor(with: post, loadAutosaveRevision: loadAutosaveRevision, in: postListViewController)
})
postListViewController.present(autosaveViewController, animated: true)
} else {
openEditor(with: post, loadAutosaveRevision: false, in: postListViewController)
}
}

private static func openEditor(with post: Post, loadAutosaveRevision: Bool, in postListViewController: PostListViewController) {
let editor = EditPostViewController(post: post, loadAutosaveRevision: loadAutosaveRevision)
editor.modalPresentationStyle = .fullScreen
postListViewController.present(editor, animated: false)
WPAppAnalytics.track(.postListEditAction, withProperties: postListViewController.propertiesForAnalytics(), with: post)
}

private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()

private static let timeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
return formatter
}()

private static func dateAndTime(for date: Date) -> String {
return dateFormatter.string(from: date) + " @ " + timeFormatter.string(from: date)
}

/// A dialog giving the user the choice between loading the current version a post or its autosaved version.
private static func autosaveOptionsViewController(forSaveDate saveDate: Date, autosaveDate: Date, didTapOption: @escaping (_ loadAutosaveRevision: Bool) -> Void) -> UIAlertController {

let title = NSLocalizedString("Which version would you like to edit?", comment: "Title displayed in popup when user has the option to load unsaved changes")

let saveDateFormatted = dateAndTime(for: saveDate)
let autosaveDateFormatted = dateAndTime(for: autosaveDate)
let message = String(format: NSLocalizedString("You recently made changes to this post but didn't save them. Choose a version to load:\n\nFrom this device\nSaved on %@\n\nFrom another device\nSaved on %@\n", comment: "Message displayed in popup when user has the option to load unsaved changes. \n is a placeholder for a new line, and the two %@ are placeholders for the date of last save on this device, and date of last autosave on another device, respectively."), saveDateFormatted, autosaveDateFormatted)

let loadSaveButtonTitle = NSLocalizedString("From this device", comment: "Button title displayed in popup indicating date of change on device")
let fromAutosaveButtonTitle = NSLocalizedString("From another device", comment: "Button title displayed in popup indicating date of change on another device")

let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: loadSaveButtonTitle, style: .default) { _ in
didTapOption(false)
})
alertController.addAction(UIAlertAction(title: fromAutosaveButtonTitle, style: .default) { _ in
didTapOption(true)
})

return alertController
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -518,10 +518,8 @@ class PostListViewController: AbstractPostListViewController, UIViewControllerRe
presentAlertForPostBeingUploaded()
return
}
let editor = EditPostViewController(post: post)
editor.modalPresentationStyle = .fullScreen
present(editor, animated: false)
WPAppAnalytics.track(.postListEditAction, withProperties: propertiesForAnalytics(), with: apost)

PostListEditorPresenter.handle(post: post, in: self)
}

func presentAlertForPostBeingUploaded() {
Expand Down
2 changes: 1 addition & 1 deletion WordPress/Classes/WordPress.xcdatamodeld/.xccurrentversion
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>WordPress 93.xcdatamodel</string>
<string>WordPress 94.xcdatamodel</string>
</dict>
</plist>
Loading

0 comments on commit 4408ad7

Please sign in to comment.