diff --git a/ViewModel/BrowserNavDelegate.swift b/ViewModel/BrowserNavDelegate.swift index 70c323a2..527948cf 100644 --- a/ViewModel/BrowserNavDelegate.swift +++ b/ViewModel/BrowserNavDelegate.swift @@ -5,16 +5,16 @@ // Copyright © 2023 Chris Li. All rights reserved. // -import WebKit import CoreLocation +import WebKit final class BrowserNavDelegate: NSObject, WKNavigationDelegate { - @Published private(set) var externalURL: URL? func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) + { guard let url = navigationAction.request.url else { decisionHandler(.cancel); return } if url.isKiwixURL, let redirectedURL = ZimFileService.shared.getRedirectedURL(url: url) { DispatchQueue.main.async { webView.load(URLRequest(url: redirectedURL)) } @@ -38,9 +38,9 @@ final class BrowserNavDelegate: NSObject, WKNavigationDelegate { let coordinate = url.absoluteString.replacingOccurrences(of: "geo:", with: "") if let url = URL(string: "http://maps.apple.com/?ll=\(coordinate)") { #if os(macOS) - NSWorkspace.shared.open(url) + NSWorkspace.shared.open(url) #elseif os(iOS) - UIApplication.shared.open(url) + UIApplication.shared.open(url) #endif } } @@ -50,14 +50,14 @@ final class BrowserNavDelegate: NSObject, WKNavigationDelegate { } } - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + func webView(_ webView: WKWebView, didFinish _: WKNavigation!) { webView.evaluateJavaScript("expandAllDetailTags(); getOutlineItems();") #if os(iOS) - webView.adjustTextSize() + webView.adjustTextSize() #endif } - func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + func webView(_: WKWebView, didFailProvisionalNavigation _: WKNavigation!, withError error: Error) { let error = error as NSError guard error.code != NSURLErrorCancelled else { return } NotificationCenter.default.post( diff --git a/ViewModel/BrowserScriptHandler.swift b/ViewModel/BrowserScriptHandler.swift index 696abaf4..1a2230a5 100644 --- a/ViewModel/BrowserScriptHandler.swift +++ b/ViewModel/BrowserScriptHandler.swift @@ -11,7 +11,7 @@ final class BrowserScriptHandler: NSObject, WKScriptMessageHandler { @Published private(set) var outlineItems = [OutlineItem]() @Published private(set) var outlineItemTree = [OutlineItem]() - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + func userContentController(_: WKUserContentController, didReceive message: WKScriptMessage) { if message.name == "headings", let headings = message.body as? [[String: String]] { DispatchQueue.global(qos: .userInitiated).async { self.generateOutlineList(headings: headings) @@ -24,7 +24,7 @@ final class BrowserScriptHandler: NSObject, WKScriptMessageHandler { /// - Parameter headings: list of heading element data retrieved from webview private func generateOutlineList(headings: [[String: String]]) { let allLevels = headings.compactMap { Int($0["tag"]?.suffix(1) ?? "") } - let offset = allLevels.filter({ $0 == 1 }).count == 1 ? 2 : allLevels.min() ?? 0 + let offset = allLevels.filter { $0 == 1 }.count == 1 ? 2 : allLevels.min() ?? 0 let outlineItems: [OutlineItem] = headings.enumerated().compactMap { index, heading in guard let id = heading["id"], let text = heading["text"], diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 5aeb0b8a..a1aae6c7 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -14,17 +14,17 @@ import WebKit import OrderedCollections final class BrowserViewModel: NSObject, ObservableObject, - NSFetchedResultsControllerDelegate + NSFetchedResultsControllerDelegate { - static private var cache = OrderedDictionary() - + private static var cache = OrderedDictionary() + static func getCached(tabID: NSManagedObjectID) -> BrowserViewModel { let viewModel = cache[tabID] ?? BrowserViewModel(tabID: tabID) cache.removeValue(forKey: tabID) cache[tabID] = viewModel return viewModel } - + static func purgeCache() { guard cache.count > 10 else { return } let range = 0 ..< cache.count - 5 @@ -33,9 +33,9 @@ final class BrowserViewModel: NSObject, ObservableObject, } cache.removeSubrange(range) } - + // MARK: - Properties - + @Published private(set) var canGoBack = false @Published private(set) var canGoForward = false @Published private(set) var articleTitle: String = "" @@ -60,10 +60,10 @@ final class BrowserViewModel: NSObject, ObservableObject, private var cancellables: Set = [] // MARK: - Lifecycle - + init(tabID: NSManagedObjectID? = nil) { self.tabID = tabID - self.webView = WKWebView(frame: .zero, configuration: WebViewConfiguration()) + webView = WKWebView(frame: .zero, configuration: WebViewConfiguration()) scriptHandler = BrowserScriptHandler() navDelegate = BrowserNavDelegate() uiDelegate = BrowserUIDelegate() @@ -93,7 +93,7 @@ final class BrowserViewModel: NSObject, ObservableObject, // configure web view webView.allowsBackForwardNavigationGestures = true - webView.configuration.defaultWebpagePreferences.preferredContentMode = .mobile // for font adjustment to work + webView.configuration.defaultWebpagePreferences.preferredContentMode = .mobile // for font adjustment to work webView.configuration.userContentController.removeScriptMessageHandler(forName: "headings") webView.configuration.userContentController.add(scriptHandler, name: "headings") webView.navigationDelegate = navDelegate @@ -103,7 +103,7 @@ final class BrowserViewModel: NSObject, ObservableObject, if webView.url != nil { webView.evaluateJavaScript("getOutlineItems();") } - + // setup web view property observers canGoBackObserver = webView.observe(\.canGoBack, options: .initial) { [weak self] webView, _ in self?.canGoBack = webView.canGoBack @@ -129,24 +129,25 @@ final class BrowserViewModel: NSObject, ObservableObject, guard let url, let zimFileID = UUID(uuidString: url.host ?? "") else { return nil } return try? Database.viewContext.fetch(ZimFile.fetchRequest(fileID: zimFileID)).first }() - + // update view model self?.articleTitle = title ?? "" self?.zimFileName = zimFile?.name ?? "" self?.url = url - + // update tab data if let tabID = self?.tabID, let tab = try? Database.viewContext.existingObject(with: tabID) as? Tab, - let title { + let title + { tab.title = title tab.zimFile = zimFile } - + // setup bookmark fetched results controller self?.bookmarkFetchedResultsController = NSFetchedResultsController( fetchRequest: Bookmark.fetchRequest(predicate: { - if let url = url { + if let url { return NSPredicate(format: "articleURL == %@", url as CVarArg) } else { return NSPredicate(format: "articleURL == nil") @@ -160,44 +161,45 @@ final class BrowserViewModel: NSObject, ObservableObject, try? self?.bookmarkFetchedResultsController?.performFetch() } } - + func updateLastOpened() { guard let tabID, let tab = try? Database.viewContext.existingObject(with: tabID) as? Tab else { return } tab.lastOpened = Date() } - + func persistState() { guard let tabID, let tab = try? Database.viewContext.existingObject(with: tabID) as? Tab else { return } tab.interactionState = webView.interactionState as? Data try? Database.viewContext.save() } - + // MARK: - Content Loading - + func load(url: URL) { guard webView.url != url else { return } webView.load(URLRequest(url: url)) } - + func loadRandomArticle(zimFileID: UUID? = nil) { let zimFileID = zimFileID ?? UUID(uuidString: webView.url?.host ?? "") guard let url = ZimFileService.shared.getRandomPageURL(zimFileID: zimFileID) else { return } load(url: url) } - + func loadMainArticle(zimFileID: UUID? = nil) { let zimFileID = zimFileID ?? UUID(uuidString: webView.url?.host ?? "") guard let url = ZimFileService.shared.getMainPageURL(zimFileID: zimFileID) else { return } load(url: url) } - + // MARK: - Bookmark - - func controller(_ controller: NSFetchedResultsController, - didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { + + func controller(_: NSFetchedResultsController, + didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) + { articleBookmarked = !snapshot.itemIdentifiers.isEmpty } - + func createBookmark(url: URL? = nil) { guard let url = url ?? webView.url else { return } Database.performBackgroundTask { context in @@ -217,7 +219,7 @@ final class BrowserViewModel: NSObject, ObservableObject, try? context.save() } } - + func deleteBookmark(url: URL? = nil) { guard let url = url ?? webView.url else { return } Database.performBackgroundTask { context in @@ -227,9 +229,9 @@ final class BrowserViewModel: NSObject, ObservableObject, try? context.save() } } - + // MARK: - Outline - + /// Scroll to an outline item /// - Parameter outlineItemID: ID of the outline item to scroll to func scrollTo(outlineItemID: String) {