@@ -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 | |||
@@ -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 | |||
} | |||
@@ -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 } | |||
@@ -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; | |||
@@ -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() { | |||
@@ -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 | |||
@@ -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() { | |||
@@ -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) | |||
@@ -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 { | |||