Two entry points backed by documents.search: a per-collection toolbar search field, and a sidebar-driven global search with collection/date/ sort/status filters. Both debounce via .task(id:) instead of a timer.
49 lines
1.6 KiB
Swift
49 lines
1.6 KiB
Swift
import Foundation
|
|
import Observation
|
|
import OutlineKit
|
|
|
|
/// Backs the contextual toolbar search — matches scoped to whichever collection
|
|
/// is currently open ("search what you're looking at").
|
|
///
|
|
/// Uses `documents.search` rather than `documents.search_titles`: the latter
|
|
/// consistently failed against the connected server (likely unavailable on
|
|
/// this Outline version — see ARCHITECTURE.md's note on server version drift),
|
|
/// while `documents.search` is the same, longer-established endpoint the
|
|
/// sidebar's global search already relies on.
|
|
@MainActor
|
|
@Observable
|
|
final class DocumentTitleSearchViewModel {
|
|
var results: [OutlineDocumentSearchResult] = []
|
|
var isSearching = false
|
|
var errorMessage: String?
|
|
|
|
private let apiClient: OutlineAPIClient
|
|
private let collectionId: String
|
|
|
|
init(apiClient: OutlineAPIClient, collectionId: String) {
|
|
self.apiClient = apiClient
|
|
self.collectionId = collectionId
|
|
}
|
|
|
|
func search(query: String) async {
|
|
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else {
|
|
results = []
|
|
errorMessage = nil
|
|
return
|
|
}
|
|
|
|
isSearching = true
|
|
errorMessage = nil
|
|
defer { isSearching = false }
|
|
|
|
do {
|
|
results = try await apiClient.searchDocuments(
|
|
DocumentSearchRequest(query: trimmed, collectionId: collectionId)
|
|
)
|
|
} catch {
|
|
errorMessage = outlineErrorMessage(error, fallback: "Search failed. Try again.")
|
|
}
|
|
}
|
|
}
|