@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
- [Mac] In a post with unpublished changes (i.e., with "local" or "edited" status), the post is autosaved after a one-second pause in typing. | |||
- [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. | |||
### Changed | |||
@@ -4,6 +4,7 @@ struct PostListFilteredView: View { | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@Binding var postCount: Int | |||
@FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults<WFACollection> | |||
var fetchRequest: FetchRequest<WFAPost> | |||
init(collection: WFACollection?, showAllPosts: Bool, postCount: Binding<Int>) { | |||
@@ -32,67 +33,64 @@ struct PostListFilteredView: View { | |||
var body: some View { | |||
#if os(iOS) | |||
List(selection: $model.selectedPost) { | |||
ForEach(fetchRequest.wrappedValue, id: \.self) { post in | |||
NavigationLink( | |||
destination: PostEditorView(post: post), | |||
tag: post, | |||
selection: $model.selectedPost, | |||
label: { | |||
if model.showAllPosts { | |||
if let collection = collections.filter { $0.alias == post.collectionAlias }.first { | |||
PostCellView(post: post, collectionName: collection.title) | |||
} else { | |||
let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" | |||
PostCellView(post: post, collectionName: collectionName) | |||
} | |||
} else { | |||
PostCellView(post: post) | |||
} | |||
}) | |||
.deleteDisabled(post.status != PostStatus.local.rawValue) | |||
} | |||
.onDelete(perform: { indexSet in | |||
for index in indexSet { | |||
let post = fetchRequest.wrappedValue[index] | |||
delete(post) | |||
} | |||
if #available(iOS 15, *) { | |||
SearchablePostListFilteredView( | |||
postCount: $postCount, | |||
collections: collections, | |||
fetchRequest: fetchRequest, | |||
onDelete: delete(_:) | |||
) | |||
.environmentObject(model) | |||
.onAppear(perform: { | |||
self.postCount = fetchRequest.wrappedValue.count | |||
}) | |||
} | |||
.onAppear(perform: { | |||
self.postCount = fetchRequest.wrappedValue.count | |||
}) | |||
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in | |||
self.postCount = value | |||
}) | |||
#else | |||
List(selection: $model.selectedPost) { | |||
ForEach(fetchRequest.wrappedValue, id: \.self) { post in | |||
NavigationLink( | |||
destination: PostEditorView(post: post), | |||
tag: post, | |||
selection: $model.selectedPost, | |||
label: { | |||
if model.showAllPosts { | |||
if let collection = collections.filter { $0.alias == post.collectionAlias }.first { | |||
PostCellView(post: post, collectionName: collection.title) | |||
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in | |||
self.postCount = value | |||
}) | |||
} else { | |||
List(selection: $model.selectedPost) { | |||
ForEach(fetchRequest.wrappedValue, id: \.self) { post in | |||
NavigationLink( | |||
destination: PostEditorView(post: post), | |||
tag: post, | |||
selection: $model.selectedPost, | |||
label: { | |||
if model.showAllPosts { | |||
if let collection = collections.filter({ $0.alias == post.collectionAlias }).first { | |||
PostCellView(post: post, collectionName: collection.title) | |||
} else { | |||
// swiftlint:disable:next line_length | |||
let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" | |||
PostCellView(post: post, collectionName: collectionName) | |||
} | |||
} else { | |||
let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" | |||
PostCellView(post: post, collectionName: collectionName) | |||
PostCellView(post: post) | |||
} | |||
} else { | |||
PostCellView(post: post) | |||
} | |||
}) | |||
}) | |||
.deleteDisabled(post.status != PostStatus.local.rawValue) | |||
} | |||
.onDelete(perform: { indexSet in | |||
for index in indexSet { | |||
let post = fetchRequest.wrappedValue[index] | |||
delete(post) | |||
} | |||
.onDelete(perform: { indexSet in | |||
for index in indexSet { | |||
let post = fetchRequest.wrappedValue[index] | |||
delete(post) | |||
} | |||
}) | |||
} | |||
.onAppear(perform: { | |||
self.postCount = fetchRequest.wrappedValue.count | |||
}) | |||
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in | |||
self.postCount = value | |||
}) | |||
} | |||
#else | |||
SearchablePostListFilteredView( | |||
postCount: $postCount, | |||
collections: collections, | |||
fetchRequest: fetchRequest, | |||
onDelete: delete(_:) | |||
) | |||
.environmentObject(model) | |||
.alert(isPresented: $model.isPresentingDeleteAlert) { | |||
Alert( | |||
title: Text("Delete Post?"), | |||
@@ -0,0 +1,58 @@ | |||
import SwiftUI | |||
@available(iOS 15, macOS 12.0, *) | |||
struct SearchablePostListFilteredView: View { | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@Binding var postCount: Int | |||
@State private var searchString = "" | |||
var collections: FetchedResults<WFACollection> | |||
var fetchRequest: FetchRequest<WFAPost> | |||
var onDelete: (WFAPost) -> Void | |||
var body: some View { | |||
List(selection: $model.selectedPost) { | |||
ForEach(fetchRequest.wrappedValue, id: \.self) { post in | |||
if !searchString.isEmpty && | |||
!post.title.localizedCaseInsensitiveContains(searchString) && | |||
!post.body.localizedCaseInsensitiveContains(searchString) { | |||
EmptyView() | |||
} else { | |||
NavigationLink( | |||
destination: PostEditorView(post: post), | |||
tag: post, | |||
selection: $model.selectedPost, | |||
label: { | |||
if model.showAllPosts { | |||
if let collection = collections.filter({ $0.alias == post.collectionAlias }).first { | |||
PostCellView(post: post, collectionName: collection.title) | |||
} else { | |||
// swiftlint:disable:next line_length | |||
let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" | |||
PostCellView(post: post, collectionName: collectionName) | |||
} | |||
} else { | |||
PostCellView(post: post) | |||
} | |||
}) | |||
.deleteDisabled(post.status != PostStatus.local.rawValue) | |||
} | |||
} | |||
.onDelete(perform: { indexSet in | |||
for index in indexSet { | |||
let post = fetchRequest.wrappedValue[index] | |||
delete(post) | |||
} | |||
}) | |||
} | |||
#if os(iOS) | |||
.searchable(text: $searchString, prompt: "Search across posts") | |||
#else | |||
.searchable(text: $searchString, placement: .toolbar, prompt: "Search across posts") | |||
#endif | |||
} | |||
func delete(_ post: WFAPost) { | |||
onDelete(post) | |||
} | |||
} |
@@ -137,7 +137,11 @@ | |||
17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; | |||
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; }; | |||
375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */; }; | |||
37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; | |||
37F749D229B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; | |||
3779389729EC0C880032D6C1 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3779389629EC0C880032D6C1 /* HelpCommands.swift */; }; | |||
37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; | |||
37F749D229B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; | |||
/* End PBXBuildFile section */ | |||
/* Begin PBXContainerItemProxy section */ | |||
@@ -269,7 +273,9 @@ | |||
17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = "<group>"; }; | |||
17E5DF892543610700DCDC9B /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = "<group>"; }; | |||
375A67E728FC555C007A1AC0 /* MultilineTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextView.swift; sourceTree = "<group>"; }; | |||
37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePostListFilteredView.swift; sourceTree = "<group>"; }; | |||
3779389629EC0C880032D6C1 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = "<group>"; }; | |||
37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePostListFilteredView.swift; sourceTree = "<group>"; }; | |||
/* End PBXFileReference section */ | |||
/* Begin PBXFrameworksBuildPhase section */ | |||
@@ -606,6 +612,7 @@ | |||
1756AE7924CB65DF00FD7257 /* PostListView.swift */, | |||
17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */, | |||
17C42E642509237800072984 /* PostListFilteredView.swift */, | |||
37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */, | |||
); | |||
path = PostList; | |||
sourceTree = "<group>"; | |||
@@ -937,6 +944,7 @@ | |||
17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, | |||
1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */, | |||
17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, | |||
37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */, | |||
17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */, | |||
1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */, | |||
17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, | |||
@@ -981,6 +989,7 @@ | |||
17027E26286741B90062EB29 /* Logging.swift in Sources */, | |||
1727526B2809991A003D0A6A /* ErrorHandling.swift in Sources */, | |||
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */, | |||
37F749D229B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */, | |||
17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, | |||
1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */, | |||
1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */, | |||
@@ -1483,7 +1492,7 @@ | |||
repositoryURL = "https://github.com/writefreely/writefreely-swift"; | |||
requirement = { | |||
kind = upToNextMajorVersion; | |||
minimumVersion = 0.3.6; | |||
minimumVersion = 0.3.7; | |||
}; | |||
}; | |||
17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */ = { | |||
@@ -5,6 +5,16 @@ struct PostCommands: Commands { | |||
var body: some Commands { | |||
CommandMenu("Post") { | |||
Button("Find In Posts") { | |||
if let toolbar = NSApp.keyWindow?.toolbar, | |||
let search = toolbar.items.first(where: { | |||
$0.itemIdentifier.rawValue == "com.apple.SwiftUI.search" | |||
}) as? NSSearchToolbarItem { | |||
search.beginSearchInteraction() | |||
} | |||
} | |||
.keyboardShortcut("f", modifiers: [.command, .shift]) | |||
Group { | |||
Button(action: sendPostUrlToPasteboard, label: { Text("Copy Link To Published Post") }) | |||
.disabled(model.selectedPost?.status == PostStatus.local.rawValue) | |||