mirror of
https://github.com/writeas/writefreely-swiftui-multiplatform.git
synced 2024-11-15 01:11:02 +00:00
Handle undo of edited posts (#251)
This commit is contained in:
parent
ba3f44b287
commit
73d219d0ec
@ -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 a context-menu item to delete local posts from the post list.
|
||||||
- [Mac] Added methods to fetch device logs.
|
- [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 search for text across all posts.
|
||||||
|
- [iOS, Mac] Added a way to refresh an edited post from the server copy.
|
||||||
|
|
||||||
### Changed
|
### 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] Updated the URL and minimum version of the WriteFreely Swift package.
|
||||||
- [Mac] Upgraded the Sparkle package to v2.
|
- [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.
|
- [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
|
### Fixed
|
||||||
|
|
||||||
|
@ -244,6 +244,7 @@ extension WriteFreelyModel {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
self.selectedPost = cachedPost
|
self.selectedPost = cachedPost
|
||||||
#endif
|
#endif
|
||||||
|
cachedPost.status = PostStatus.published.rawValue
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
self.currentError = AppError.genericError(error.localizedDescription)
|
self.currentError = AppError.genericError(error.localizedDescription)
|
||||||
@ -280,7 +281,6 @@ extension WriteFreelyModel {
|
|||||||
cachedPost.postId = fetchedPost.postId
|
cachedPost.postId = fetchedPost.postId
|
||||||
cachedPost.rtl = fetchedPost.rtl ?? false
|
cachedPost.rtl = fetchedPost.rtl ?? false
|
||||||
cachedPost.slug = fetchedPost.slug
|
cachedPost.slug = fetchedPost.slug
|
||||||
cachedPost.status = PostStatus.published.rawValue
|
|
||||||
cachedPost.title = fetchedPost.title ?? ""
|
cachedPost.title = fetchedPost.title ?? ""
|
||||||
cachedPost.updatedDate = fetchedPost.updatedDate
|
cachedPost.updatedDate = fetchedPost.updatedDate
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,9 @@ struct PostEditorModel {
|
|||||||
@AppStorage(WFDefaults.selectedCollectionURL, store: UserDefaults.shared) var selectedCollectionURL: URL?
|
@AppStorage(WFDefaults.selectedCollectionURL, store: UserDefaults.shared) var selectedCollectionURL: URL?
|
||||||
@AppStorage(WFDefaults.lastDraftURL, store: UserDefaults.shared) var lastDraftURL: URL?
|
@AppStorage(WFDefaults.lastDraftURL, store: UserDefaults.shared) var lastDraftURL: URL?
|
||||||
|
|
||||||
|
private(set) var initialPostTitle: String?
|
||||||
|
private(set) var initialPostBody: String?
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
var postToUpdate: WFAPost?
|
var postToUpdate: WFAPost?
|
||||||
#endif
|
#endif
|
||||||
@ -58,6 +61,21 @@ struct PostEditorModel {
|
|||||||
return collection
|
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? {
|
private func fetchManagedObject(from objectURL: URL) -> NSManagedObject? {
|
||||||
let coordinator = LocalStorageManager.standard.container.persistentStoreCoordinator
|
let coordinator = LocalStorageManager.standard.container.persistentStoreCoordinator
|
||||||
guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil }
|
guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil }
|
||||||
|
@ -1063,7 +1063,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 701;
|
CURRENT_PROJECT_VERSION = 702;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
||||||
@ -1094,7 +1094,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 701;
|
CURRENT_PROJECT_VERSION = 702;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
INFOPLIST_FILE = "ActionExtension-iOS/Info.plist";
|
||||||
@ -1237,7 +1237,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 701;
|
CURRENT_PROJECT_VERSION = 702;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
@ -1263,7 +1263,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 701;
|
CURRENT_PROJECT_VERSION = 702;
|
||||||
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
DEVELOPMENT_TEAM = TPPAB4YBA6;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
|
@ -84,7 +84,7 @@ struct PostEditorView: View {
|
|||||||
})
|
})
|
||||||
.accessibilityHint(Text("Choose the blog you want to publish this post to"))
|
.accessibilityHint(Text("Choose the blog you want to publish this post to"))
|
||||||
.disabled(post.body.count == 0)
|
.disabled(post.body.count == 0)
|
||||||
} else {
|
} else if post.status == PostStatus.edited.rawValue {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if model.account.isLoggedIn {
|
if model.account.isLoggedIn {
|
||||||
publishPost()
|
publishPost()
|
||||||
@ -95,6 +95,14 @@ struct PostEditorView: View {
|
|||||||
Label("Publish", systemImage: "paperplane")
|
Label("Publish", systemImage: "paperplane")
|
||||||
})
|
})
|
||||||
.disabled(post.status == PostStatus.published.rawValue || post.body.count == 0)
|
.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: {
|
Button(action: {
|
||||||
sharePost()
|
sharePost()
|
||||||
@ -160,6 +168,7 @@ struct PostEditorView: View {
|
|||||||
})
|
})
|
||||||
.onAppear(perform: {
|
.onAppear(perform: {
|
||||||
self.selectedCollection = collections.first { $0.alias == post.collectionAlias }
|
self.selectedCollection = collections.first { $0.alias == post.collectionAlias }
|
||||||
|
model.editor.setInitialValues(for: post)
|
||||||
if post.status != PostStatus.published.rawValue {
|
if post.status != PostStatus.published.rawValue {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.model.editor.saveLastDraft(post)
|
self.model.editor.saveLastDraft(post)
|
||||||
@ -201,9 +210,8 @@ struct PostEditorView: View {
|
|||||||
LocalStorageManager.standard.saveContext()
|
LocalStorageManager.standard.saveContext()
|
||||||
model.publish(post: post)
|
model.publish(post: post)
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
model.editor.setInitialValues(for: post)
|
||||||
self.hideKeyboard()
|
self.hideKeyboard()
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sharePost() {
|
private func sharePost() {
|
||||||
|
@ -2,6 +2,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct PostTextEditingView: View {
|
struct PostTextEditingView: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||||
|
@EnvironmentObject var model: WriteFreelyModel
|
||||||
@ObservedObject var post: WFAPost
|
@ObservedObject var post: WFAPost
|
||||||
@Binding var updatingTitleFromServer: Bool
|
@Binding var updatingTitleFromServer: Bool
|
||||||
@Binding var updatingBodyFromServer: Bool
|
@Binding var updatingBodyFromServer: Bool
|
||||||
@ -35,13 +36,16 @@ struct PostTextEditingView: View {
|
|||||||
)
|
)
|
||||||
.accessibilityLabel(Text("Title (optional)"))
|
.accessibilityLabel(Text("Title (optional)"))
|
||||||
.accessibilityHint(Text("Add or edit the title for your post; use the Return key to skip to the body"))
|
.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 {
|
if post.status == PostStatus.published.rawValue && !updatingTitleFromServer {
|
||||||
post.status = PostStatus.edited.rawValue
|
post.status = PostStatus.edited.rawValue
|
||||||
}
|
}
|
||||||
if updatingTitleFromServer {
|
if updatingTitleFromServer {
|
||||||
updatingTitleFromServer = false
|
updatingTitleFromServer = false
|
||||||
}
|
}
|
||||||
|
if post.status == PostStatus.edited.rawValue && value == model.editor.initialPostTitle {
|
||||||
|
post.status = PostStatus.published.rawValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MultilineTextField(
|
MultilineTextField(
|
||||||
"Write...",
|
"Write...",
|
||||||
@ -51,13 +55,16 @@ struct PostTextEditingView: View {
|
|||||||
)
|
)
|
||||||
.accessibilityLabel(Text("Body"))
|
.accessibilityLabel(Text("Body"))
|
||||||
.accessibilityHint(Text("Add or edit the body of your post"))
|
.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 {
|
if post.status == PostStatus.published.rawValue && !updatingBodyFromServer {
|
||||||
post.status = PostStatus.edited.rawValue
|
post.status = PostStatus.edited.rawValue
|
||||||
}
|
}
|
||||||
if updatingBodyFromServer {
|
if updatingBodyFromServer {
|
||||||
updatingBodyFromServer = false
|
updatingBodyFromServer = false
|
||||||
}
|
}
|
||||||
|
if post.status == PostStatus.edited.rawValue && value == model.editor.initialPostBody {
|
||||||
|
post.status = PostStatus.published.rawValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: titleIsFirstResponder, perform: { value in
|
.onChange(of: titleIsFirstResponder, perform: { value in
|
||||||
|
@ -31,6 +31,17 @@ struct ActivePostToolbarView: View {
|
|||||||
.frame(minWidth: 50, alignment: .center)
|
.frame(minWidth: 50, alignment: .center)
|
||||||
.layoutPriority(1)
|
.layoutPriority(1)
|
||||||
.padding(.horizontal)
|
.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 {
|
if activePost.status == PostStatus.local.rawValue {
|
||||||
Menu(content: {
|
Menu(content: {
|
||||||
Label("Publish To:", systemImage: "paperplane")
|
Label("Publish To:", systemImage: "paperplane")
|
||||||
@ -131,6 +142,7 @@ struct ActivePostToolbarView: View {
|
|||||||
LocalStorageManager.standard.saveContext()
|
LocalStorageManager.standard.saveContext()
|
||||||
model.publish(post: post)
|
model.publish(post: post)
|
||||||
}
|
}
|
||||||
|
model.editor.setInitialValues(for: post)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openSettingsWindow() {
|
private func openSettingsWindow() {
|
||||||
|
@ -16,6 +16,7 @@ struct PostEditorView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
.background(Color(NSColor.controlBackgroundColor))
|
.background(Color(NSColor.controlBackgroundColor))
|
||||||
.onAppear(perform: {
|
.onAppear(perform: {
|
||||||
|
model.editor.setInitialValues(for: post)
|
||||||
if post.status != PostStatus.published.rawValue {
|
if post.status != PostStatus.published.rawValue {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.model.editor.saveLastDraft(post)
|
self.model.editor.saveLastDraft(post)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PostTextEditingView: View {
|
struct PostTextEditingView: View {
|
||||||
|
@EnvironmentObject var model: WriteFreelyModel
|
||||||
@ObservedObject var post: WFAPost
|
@ObservedObject var post: WFAPost
|
||||||
@Binding var updatingFromServer: Bool
|
@Binding var updatingFromServer: Bool
|
||||||
@State private var appearance: PostAppearance = .serif
|
@State private var appearance: PostAppearance = .serif
|
||||||
@ -74,8 +75,15 @@ struct PostTextEditingView: View {
|
|||||||
private func onTextChange(_ text: String) {
|
private func onTextChange(_ text: String) {
|
||||||
extractTitle(text)
|
extractTitle(text)
|
||||||
|
|
||||||
if post.status == PostStatus.published.rawValue && !updatingFromServer {
|
if !updatingFromServer {
|
||||||
post.status = PostStatus.edited.rawValue
|
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 {
|
if updatingFromServer {
|
||||||
|
Loading…
Reference in New Issue
Block a user