diff --git a/Outpost/Features/Collections/CollectionDocumentsOutline.swift b/Outpost/Features/Collections/CollectionDocumentsOutline.swift index 1c40219..a93b840 100644 --- a/Outpost/Features/Collections/CollectionDocumentsOutline.swift +++ b/Outpost/Features/Collections/CollectionDocumentsOutline.swift @@ -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() } diff --git a/Outpost/Features/Collections/CollectionTreeRow.swift b/Outpost/Features/Collections/CollectionTreeRow.swift index 1bd159e..203e88e 100644 --- a/Outpost/Features/Collections/CollectionTreeRow.swift +++ b/Outpost/Features/Collections/CollectionTreeRow.swift @@ -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 ) diff --git a/Outpost/Features/Collections/CollectionsTreeView.swift b/Outpost/Features/Collections/CollectionsTreeView.swift index fe6bf27..71a6dd9 100644 --- a/Outpost/Features/Collections/CollectionsTreeView.swift +++ b/Outpost/Features/Collections/CollectionsTreeView.swift @@ -7,6 +7,10 @@ struct CollectionsTreeView: View { @Binding private var selectedCollection: OutlineCollection? let selectedDocumentID: String? @State private var expandedCollectionIDs: Set = [] + /// 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, 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, diff --git a/Outpost/Features/Collections/ContentView_macOS.swift b/Outpost/Features/Collections/ContentView_macOS.swift index 4d650f8..6410fee 100644 --- a/Outpost/Features/Collections/ContentView_macOS.swift +++ b/Outpost/Features/Collections/ContentView_macOS.swift @@ -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 } ) } } diff --git a/Outpost/Features/Collections/DocumentReaderView.swift b/Outpost/Features/Collections/DocumentReaderView.swift index 94db606..f5f80ff 100644 --- a/Outpost/Features/Collections/DocumentReaderView.swift +++ b/Outpost/Features/Collections/DocumentReaderView.swift @@ -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.")