mirror of
https://github.com/writeas/writefreely-swiftui-multiplatform.git
synced 2024-11-15 01:11:02 +00:00
Add a "search posts" feature to the post list (#245)
This commit is contained in:
parent
579db0c889
commit
6bb8be4d46
@ -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] 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 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.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ struct PostListFilteredView: View {
|
|||||||
@EnvironmentObject var model: WriteFreelyModel
|
@EnvironmentObject var model: WriteFreelyModel
|
||||||
@Binding var postCount: Int
|
@Binding var postCount: Int
|
||||||
@FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults<WFACollection>
|
@FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults<WFACollection>
|
||||||
|
|
||||||
var fetchRequest: FetchRequest<WFAPost>
|
var fetchRequest: FetchRequest<WFAPost>
|
||||||
|
|
||||||
init(collection: WFACollection?, showAllPosts: Bool, postCount: Binding<Int>) {
|
init(collection: WFACollection?, showAllPosts: Bool, postCount: Binding<Int>) {
|
||||||
@ -32,67 +33,64 @@ struct PostListFilteredView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
List(selection: $model.selectedPost) {
|
if #available(iOS 15, *) {
|
||||||
ForEach(fetchRequest.wrappedValue, id: \.self) { post in
|
SearchablePostListFilteredView(
|
||||||
NavigationLink(
|
postCount: $postCount,
|
||||||
destination: PostEditorView(post: post),
|
collections: collections,
|
||||||
tag: post,
|
fetchRequest: fetchRequest,
|
||||||
selection: $model.selectedPost,
|
onDelete: delete(_:)
|
||||||
label: {
|
)
|
||||||
if model.showAllPosts {
|
.environmentObject(model)
|
||||||
if let collection = collections.filter { $0.alias == post.collectionAlias }.first {
|
.onAppear(perform: {
|
||||||
PostCellView(post: post, collectionName: collection.title)
|
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)
|
||||||
|
} else {
|
||||||
|
// swiftlint:disable:next line_length
|
||||||
|
let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts"
|
||||||
|
PostCellView(post: post, collectionName: collectionName)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts"
|
PostCellView(post: post)
|
||||||
PostCellView(post: post, collectionName: collectionName)
|
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
PostCellView(post: post)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.deleteDisabled(post.status != PostStatus.local.rawValue)
|
.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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.onAppear(perform: {
|
|
||||||
self.postCount = fetchRequest.wrappedValue.count
|
|
||||||
})
|
|
||||||
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in
|
|
||||||
self.postCount = value
|
|
||||||
})
|
|
||||||
#else
|
#else
|
||||||
List(selection: $model.selectedPost) {
|
SearchablePostListFilteredView(
|
||||||
ForEach(fetchRequest.wrappedValue, id: \.self) { post in
|
postCount: $postCount,
|
||||||
NavigationLink(
|
collections: collections,
|
||||||
destination: PostEditorView(post: post),
|
fetchRequest: fetchRequest,
|
||||||
tag: post,
|
onDelete: delete(_:)
|
||||||
selection: $model.selectedPost,
|
)
|
||||||
label: {
|
.environmentObject(model)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.alert(isPresented: $model.isPresentingDeleteAlert) {
|
.alert(isPresented: $model.isPresentingDeleteAlert) {
|
||||||
Alert(
|
Alert(
|
||||||
title: Text("Delete Post?"),
|
title: Text("Delete Post?"),
|
||||||
|
58
Shared/PostList/SearchablePostListFilteredView.swift
Normal file
58
Shared/PostList/SearchablePostListFilteredView.swift
Normal file
@ -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 */; };
|
17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; };
|
||||||
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; };
|
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; };
|
||||||
375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375A67E728FC555C007A1AC0 /* MultilineTextView.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 */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -269,7 +273,9 @@
|
|||||||
17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -606,6 +612,7 @@
|
|||||||
1756AE7924CB65DF00FD7257 /* PostListView.swift */,
|
1756AE7924CB65DF00FD7257 /* PostListView.swift */,
|
||||||
17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */,
|
17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */,
|
||||||
17C42E642509237800072984 /* PostListFilteredView.swift */,
|
17C42E642509237800072984 /* PostListFilteredView.swift */,
|
||||||
|
37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */,
|
||||||
);
|
);
|
||||||
path = PostList;
|
path = PostList;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -937,6 +944,7 @@
|
|||||||
17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */,
|
17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */,
|
||||||
1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */,
|
1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */,
|
||||||
17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */,
|
17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */,
|
||||||
|
37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */,
|
||||||
17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */,
|
17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */,
|
||||||
1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */,
|
1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */,
|
||||||
17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */,
|
17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */,
|
||||||
@ -981,6 +989,7 @@
|
|||||||
17027E26286741B90062EB29 /* Logging.swift in Sources */,
|
17027E26286741B90062EB29 /* Logging.swift in Sources */,
|
||||||
1727526B2809991A003D0A6A /* ErrorHandling.swift in Sources */,
|
1727526B2809991A003D0A6A /* ErrorHandling.swift in Sources */,
|
||||||
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */,
|
17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */,
|
||||||
|
37F749D229B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */,
|
||||||
17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */,
|
17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */,
|
||||||
1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */,
|
1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */,
|
||||||
1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */,
|
1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */,
|
||||||
@ -1483,7 +1492,7 @@
|
|||||||
repositoryURL = "https://github.com/writefreely/writefreely-swift";
|
repositoryURL = "https://github.com/writefreely/writefreely-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 0.3.6;
|
minimumVersion = 0.3.7;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */ = {
|
17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */ = {
|
||||||
|
@ -5,6 +5,16 @@ struct PostCommands: Commands {
|
|||||||
|
|
||||||
var body: some Commands {
|
var body: some Commands {
|
||||||
CommandMenu("Post") {
|
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 {
|
Group {
|
||||||
Button(action: sendPostUrlToPasteboard, label: { Text("Copy Link To Published Post") })
|
Button(action: sendPostUrlToPasteboard, label: { Text("Copy Link To Published Post") })
|
||||||
.disabled(model.selectedPost?.status == PostStatus.local.rawValue)
|
.disabled(model.selectedPost?.status == PostStatus.local.rawValue)
|
||||||
|
Loading…
Reference in New Issue
Block a user