fix(sidebar): auto-refresh after creating a doc from the reader toolbar
The reader's inline "New Document" button (createChildDocument()) only navigated to the new document — no handle on the sidebar row it landed under, so the tree stayed stale until the periodic remote-changes poll surfaced the reload banner. Right-click "New Document" on a collection already refreshed correctly (bumps its own documentsRefreshToken) and is untouched. Threads a documentsChangedToken from ContentView_macOS down through CollectionsTreeView -> CollectionTreeRow -> CollectionDocumentsOutline as externalRefreshToken; every expanded row reloads itself when it bumps, since the reader doesn't know which row (if any) corresponds to where the new document landed.
This commit is contained in:
@@ -13,6 +13,12 @@ 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, specifically, which doesn't know which
|
||||
/// (if any) sidebar row corresponds to the collection its new document
|
||||
/// landed in, 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.
|
||||
@@ -32,6 +38,7 @@ struct CollectionDocumentsOutline: View {
|
||||
collection: OutlineCollection,
|
||||
sortOption: SidebarSortOption,
|
||||
refreshToken: Int,
|
||||
externalRefreshToken: Int,
|
||||
selectedDocumentID: String?,
|
||||
onSelectDocument: @escaping ([OutlineDocument]) -> Void
|
||||
) {
|
||||
@@ -40,6 +47,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
|
||||
}
|
||||
@@ -71,7 +79,10 @@ struct CollectionDocumentsOutline: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.task(id: refreshToken) {
|
||||
// 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()
|
||||
await loadPins()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -62,6 +64,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,
|
||||
|
||||
@@ -12,6 +12,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
|
||||
/// "New Document" button) — every expanded sidebar row reloads itself
|
||||
/// in response. See `CollectionDocumentsOutline.externalRefreshToken`.
|
||||
@State private var documentsChangedToken = 0
|
||||
|
||||
private var trimmedGlobalQuery: String {
|
||||
globalSearchQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -153,6 +158,7 @@ struct ContentView_macOS: View {
|
||||
apiClient: apiClient,
|
||||
selectedCollection: $selectedCollection,
|
||||
selectedDocumentID: documentPath.last?.id,
|
||||
externalRefreshToken: documentsChangedToken,
|
||||
onSelectDocument: selectDocumentChain,
|
||||
onSearchInCollection: searchInCollection
|
||||
)
|
||||
@@ -229,7 +235,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
|
||||
@@ -39,13 +44,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 {
|
||||
@@ -464,6 +471,7 @@ struct DocumentReaderView: View {
|
||||
let child = try await apiClient.createDocument(
|
||||
CreateDocumentRequest(title: "Untitled", text: "", collectionId: collectionId, parentDocumentId: viewModel.documentId)
|
||||
)
|
||||
onDocumentCreated()
|
||||
onOpenChild(child)
|
||||
} catch {
|
||||
actionErrorMessage = outlineErrorMessage(error, fallback: "Couldn't create a new document.")
|
||||
|
||||
Reference in New Issue
Block a user