// // NavigationManager.swift // Gemini // // Created by Shadowfacts on 7/14/20. // import Foundation import Combine public protocol NavigationManagerDelegate: AnyObject { func loadNonGeminiURL(_ url: URL) } public class NavigationManager: NSObject, ObservableObject { public weak var delegate: NavigationManagerDelegate? @Published public var currentURL: URL @Published public var backStack = [URL]() @Published public var forwardStack = [URL]() public let navigationOperation = PassthroughSubject() public var displayURL: String { var components = URLComponents(url: currentURL, resolvingAgainstBaseURL: false)! if components.port == 1965 { components.port = nil } return components.string! } public init(url: URL) { self.currentURL = url } public func changeURL(_ url: URL) { var components = URLComponents(url: url, resolvingAgainstBaseURL: false)! if let scheme = url.scheme { if scheme != "gemini" { delegate?.loadNonGeminiURL(url) return } } else { components.scheme = "gemini" } // Foundation parses bare hosts (e.g. `example.com`) as having no host and a path of `example.com` if components.host == nil { components.host = components.path components.path = "/" } // Some Gemini servers break on empty paths if components.path.isEmpty { components.path = "/" } let url = components.url! backStack.append(currentURL) currentURL = url forwardStack = [] navigationOperation.send(.go) } @objc public func reload() { let url = currentURL currentURL = url navigationOperation.send(.reload) } @objc public func goBack() { back(count: 1) } public func back(count: Int) { guard count <= backStack.count else { return } var removed = backStack.suffix(count) backStack.removeLast(count) forwardStack.insert(currentURL, at: 0) currentURL = removed.removeFirst() forwardStack.insert(contentsOf: removed, at: 0) navigationOperation.send(.backward(count: count)) } @objc public func goForward() { forward(count: 1) } public func forward(count: Int) { guard count <= forwardStack.count else { return } var removed = forwardStack.prefix(count) forwardStack.removeFirst(count) backStack.append(currentURL) currentURL = removed.removeLast() backStack.append(contentsOf: removed) navigationOperation.send(.forward(count: count)) } } public extension NavigationManager { enum Operation { case go, reload, forward(count: Int), backward(count: Int) } }