From 73d219d0ec91fc40de3cbacaff80727cb31d50ea Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Sun, 23 Jul 2023 07:19:52 -0400 Subject: [PATCH] Handle undo of edited posts (#251) --- CHANGELOG.md | 2 ++ .../WriteFreelyModel+APIHandlers.swift | 2 +- Shared/PostEditor/PostEditorModel.swift | 18 ++++++++++++++++++ .../project.pbxproj | 8 ++++---- iOS/PostEditor/PostEditorView.swift | 14 +++++++++++--- iOS/PostEditor/PostTextEditingView.swift | 11 +++++++++-- macOS/Navigation/ActivePostToolbarView.swift | 12 ++++++++++++ macOS/PostEditor/PostEditorView.swift | 1 + macOS/PostEditor/PostTextEditingView.swift | 12 ++++++++++-- 9 files changed, 68 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e90ce..96ce23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Mac] Added a context-menu item to delete local posts from the post list. - [Mac] Added methods to fetch device logs. - [iOS, Mac] Added a way to search for text across all posts. +- [iOS, Mac] Added a way to refresh an edited post from the server copy. ### Changed @@ -25,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Mac] Updated the URL and minimum version of the WriteFreely Swift package. - [Mac] Upgraded the Sparkle package to v2. - [Mac] The app now prompts you to reach out to our user forums if it detects a crash. +- [iOS, Mac] The app now reverts a post from edited to published status if you undo your changes. ### Fixed diff --git a/Shared/Extensions/WriteFreelyModel+APIHandlers.swift b/Shared/Extensions/WriteFreelyModel+APIHandlers.swift index 5843328..f3876cf 100644 --- a/Shared/Extensions/WriteFreelyModel+APIHandlers.swift +++ b/Shared/Extensions/WriteFreelyModel+APIHandlers.swift @@ -244,6 +244,7 @@ extension WriteFreelyModel { #if os(macOS) self.selectedPost = cachedPost #endif + cachedPost.status = PostStatus.published.rawValue } } catch { self.currentError = AppError.genericError(error.localizedDescription) @@ -280,7 +281,6 @@ extension WriteFreelyModel { cachedPost.postId = fetchedPost.postId cachedPost.rtl = fetchedPost.rtl ?? false cachedPost.slug = fetchedPost.slug - cachedPost.status = PostStatus.published.rawValue cachedPost.title = fetchedPost.title ?? "" cachedPost.updatedDate = fetchedPost.updatedDate } diff --git a/Shared/PostEditor/PostEditorModel.swift b/Shared/PostEditor/PostEditorModel.swift index b8bda48..319beaa 100644 --- a/Shared/PostEditor/PostEditorModel.swift +++ b/Shared/PostEditor/PostEditorModel.swift @@ -12,6 +12,9 @@ struct PostEditorModel { @AppStorage(WFDefaults.selectedCollectionURL, store: UserDefaults.shared) var selectedCollectionURL: URL? @AppStorage(WFDefaults.lastDraftURL, store: UserDefaults.shared) var lastDraftURL: URL? + private(set) var initialPostTitle: String? + private(set) var initialPostBody: String? + #if os(macOS) var postToUpdate: WFAPost? #endif @@ -58,6 +61,21 @@ struct PostEditorModel { return collection } + /// Sets the initial values for title and body on a published post. + /// + /// Used to detect if the title and body have changed back to their initial values. If the passed `WFAPost` isn't + /// published, any title and post values already stored are reset to `nil`. + /// - Parameter post: The `WFAPost` for which we're setting initial title/body values. + mutating func setInitialValues(for post: WFAPost) { + if post.status != PostStatus.published.rawValue { + initialPostTitle = nil + initialPostBody = nil + return + } + initialPostTitle = post.title + initialPostBody = post.body + } + private func fetchManagedObject(from objectURL: URL) -> NSManagedObject? { let coordinator = LocalStorageManager.standard.container.persistentStoreCoordinator guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil } diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj index ea527e2..b6f619d 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj +++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj @@ -1063,7 +1063,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 702; DEVELOPMENT_TEAM = TPPAB4YBA6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; @@ -1094,7 +1094,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 702; DEVELOPMENT_TEAM = TPPAB4YBA6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; @@ -1237,7 +1237,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 702; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; @@ -1263,7 +1263,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 702; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index 4fed230..0a875f3 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -84,7 +84,7 @@ struct PostEditorView: View { }) .accessibilityHint(Text("Choose the blog you want to publish this post to")) .disabled(post.body.count == 0) - } else { + } else if post.status == PostStatus.edited.rawValue { Button(action: { if model.account.isLoggedIn { publishPost() @@ -95,6 +95,14 @@ struct PostEditorView: View { Label("Publish", systemImage: "paperplane") }) .disabled(post.status == PostStatus.published.rawValue || post.body.count == 0) + + Button(action: { + model.updateFromServer(post: post) + }, label: { + Label("Revert", systemImage: "clock.arrow.circlepath") + }) + .accessibilityHint(Text("Replace the edited post with the published version from the server")) + .disabled(post.status != PostStatus.edited.rawValue) } Button(action: { sharePost() @@ -160,6 +168,7 @@ struct PostEditorView: View { }) .onAppear(perform: { self.selectedCollection = collections.first { $0.alias == post.collectionAlias } + model.editor.setInitialValues(for: post) if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { self.model.editor.saveLastDraft(post) @@ -201,9 +210,8 @@ struct PostEditorView: View { LocalStorageManager.standard.saveContext() model.publish(post: post) } - #if os(iOS) + model.editor.setInitialValues(for: post) self.hideKeyboard() - #endif } private func sharePost() { diff --git a/iOS/PostEditor/PostTextEditingView.swift b/iOS/PostEditor/PostTextEditingView.swift index 0482f7d..3bf07b9 100644 --- a/iOS/PostEditor/PostTextEditingView.swift +++ b/iOS/PostEditor/PostTextEditingView.swift @@ -2,6 +2,7 @@ import SwiftUI struct PostTextEditingView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass + @EnvironmentObject var model: WriteFreelyModel @ObservedObject var post: WFAPost @Binding var updatingTitleFromServer: Bool @Binding var updatingBodyFromServer: Bool @@ -35,13 +36,16 @@ struct PostTextEditingView: View { ) .accessibilityLabel(Text("Title (optional)")) .accessibilityHint(Text("Add or edit the title for your post; use the Return key to skip to the body")) - .onChange(of: post.title) { _ in + .onChange(of: post.title) { value in if post.status == PostStatus.published.rawValue && !updatingTitleFromServer { post.status = PostStatus.edited.rawValue } if updatingTitleFromServer { updatingTitleFromServer = false } + if post.status == PostStatus.edited.rawValue && value == model.editor.initialPostTitle { + post.status = PostStatus.published.rawValue + } } MultilineTextField( "Write...", @@ -51,13 +55,16 @@ struct PostTextEditingView: View { ) .accessibilityLabel(Text("Body")) .accessibilityHint(Text("Add or edit the body of your post")) - .onChange(of: post.body) { _ in + .onChange(of: post.body) { value in if post.status == PostStatus.published.rawValue && !updatingBodyFromServer { post.status = PostStatus.edited.rawValue } if updatingBodyFromServer { updatingBodyFromServer = false } + if post.status == PostStatus.edited.rawValue && value == model.editor.initialPostBody { + post.status = PostStatus.published.rawValue + } } } .onChange(of: titleIsFirstResponder, perform: { value in diff --git a/macOS/Navigation/ActivePostToolbarView.swift b/macOS/Navigation/ActivePostToolbarView.swift index 530199b..911689d 100644 --- a/macOS/Navigation/ActivePostToolbarView.swift +++ b/macOS/Navigation/ActivePostToolbarView.swift @@ -31,6 +31,17 @@ struct ActivePostToolbarView: View { .frame(minWidth: 50, alignment: .center) .layoutPriority(1) .padding(.horizontal) + if activePost.status == PostStatus.edited.rawValue { + Button(action: { + model.editor.postToUpdate = activePost + model.updateFromServer(post: activePost) + model.selectedPost = nil + }, label: { + Image(systemName: "clock.arrow.circlepath") + .accessibilityLabel(Text("Revert post")) + .accessibilityHint(Text("Replace the edited post with the published version from the server")) + }) + } if activePost.status == PostStatus.local.rawValue { Menu(content: { Label("Publish To:", systemImage: "paperplane") @@ -131,6 +142,7 @@ struct ActivePostToolbarView: View { LocalStorageManager.standard.saveContext() model.publish(post: post) } + model.editor.setInitialValues(for: post) } private func openSettingsWindow() { diff --git a/macOS/PostEditor/PostEditorView.swift b/macOS/PostEditor/PostEditorView.swift index b76e921..a6d928c 100644 --- a/macOS/PostEditor/PostEditorView.swift +++ b/macOS/PostEditor/PostEditorView.swift @@ -16,6 +16,7 @@ struct PostEditorView: View { .padding() .background(Color(NSColor.controlBackgroundColor)) .onAppear(perform: { + model.editor.setInitialValues(for: post) if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { self.model.editor.saveLastDraft(post) diff --git a/macOS/PostEditor/PostTextEditingView.swift b/macOS/PostEditor/PostTextEditingView.swift index 5e60002..8922952 100644 --- a/macOS/PostEditor/PostTextEditingView.swift +++ b/macOS/PostEditor/PostTextEditingView.swift @@ -1,6 +1,7 @@ import SwiftUI struct PostTextEditingView: View { + @EnvironmentObject var model: WriteFreelyModel @ObservedObject var post: WFAPost @Binding var updatingFromServer: Bool @State private var appearance: PostAppearance = .serif @@ -74,8 +75,15 @@ struct PostTextEditingView: View { private func onTextChange(_ text: String) { extractTitle(text) - if post.status == PostStatus.published.rawValue && !updatingFromServer { - post.status = PostStatus.edited.rawValue + if !updatingFromServer { + if post.status == PostStatus.published.rawValue { + post.status = PostStatus.edited.rawValue + } + if post.status == PostStatus.edited.rawValue, + post.title == model.editor.initialPostTitle, + post.body == model.editor.initialPostBody { + post.status = PostStatus.published.rawValue + } } if updatingFromServer {