diff --git a/App/App_macOS.swift b/App/App_macOS.swift index 531286e4..0f6dc896 100644 --- a/App/App_macOS.swift +++ b/App/App_macOS.swift @@ -18,7 +18,6 @@ import UserNotifications import Combine import Defaults import CoreKiwix -import PassKit #if os(macOS) final class AppDelegate: NSObject, NSApplicationDelegate { @@ -37,6 +36,7 @@ struct Kiwix: App { @State private var selectedAmount: SelectedAmount? @StateObject var formReset = FormReset() @FocusState private var isSearchFocused: Bool + @FocusedValue(\.browserURL) var browserURL init() { UNUserNotificationCenter.current().delegate = notificationCenterDelegate @@ -78,6 +78,16 @@ struct Kiwix: App { SidebarNavigationCommands() Divider() } + CommandGroup(after: .pasteboard) { + Button(LocalString.library_zim_file_context_copy_url) { + if let browserURL { + CopyPasteMenu.copyToPasteBoard(url: browserURL) + } + } + .disabled(browserURL == nil) + .keyboardShortcut("c", modifiers: [.command, .shift]) + + } CommandGroup(after: .textEditing) { Button(LocalString.common_search) { isSearchFocused = true @@ -169,231 +179,4 @@ struct Kiwix: App { } } -struct RootView: View { - @Environment(\.openWindow) var openWindow - @Environment(\.controlActiveState) var controlActiveState - @StateObject private var navigation = NavigationViewModel() - @State private var currentNavItem: MenuItem? - @StateObject private var windowTracker = WindowTracker() - @State private var paymentButtonLabel: PayWithApplePayButtonLabel? - var isSearchFocused: FocusState.Binding - @StateObject private var selection = SelectedZimFileViewModel() - // Open file alerts - @State private var isOpenFileAlertPresented = false - @State private var openFileAlert: OpenFileAlert? - - private let primaryItems: [MenuItem] = [.bookmarks] - private let libraryItems: [MenuItem] = [.opened, .categories, .downloads, .new] - private let openURL = NotificationCenter.default.publisher(for: .openURL) - private let appTerminates = NotificationCenter.default.publisher(for: NSApplication.willTerminateNotification) - private let tabCloses = NotificationCenter.default.publisher(for: NSWindow.willCloseNotification) - private let goBackPublisher = NotificationCenter.default.publisher(for: .goBack) - private let goForwardPublisher = NotificationCenter.default.publisher(for: .goForward) - /// Close other tabs then the ones received - private let keepOnlyTabs = NotificationCenter.default.publisher(for: .keepOnlyTabs) - // in essence it's the "zim://" value - private static let zimURL: String = "\(KiwixURLSchemeHandler.ZIMScheme)://" - - var body: some View { - NavigationSplitView { - List(selection: $currentNavItem) { - ForEach( - [MenuItem.tab(objectID: navigation.currentTabId)] + primaryItems, - id: \.self - ) { menuItem in - Label(menuItem.name, systemImage: menuItem.icon) - } - if FeatureFlags.hasLibrary { - Section(LocalString.app_macos_navigation_button_library) { - ForEach(libraryItems, id: \.self) { menuItem in - Label(menuItem.name, systemImage: menuItem.icon) - } - } - } - } - .frame(minWidth: 160) - .safeAreaInset(edge: .bottom) { - if paymentButtonLabel != nil && Brand.hideDonation != true { - SupportKiwixButton { - openWindow(id: "donation") - } - } - } - } detail: { - switch navigation.currentItem { - case .loading: - LoadingDataView() - case .tab(let tabID): - BrowserTab(tabID: tabID) - .modifier(SearchFocused(isSearchFocused: isSearchFocused)) - case .bookmarks: - Bookmarks() - .modifier(SearchFocused(isSearchFocused: isSearchFocused)) - case .opened: - ZimFilesMultiOpened() - case .categories: - DetailSidePanel(content: { ZimFilesCategories(dismiss: nil) }) - .modifier(SearchFocused(isSearchFocused: isSearchFocused)) - case .downloads: - DetailSidePanel(content: { ZimFilesDownloads(dismiss: nil) }) - case .new: - DetailSidePanel(content: { ZimFilesNew(dismiss: nil) }) - .modifier(SearchFocused(isSearchFocused: isSearchFocused)) - default: - EmptyView() - } - } - .frame(minWidth: 650, minHeight: 500) - .focusedSceneValue(\.navigationItem, $navigation.currentItem) - .modifier(AlertHandler()) - .modifier(OpenFileHandler()) - .modifier(SaveContentHandler()) - .environmentObject(navigation) - .onChange(of: currentNavItem) { newValue in - navigation.currentItem = newValue?.navigationItem - } - .onChange(of: navigation.currentItem) { newValue in - guard let newValue else { return } - let navItem = MenuItem(from: newValue) - if currentNavItem != navItem { - currentNavItem = navItem - } - } - .onOpenURL { url in - /// if the app was just started via URL or file (wasn't open before) - /// we want to load the content in the first window - /// otherwise in a new tab (but within the currently active window) - let isAppStart = NSApplication.shared.windows.count == 1 - if url.isFileURL { - // from opening an external file - let browser = BrowserViewModel.getCached(tabID: navigation.currentTabId) - if isAppStart { - browser.forceLoadingState() - } - Task { // open the ZIM file - if let metadata = await LibraryOperations.open(url: url), - let mainPageURL = await ZimFileService.shared.getMainPageURL(zimFileID: metadata.fileID) { - if isAppStart { - browser.load(url: mainPageURL) - } else { - browser.createNewWindow(with: mainPageURL) - } - } else { - await browser.clear() - isOpenFileAlertPresented = true - openFileAlert = .unableToOpen(filenames: [url.lastPathComponent]) - } - } - } else if url.isZIMURL { - // from deeplinks - if isAppStart { - let browser = BrowserViewModel.getCached(tabID: navigation.currentTabId) - browser.load(url: url) - } else { - Task { @MainActor in - // we want to open the deeplink a new tab (in the currently active window) - // at this point though, the latest tab is active, that received the deeplink handling - // therefore we do need to wait 1 UI cycle, to finish the original - // deeplink handling (hence the Task { @MainActor solution) - // This way we can activate the newly opened tab with the deeplink content in it - let browser = BrowserViewModel.getCached(tabID: navigation.currentTabId) - browser.createNewWindow(with: url) - } - } - } - } - .alert(LocalString.file_import_alert_no_open_title, - isPresented: $isOpenFileAlertPresented, presenting: openFileAlert) { _ in - } message: { alert in - switch alert { - case .unableToOpen(let filenames): - let name = ListFormatter.localizedString(byJoining: filenames) - Text(LocalString.file_import_alert_no_open_message(withArgs: name)) - } - } - .onReceive(openURL) { notification in - guard let url = notification.userInfo?["url"] as? URL else { - return - } - guard controlActiveState == .key else { return } - let tabID = navigation.currentTabId - currentNavItem = .tab(objectID: tabID) - BrowserViewModel.getCached(tabID: tabID).load(url: url) - } - .onReceive(tabCloses) { publisher in - // closing one window either by CMD+W || red(X) close button - guard windowTracker.current == publisher.object as? NSWindow else { - // when exiting full screen video, we get the same notification - // but that's not comming from our window - return - } - windowTracker.current = nil // remove the reference to this window, see guard above - - guard !navigation.isTerminating else { - // tab closed by app termination - return - } - let tabID = navigation.currentTabId - let browser = BrowserViewModel.getCached(tabID: tabID) - // tab closed by user - browser.pauseVideoWhenNotInPIP() - navigation.deleteTab(tabID: tabID) - } - .onReceive(keepOnlyTabs) {notification in - guard let tabsToKeep = notification.userInfo?["tabIds"] as? Set else { - return - } - navigation.keepOnlyTabsBy(tabIds: tabsToKeep) - } - .onReceive(appTerminates) { _ in - // CMD+Q -> Quit Kiwix, this also closes the last window - navigation.isTerminating = true - }.onReceive(goForwardPublisher) { _ in - guard case .tab(let tabID) = navigation.currentItem else { - return - } - BrowserViewModel.getCached(tabID: tabID).webView.goForward() - }.onReceive(goBackPublisher) { [weak navigation] _ in - guard case .tab(let tabID) = navigation?.currentItem else { - return - } - BrowserViewModel.getCached(tabID: tabID).webView.goBack() - }.task { - switch AppType.current { - case .kiwix: - await LibraryOperations.reopen() - currentNavItem = .tab(objectID: navigation.currentTabId) - LibraryOperations.scanDirectory(URL.documentDirectory) - LibraryOperations.applyFileBackupSetting() - DownloadService.shared.restartHeartbeatIfNeeded() - case let .custom(zimFileURL): - await LibraryOperations.open(url: zimFileURL) - ZimMigration.forCustomApps() - currentNavItem = .tab(objectID: navigation.currentTabId) - } - // MARK: - payment button init - if Brand.hideDonation == false { - paymentButtonLabel = await Payment.paymentButtonTypeAsync() - } - - // MARK: - migrations - if !ProcessInfo.processInfo.arguments.contains("testing") { - _ = MigrationService().migrateAll() - } - } - // special hook to trigger the zim file search in the nav bar, when a web view is opened - // and the cmd+f is triggering the search in page - .onReceive(NotificationCenter.default.publisher(for: .zimSearch)) { _ in - isSearchFocused.wrappedValue = true - } - .withHostingWindow { [weak windowTracker] hostWindow in - windowTracker?.current = hostWindow - } - .handlesExternalEvents( - preferring: Set([Self.zimURL]), - allowing: Set([Self.zimURL, "file:///"]) - ) - } -} - #endif diff --git a/App/RootView_macOS.swift b/App/RootView_macOS.swift new file mode 100644 index 00000000..99a8a34f --- /dev/null +++ b/App/RootView_macOS.swift @@ -0,0 +1,247 @@ +// This file is part of Kiwix for iOS & macOS. +// +// Kiwix is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// any later version. +// +// Kiwix is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kiwix; If not, see https://www.gnu.org/licenses/. + +#if os(macOS) +import SwiftUI +import PassKit + +struct RootView: View { + @Environment(\.openWindow) var openWindow + @Environment(\.controlActiveState) var controlActiveState + @StateObject private var navigation = NavigationViewModel() + @State private var currentNavItem: MenuItem? + @StateObject private var windowTracker = WindowTracker() + @State private var paymentButtonLabel: PayWithApplePayButtonLabel? + var isSearchFocused: FocusState.Binding + @StateObject private var selection = SelectedZimFileViewModel() + // Open file alerts + @State private var isOpenFileAlertPresented = false + @State private var openFileAlert: OpenFileAlert? + + private let primaryItems: [MenuItem] = [.bookmarks] + private let libraryItems: [MenuItem] = [.opened, .categories, .downloads, .new] + private let openURL = NotificationCenter.default.publisher(for: .openURL) + private let appTerminates = NotificationCenter.default.publisher(for: NSApplication.willTerminateNotification) + private let tabCloses = NotificationCenter.default.publisher(for: NSWindow.willCloseNotification) + private let goBackPublisher = NotificationCenter.default.publisher(for: .goBack) + private let goForwardPublisher = NotificationCenter.default.publisher(for: .goForward) + /// Close other tabs then the ones received + private let keepOnlyTabs = NotificationCenter.default.publisher(for: .keepOnlyTabs) + // in essence it's the "zim://" value + private static let zimURL: String = "\(KiwixURLSchemeHandler.ZIMScheme)://" + + var body: some View { + NavigationSplitView { + List(selection: $currentNavItem) { + ForEach( + [MenuItem.tab(objectID: navigation.currentTabId)] + primaryItems, + id: \.self + ) { menuItem in + Label(menuItem.name, systemImage: menuItem.icon) + } + if FeatureFlags.hasLibrary { + Section(LocalString.app_macos_navigation_button_library) { + ForEach(libraryItems, id: \.self) { menuItem in + Label(menuItem.name, systemImage: menuItem.icon) + } + } + } + } + .frame(minWidth: 160) + .safeAreaInset(edge: .bottom) { + if paymentButtonLabel != nil && Brand.hideDonation != true { + SupportKiwixButton { + openWindow(id: "donation") + } + } + } + } detail: { + switch navigation.currentItem { + case .loading: + LoadingDataView() + case .tab(let tabID): + BrowserTab(tabID: tabID) + .modifier(SearchFocused(isSearchFocused: isSearchFocused)) + case .bookmarks: + Bookmarks() + .modifier(SearchFocused(isSearchFocused: isSearchFocused)) + case .opened: + ZimFilesMultiOpened() + case .categories: + DetailSidePanel(content: { ZimFilesCategories(dismiss: nil) }) + .modifier(SearchFocused(isSearchFocused: isSearchFocused)) + case .downloads: + DetailSidePanel(content: { ZimFilesDownloads(dismiss: nil) }) + case .new: + DetailSidePanel(content: { ZimFilesNew(dismiss: nil) }) + .modifier(SearchFocused(isSearchFocused: isSearchFocused)) + default: + EmptyView() + } + } + .frame(minWidth: 650, minHeight: 500) + .focusedSceneValue(\.navigationItem, $navigation.currentItem) + .modifier(AlertHandler()) + .modifier(OpenFileHandler()) + .modifier(SaveContentHandler()) + .environmentObject(navigation) + .onChange(of: currentNavItem) { newValue in + navigation.currentItem = newValue?.navigationItem + } + .onChange(of: navigation.currentItem) { newValue in + guard let newValue else { return } + let navItem = MenuItem(from: newValue) + if currentNavItem != navItem { + currentNavItem = navItem + } + } + .onOpenURL { url in + /// if the app was just started via URL or file (wasn't open before) + /// we want to load the content in the first window + /// otherwise in a new tab (but within the currently active window) + let isAppStart = NSApplication.shared.windows.count == 1 + if url.isFileURL { + // from opening an external file + let browser = BrowserViewModel.getCached(tabID: navigation.currentTabId) + if isAppStart { + browser.forceLoadingState() + } + Task { // open the ZIM file + if let metadata = await LibraryOperations.open(url: url), + let mainPageURL = await ZimFileService.shared.getMainPageURL(zimFileID: metadata.fileID) { + if isAppStart { + browser.load(url: mainPageURL) + } else { + browser.createNewWindow(with: mainPageURL) + } + } else { + await browser.clear() + isOpenFileAlertPresented = true + openFileAlert = .unableToOpen(filenames: [url.lastPathComponent]) + } + } + } else if url.isZIMURL { + // from deeplinks + if isAppStart { + let browser = BrowserViewModel.getCached(tabID: navigation.currentTabId) + browser.load(url: url) + } else { + Task { @MainActor in + // we want to open the deeplink a new tab (in the currently active window) + // at this point though, the latest tab is active, that received the deeplink handling + // therefore we do need to wait 1 UI cycle, to finish the original + // deeplink handling (hence the Task { @MainActor solution) + // This way we can activate the newly opened tab with the deeplink content in it + let browser = BrowserViewModel.getCached(tabID: navigation.currentTabId) + browser.createNewWindow(with: url) + } + } + } + } + .alert(LocalString.file_import_alert_no_open_title, + isPresented: $isOpenFileAlertPresented, presenting: openFileAlert) { _ in + } message: { alert in + switch alert { + case .unableToOpen(let filenames): + let name = ListFormatter.localizedString(byJoining: filenames) + Text(LocalString.file_import_alert_no_open_message(withArgs: name)) + } + } + .onReceive(openURL) { notification in + guard let url = notification.userInfo?["url"] as? URL else { + return + } + guard controlActiveState == .key else { return } + let tabID = navigation.currentTabId + currentNavItem = .tab(objectID: tabID) + BrowserViewModel.getCached(tabID: tabID).load(url: url) + } + .onReceive(tabCloses) { publisher in + // closing one window either by CMD+W || red(X) close button + guard windowTracker.current == publisher.object as? NSWindow else { + // when exiting full screen video, we get the same notification + // but that's not comming from our window + return + } + windowTracker.current = nil // remove the reference to this window, see guard above + + guard !navigation.isTerminating else { + // tab closed by app termination + return + } + let tabID = navigation.currentTabId + let browser = BrowserViewModel.getCached(tabID: tabID) + // tab closed by user + browser.pauseVideoWhenNotInPIP() + navigation.deleteTab(tabID: tabID) + } + .onReceive(keepOnlyTabs) {notification in + guard let tabsToKeep = notification.userInfo?["tabIds"] as? Set else { + return + } + navigation.keepOnlyTabsBy(tabIds: tabsToKeep) + } + .onReceive(appTerminates) { _ in + // CMD+Q -> Quit Kiwix, this also closes the last window + navigation.isTerminating = true + }.onReceive(goForwardPublisher) { _ in + guard case .tab(let tabID) = navigation.currentItem else { + return + } + BrowserViewModel.getCached(tabID: tabID).webView.goForward() + }.onReceive(goBackPublisher) { [weak navigation] _ in + guard case .tab(let tabID) = navigation?.currentItem else { + return + } + BrowserViewModel.getCached(tabID: tabID).webView.goBack() + }.task { + switch AppType.current { + case .kiwix: + await LibraryOperations.reopen() + currentNavItem = .tab(objectID: navigation.currentTabId) + LibraryOperations.scanDirectory(URL.documentDirectory) + LibraryOperations.applyFileBackupSetting() + DownloadService.shared.restartHeartbeatIfNeeded() + case let .custom(zimFileURL): + await LibraryOperations.open(url: zimFileURL) + ZimMigration.forCustomApps() + currentNavItem = .tab(objectID: navigation.currentTabId) + } + // MARK: - payment button init + if Brand.hideDonation == false { + paymentButtonLabel = await Payment.paymentButtonTypeAsync() + } + + // MARK: - migrations + if !ProcessInfo.processInfo.arguments.contains("testing") { + _ = MigrationService().migrateAll() + } + } + // special hook to trigger the zim file search in the nav bar, when a web view is opened + // and the cmd+f is triggering the search in page + .onReceive(NotificationCenter.default.publisher(for: .zimSearch)) { _ in + isSearchFocused.wrappedValue = true + } + .withHostingWindow { [weak windowTracker] hostWindow in + windowTracker?.current = hostWindow + } + .handlesExternalEvents( + preferring: Set([Self.zimURL]), + allowing: Set([Self.zimURL, "file:///"]) + ) + } +} + +#endif diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index a4c3f5c2..6f47b159 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -39,6 +39,7 @@ "common.button.no" = "no"; "common.button.print" = "Print"; "common.button.share" = "Share"; +"common.button.share_as_pdf" = "Share as PDF"; "common.search" = "Search"; "common.tab.manager.title" = "Tabs Manager"; diff --git a/Support/qqq.lproj/Localizable.strings b/Support/qqq.lproj/Localizable.strings index 9cc6b82d..75e0091c 100644 --- a/Support/qqq.lproj/Localizable.strings +++ b/Support/qqq.lproj/Localizable.strings @@ -23,6 +23,7 @@ "common.button.no" = "It is a title in the summary table on macOS: we list the attributes of a ZIM file: does it contain pictures yes/no, does it contain Videos yes/no, Details? yes/no"; "common.button.print" = "Accessibility label for button to print the currently loaded article"; "common.button.share" = "Accessibility label for share button, to share the currently loaded article with an external app."; +"common.button.share_as_pdf" = "Accesibility label for share button. to share the curretly loadded article with an external application in a PDF file format."; "common.search" = "The default placeholder text for searchbars, when the search input field is empty"; "common.tab.manager.title" = "Accessibility label for button tab bar button that opens an overlay menu."; "common.tab.navigation.title" = "On iPad it is a title in the sidemenu grouping tabs related buttons"; diff --git a/Views/BrowserTab.swift b/Views/BrowserTab.swift index 2b14a98e..64bf3e43 100644 --- a/Views/BrowserTab.swift +++ b/Views/BrowserTab.swift @@ -77,12 +77,21 @@ struct BrowserTab: View { } #else if !Brand.hideShareButton { - ExportButton( - relativeToView: browser.webView, - webViewURL: browser.webView.url, - pageDataWithExtension: { [weak browser] in await browser?.pageDataWithExtension() }, - isButtonDisabled: browser.zimFileName.isEmpty - ) + Menu { + ExportButton( + relativeToView: browser.webView, + webViewURL: browser.webView.url, + pageDataWithExtension: { [weak browser] in await browser?.pageDataWithExtension() }, + isButtonDisabled: browser.zimFileName.isEmpty, + buttonLabel: LocalString.common_button_share_as_pdf + ) + if let url = browser.webView.url { + CopyPasteMenu(url: url) + .keyboardShortcut("c", modifiers: [.command, .shift]) + } + } label: { + Label(LocalString.common_button_share, systemImage: "square.and.arrow.up") + }.disabled(browser.webView.url == nil) } if !Brand.hidePrintButton { PrintButton(browserURLName: { [weak browser] in @@ -112,6 +121,9 @@ struct BrowserTab: View { } .environmentObject(search) .focusedSceneValue(\.isBrowserURLSet, browser.url != nil) + #if os(macOS) + .focusedSceneValue(\.browserURL, browser.url) + #endif .focusedSceneValue(\.canGoBack, browser.canGoBack) .focusedSceneValue(\.canGoForward, browser.canGoForward) .modifier(ExternalLinkHandler(externalURL: $browser.externalURL)) diff --git a/Views/BuildingBlocks/CopyPasteMenu.swift b/Views/BuildingBlocks/CopyPasteMenu.swift index fe5cc061..5a27f65a 100644 --- a/Views/BuildingBlocks/CopyPasteMenu.swift +++ b/Views/BuildingBlocks/CopyPasteMenu.swift @@ -18,18 +18,24 @@ import UniformTypeIdentifiers struct CopyPasteMenu: View { - let downloadURL: URL + let url: URL var body: some View { Button { #if os(macOS) - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(downloadURL.absoluteString, forType: .string) + Self.copyToPasteBoard(url: url) #elseif os(iOS) - UIPasteboard.general.setValue(downloadURL.absoluteString, forPasteboardType: UTType.url.identifier) + UIPasteboard.general.setValue(url.absoluteString, forPasteboardType: UTType.url.identifier) #endif } label: { Label(LocalString.library_zim_file_context_copy_url, systemImage: "doc.on.doc") } } + + #if os(macOS) + public static func copyToPasteBoard(url: URL) { + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(url.absoluteString, forType: .string) + } + #endif } diff --git a/Views/Buttons/ExportButton.swift b/Views/Buttons/ExportButton.swift index 91d4821a..b383dfc2 100644 --- a/Views/Buttons/ExportButton.swift +++ b/Views/Buttons/ExportButton.swift @@ -23,6 +23,8 @@ struct ExportButton: View { let webViewURL: URL? let pageDataWithExtension: () async -> (Data, String?)? let isButtonDisabled: Bool + + var buttonLabel: String = LocalString.common_button_share /// - Returns: Returns the browser data, fileName and extension private func dataNameAndExtension() async -> FileExportData? { @@ -48,12 +50,22 @@ struct ExportButton: View { NotificationCenter.exportFileData(exportData) #else guard let url = await tempFileURL() else { return } - NSSharingServicePicker(items: [url]).show(relativeTo: .null, of: relativeToView, preferredEdge: .minY) + NSSharingServicePicker(items: [url]).show( + relativeTo: NSRect( + origin: .zero, + size: CGSize( + width: 640, + height: 54 + ) + ), + of: relativeToView, + preferredEdge: .minY + ) #endif } } label: { Label { - Text(LocalString.common_button_share) + Text(buttonLabel) } icon: { Image(systemName: "square.and.arrow.up") } diff --git a/Views/Commands.swift b/Views/Commands.swift index 262bbaee..8ddbd1f1 100644 --- a/Views/Commands.swift +++ b/Views/Commands.swift @@ -25,6 +25,10 @@ struct IsBrowserURLSet: FocusedValueKey { typealias Value = Bool } +struct BrowserURL: FocusedValueKey { + typealias Value = URL +} + struct CanGoBackKey: FocusedValueKey { typealias Value = Bool } @@ -38,6 +42,12 @@ struct NavigationItemKey: FocusedValueKey { } extension FocusedValues { + #if os(macOS) + var browserURL: BrowserURL.Value? { + get { self[BrowserURL.self] } + set { self[BrowserURL.self] = newValue} + } + #endif var isBrowserURLSet: IsBrowserURLSet.Value? { get { self[IsBrowserURLSet.self] } set { self[IsBrowserURLSet.self] = newValue } diff --git a/Views/Library/ZimFileContextMenu.swift b/Views/Library/ZimFileContextMenu.swift index 8924bdc7..05def96c 100644 --- a/Views/Library/ZimFileContextMenu.swift +++ b/Views/Library/ZimFileContextMenu.swift @@ -23,7 +23,7 @@ struct ZimFileContextMenu: View { Section { ArticleActions(zimFileID: zimFile.fileID) } } if let downloadURL = zimFile.downloadURL { - Section { CopyPasteMenu(downloadURL: downloadURL) } + Section { CopyPasteMenu(url: downloadURL) } } } } diff --git a/Views/ViewModifiers/CopyURLContext.swift b/Views/ViewModifiers/CopyURLContext.swift new file mode 100644 index 00000000..a564e641 --- /dev/null +++ b/Views/ViewModifiers/CopyURLContext.swift @@ -0,0 +1,34 @@ +// This file is part of Kiwix for iOS & macOS. +// +// Kiwix is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// any later version. +// +// Kiwix is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kiwix; If not, see https://www.gnu.org/licenses/. + +#if os(macOS) +import SwiftUI + +struct CopyURLContext: ViewModifier { + + @State var url: URL? + + func body(content: Content) -> some View { + if let url { + content.contextMenu { + CopyPasteMenu(url: url) + } + } else { + content + } + } +} + +#endif