fix(sidebar): auto-refresh after creating a doc from Home or the reader toolbar
The reader's inline "New Document" button and Home's New Document sheet only navigated to the new document — neither had a handle on the sidebar row it landed under, so the tree stayed stale until the periodic remote-changes poll (45s) surfaced the "reload" banner. New Document flows that already have a direct handle on their own sidebar row (right-click a collection, right-click a document) already refreshed correctly and are untouched. Threads a documentsChangedToken from ContentView_macOS down through CollectionsTreeView -> CollectionTreeRow -> CollectionDocumentsOutline as externalRefreshToken; every expanded row reloads itself when it bumps, since neither Home nor the reader knows which row (if any) corresponds to where the new document landed.
This commit is contained in:
@@ -12,6 +12,13 @@ struct CollectionDocumentsOutline: View {
|
||||
@State private var viewModel: DocumentsViewModel
|
||||
let sortOption: SidebarSortOption
|
||||
let refreshToken: Int
|
||||
/// Bumped from `ContentView_macOS` whenever a document is created from
|
||||
/// somewhere that has no direct handle on this row — the reader's
|
||||
/// toolbar "New Document" button and Home's, specifically. Those can't
|
||||
/// call `onDocumentsChanged()` the way a same-row sheet does, since they
|
||||
/// don't know which (if any) sidebar row corresponds to where the new
|
||||
/// document landed, so every expanded row just reloads itself.
|
||||
let externalRefreshToken: Int
|
||||
let selectedDocumentID: String?
|
||||
/// Full chain from root to the clicked document (inclusive) — lets the
|
||||
/// toolbar render the real hierarchy instead of just the leaf title.
|
||||
@@ -26,6 +33,7 @@ struct CollectionDocumentsOutline: View {
|
||||
collection: OutlineCollection,
|
||||
sortOption: SidebarSortOption,
|
||||
refreshToken: Int,
|
||||
externalRefreshToken: Int,
|
||||
selectedDocumentID: String?,
|
||||
onSelectDocument: @escaping ([OutlineDocument]) -> Void
|
||||
) {
|
||||
@@ -33,6 +41,7 @@ struct CollectionDocumentsOutline: View {
|
||||
_viewModel = State(initialValue: DocumentsViewModel(apiClient: apiClient, collection: collection))
|
||||
self.sortOption = sortOption
|
||||
self.refreshToken = refreshToken
|
||||
self.externalRefreshToken = externalRefreshToken
|
||||
self.selectedDocumentID = selectedDocumentID
|
||||
self.onSelectDocument = onSelectDocument
|
||||
}
|
||||
@@ -62,7 +71,10 @@ struct CollectionDocumentsOutline: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.task(id: refreshToken) { await viewModel.load() }
|
||||
// Combined into one identity rather than two separate `.task(id:)`
|
||||
// modifiers — each of those fires once unconditionally on first
|
||||
// appear, so two of them would double the initial load.
|
||||
.task(id: "\(refreshToken)-\(externalRefreshToken)") { await viewModel.load() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ struct CollectionTreeRow: View {
|
||||
let isExpanded: Bool
|
||||
let isSelected: Bool
|
||||
let selectedDocumentID: String?
|
||||
/// See the identical parameter on `CollectionDocumentsOutline`.
|
||||
let externalRefreshToken: Int
|
||||
let onToggle: () -> Void
|
||||
let onSelectDocument: ([OutlineDocument]) -> Void
|
||||
let onSearchInCollection: (OutlineCollection) -> Void
|
||||
@@ -63,6 +65,7 @@ struct CollectionTreeRow: View {
|
||||
collection: collection,
|
||||
sortOption: sortOption,
|
||||
refreshToken: documentsRefreshToken,
|
||||
externalRefreshToken: externalRefreshToken,
|
||||
selectedDocumentID: selectedDocumentID,
|
||||
onSelectDocument: onSelectDocument
|
||||
)
|
||||
|
||||
@@ -7,6 +7,10 @@ struct CollectionsTreeView: View {
|
||||
@Binding private var selectedCollection: OutlineCollection?
|
||||
let selectedDocumentID: String?
|
||||
@State private var expandedCollectionIDs: Set<String> = []
|
||||
/// Bumped from `ContentView_macOS` whenever a document is created
|
||||
/// somewhere with no direct handle on the sidebar row it belongs
|
||||
/// under — see the identical parameter on `CollectionDocumentsOutline`.
|
||||
let externalRefreshToken: Int
|
||||
let onSelectDocument: (OutlineCollection, [OutlineDocument]) -> Void
|
||||
let onSearchInCollection: (OutlineCollection) -> Void
|
||||
|
||||
@@ -14,12 +18,14 @@ struct CollectionsTreeView: View {
|
||||
apiClient: OutlineAPIClient,
|
||||
selectedCollection: Binding<OutlineCollection?>,
|
||||
selectedDocumentID: String?,
|
||||
externalRefreshToken: Int,
|
||||
onSelectDocument: @escaping (OutlineCollection, [OutlineDocument]) -> Void,
|
||||
onSearchInCollection: @escaping (OutlineCollection) -> Void
|
||||
) {
|
||||
_viewModel = State(initialValue: CollectionsViewModel(apiClient: apiClient))
|
||||
_selectedCollection = selectedCollection
|
||||
self.selectedDocumentID = selectedDocumentID
|
||||
self.externalRefreshToken = externalRefreshToken
|
||||
self.onSelectDocument = onSelectDocument
|
||||
self.onSearchInCollection = onSearchInCollection
|
||||
}
|
||||
@@ -81,6 +87,7 @@ struct CollectionsTreeView: View {
|
||||
isExpanded: expandedCollectionIDs.contains(collection.id),
|
||||
isSelected: selectedCollection?.id == collection.id,
|
||||
selectedDocumentID: selectedDocumentID,
|
||||
externalRefreshToken: externalRefreshToken,
|
||||
onToggle: { toggle(collection) },
|
||||
onSelectDocument: { chain in onSelectDocument(collection, chain) },
|
||||
onSearchInCollection: onSearchInCollection,
|
||||
|
||||
@@ -16,6 +16,11 @@ struct ContentView_macOS: View {
|
||||
@State private var contextualSearchQuery = ""
|
||||
@State private var isContextualSearchExpanded = false
|
||||
@FocusState private var isContextualSearchFocused: Bool
|
||||
/// Bumped whenever a document is created from somewhere with no direct
|
||||
/// handle on the sidebar row it belongs under (the reader toolbar's and
|
||||
/// Home's "New Document" buttons) — every expanded sidebar row reloads
|
||||
/// itself in response. See `CollectionDocumentsOutline.externalRefreshToken`.
|
||||
@State private var documentsChangedToken = 0
|
||||
|
||||
private var trimmedGlobalQuery: String {
|
||||
globalSearchQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -191,6 +196,7 @@ struct ContentView_macOS: View {
|
||||
apiClient: apiClient,
|
||||
selectedCollection: $selectedCollection,
|
||||
selectedDocumentID: documentPath.last?.id,
|
||||
externalRefreshToken: documentsChangedToken,
|
||||
onSelectDocument: selectDocumentChain,
|
||||
onSearchInCollection: searchInCollection
|
||||
)
|
||||
@@ -244,7 +250,7 @@ struct ContentView_macOS: View {
|
||||
if !trimmedGlobalQuery.isEmpty {
|
||||
GlobalSearchResultsView(apiClient: apiClient, query: trimmedGlobalQuery, onOpenDocument: openDocument)
|
||||
} else if isShowingHome {
|
||||
HomeView(apiClient: apiClient, onOpenDocument: openDocument)
|
||||
HomeView(apiClient: apiClient, onOpenDocument: openDocument, onDocumentCreated: { documentsChangedToken += 1 })
|
||||
} else if let selectedCollection {
|
||||
CollectionOverviewView(
|
||||
apiClient: apiClient,
|
||||
@@ -269,7 +275,8 @@ struct ContentView_macOS: View {
|
||||
if !documentPath.isEmpty {
|
||||
documentPath.removeLast()
|
||||
}
|
||||
}
|
||||
},
|
||||
onDocumentCreated: { documentsChangedToken += 1 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ struct DocumentReaderView: View {
|
||||
/// longer visible in the collection it was opened from, so the reader
|
||||
/// pops itself off the navigation stack.
|
||||
let onDeleted: () -> Void
|
||||
/// The reader's own "New Document" toolbar button has no direct handle
|
||||
/// on the sidebar row it belongs under — this tells the sidebar a
|
||||
/// document exists now so it can pick it up. See
|
||||
/// `CollectionDocumentsOutline.externalRefreshToken`.
|
||||
let onDocumentCreated: () -> Void
|
||||
|
||||
@State private var isShowingUnpublishConfirmation = false
|
||||
@State private var isShowingArchiveConfirmation = false
|
||||
@@ -40,13 +45,15 @@ struct DocumentReaderView: View {
|
||||
apiClient: OutlineAPIClient,
|
||||
document: OutlineDocument,
|
||||
onOpenChild: @escaping (OutlineDocument) -> Void,
|
||||
onDeleted: @escaping () -> Void
|
||||
onDeleted: @escaping () -> Void,
|
||||
onDocumentCreated: @escaping () -> Void
|
||||
) {
|
||||
self.apiClient = apiClient
|
||||
self.document = document
|
||||
_viewModel = State(initialValue: DocumentReaderViewModel(apiClient: apiClient, document: document))
|
||||
self.onOpenChild = onOpenChild
|
||||
self.onDeleted = onDeleted
|
||||
self.onDocumentCreated = onDocumentCreated
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -213,6 +220,7 @@ struct DocumentReaderView: View {
|
||||
}
|
||||
.sheet(isPresented: $isShowingNewDocumentSheet) {
|
||||
NewDocumentSheet(apiClient: apiClient, initialParentDocument: document) { child in
|
||||
onDocumentCreated()
|
||||
onOpenChild(child)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import OutlineKit
|
||||
struct HomeView: View {
|
||||
let apiClient: OutlineAPIClient
|
||||
let onOpenDocument: (OutlineDocument) -> Void
|
||||
/// Home has no sidebar row of its own to reload directly — this tells
|
||||
/// the sidebar a document exists now so it can pick it up. See
|
||||
/// `CollectionDocumentsOutline.externalRefreshToken`.
|
||||
let onDocumentCreated: () -> Void
|
||||
|
||||
@State private var viewModel: HomeViewModel
|
||||
@State private var selectedTab: HomeTab = .recentlyViewed
|
||||
@@ -13,9 +17,10 @@ struct HomeView: View {
|
||||
private let pinnedGridColumns = [GridItem(.adaptive(minimum: 260), spacing: 8)]
|
||||
private let tabGridColumns = [GridItem(.adaptive(minimum: 220), spacing: 12)]
|
||||
|
||||
init(apiClient: OutlineAPIClient, onOpenDocument: @escaping (OutlineDocument) -> Void) {
|
||||
init(apiClient: OutlineAPIClient, onOpenDocument: @escaping (OutlineDocument) -> Void, onDocumentCreated: @escaping () -> Void) {
|
||||
self.apiClient = apiClient
|
||||
self.onOpenDocument = onOpenDocument
|
||||
self.onDocumentCreated = onDocumentCreated
|
||||
_viewModel = State(initialValue: HomeViewModel(apiClient: apiClient))
|
||||
}
|
||||
|
||||
@@ -51,6 +56,7 @@ struct HomeView: View {
|
||||
}
|
||||
.sheet(isPresented: $isShowingNewDocumentSheet) {
|
||||
NewDocumentSheet(apiClient: apiClient) { document in
|
||||
onDocumentCreated()
|
||||
onOpenDocument(document)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user