diff --git a/Outpost/Features/Collections/DocumentReaderView.swift b/Outpost/Features/Collections/DocumentReaderView.swift index d99df2d..a4a2dc7 100644 --- a/Outpost/Features/Collections/DocumentReaderView.swift +++ b/Outpost/Features/Collections/DocumentReaderView.swift @@ -12,6 +12,7 @@ import OutlineKit struct DocumentReaderView: View { @Environment(SessionStore.self) private var session @Environment(StarStore.self) private var starStore + @AppStorage(CachingOutlineAPIClient.offlineModeDefaultsKey) private var isOfflineModeEnabled = false @State private var viewModel: DocumentReaderViewModel let apiClient: OutlineAPIClient @@ -56,6 +57,15 @@ struct DocumentReaderView: View { self.onDocumentCreated = onDocumentCreated } + /// Editing, pin/star/subscribe, and Full Width all queue and sync later + /// (see `CachingOutlineAPIClient`) — everything else here (sharing, + /// permissions, move/archive/delete/duplicate/templatize, history, + /// insights, export) hits the server directly with no offline path, so + /// it's disabled rather than left to fail confusingly on tap. + private var isEffectivelyOnline: Bool { + session.networkMonitor.isOnline && !isOfflineModeEnabled + } + var body: some View { ScrollView { VStack(alignment: .leading, spacing: 12) { @@ -110,7 +120,8 @@ struct DocumentReaderView: View { } label: { Image(systemName: "square.and.arrow.up") } - .help("Share") + .help(isEffectivelyOnline ? "Share" : "Sharing needs an internet connection") + .disabled(!isEffectivelyOnline) .popover(isPresented: $isShowingShareSheet, arrowEdge: .bottom) { DocumentShareSheet(apiClient: apiClient, documentId: viewModel.documentId) } @@ -131,7 +142,8 @@ struct DocumentReaderView: View { } label: { Image(systemName: "doc.badge.plus") } - .help("New Document") + .help(isEffectivelyOnline ? "New Document" : "Creating documents needs an internet connection") + .disabled(!isEffectivelyOnline) Menu { menuContent @@ -235,7 +247,8 @@ struct DocumentReaderView: View { viewModel.isPinned, viewModel.isInsightsEnabled ?? false, viewModel.isFullWidth, - viewModel.isEditing + viewModel.isEditing, + isEffectivelyOnline ].map(String.init).joined(separator: "-") } @@ -305,27 +318,33 @@ struct DocumentReaderView: View { Button("Permissions…") { isShowingShareSheet = true } + .disabled(!isEffectivelyOnline) Divider() Button("Templatize") { Task { await templatize() } } + .disabled(!isEffectivelyOnline) Button("Duplicate") { Task { await duplicate() } } + .disabled(!isEffectivelyOnline) Button("Unpublish") { isShowingUnpublishConfirmation = true } + .disabled(!isEffectivelyOnline) Button("Archive…") { isShowingArchiveConfirmation = true } + .disabled(!isEffectivelyOnline) Divider() Button("Move") { isShowingMoveSheet = true } + .disabled(!isEffectivelyOnline) // Multipart file upload is its own subsystem — deferred rather than // half-built here. Button("Import Document…") {} @@ -333,6 +352,7 @@ struct DocumentReaderView: View { Button("New Document") { isShowingNewDocumentSheet = true } + .disabled(!isEffectivelyOnline) Button(viewModel.isPinned ? "Unpin from Home" : "Pin to Home") { Task { await togglePin() } } @@ -342,9 +362,13 @@ struct DocumentReaderView: View { Button("History") { isShowingHistorySheet = true } + .disabled(!isEffectivelyOnline) Button("Insights") { isShowingInsightsSheet = true } + .disabled(!isEffectivelyOnline) + // Present/Search in Document both read `documents.info`, which is + // read-through cached — they work offline on whatever's cached. Button("Present") { isShowingPresentSheet = true } @@ -354,6 +378,7 @@ struct DocumentReaderView: View { Button("Download") { Task { await download() } } + .disabled(!isEffectivelyOnline) Button("Copy") { Task { await copyMarkdown() } } @@ -370,6 +395,7 @@ struct DocumentReaderView: View { get: { viewModel.isInsightsEnabled ?? false }, set: { _ in Task { await toggleInsights() } } )) + .disabled(!isEffectivelyOnline) // Confirmed against a live server: there's no per-document embeds // field. Only a workspace-level setting exists, and that's not // reachable via the API either (no `team.update` endpoint in the @@ -386,6 +412,7 @@ struct DocumentReaderView: View { Button("Delete…", role: .destructive) { isShowingDeleteConfirmation = true } + .disabled(!isEffectivelyOnline) } private func star() async {