2020-07-15 03:15:56 +00:00
|
|
|
//
|
|
|
|
// NavigationManager.swift
|
|
|
|
// Gemini
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 7/14/20.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
2021-09-29 02:13:28 +00:00
|
|
|
import Combine
|
2020-07-15 03:15:56 +00:00
|
|
|
|
2021-06-16 02:37:01 +00:00
|
|
|
public protocol NavigationManagerDelegate: AnyObject {
|
2020-09-28 02:26:44 +00:00
|
|
|
func loadNonGeminiURL(_ url: URL)
|
|
|
|
}
|
|
|
|
|
2021-09-29 02:13:28 +00:00
|
|
|
public class NavigationManager: NSObject, ObservableObject, Codable {
|
2020-07-15 03:15:56 +00:00
|
|
|
|
2020-09-28 02:26:44 +00:00
|
|
|
public weak var delegate: NavigationManagerDelegate?
|
|
|
|
|
2020-07-16 03:45:37 +00:00
|
|
|
@Published public var currentURL: URL
|
2021-09-29 02:13:28 +00:00
|
|
|
@Published public var backStack = [HistoryEntry]()
|
|
|
|
@Published public var forwardStack = [HistoryEntry]()
|
2020-07-15 03:15:56 +00:00
|
|
|
|
2020-12-18 03:27:23 +00:00
|
|
|
public let navigationOperation = PassthroughSubject<Operation, Never>()
|
|
|
|
|
2020-09-29 20:28:05 +00:00
|
|
|
public var displayURL: String {
|
|
|
|
var components = URLComponents(url: currentURL, resolvingAgainstBaseURL: false)!
|
|
|
|
if components.port == 1965 {
|
|
|
|
components.port = nil
|
|
|
|
}
|
|
|
|
return components.string!
|
|
|
|
}
|
|
|
|
|
2021-09-29 02:13:28 +00:00
|
|
|
private var currentHistoryEntry: HistoryEntry
|
|
|
|
|
2020-07-16 03:45:37 +00:00
|
|
|
public init(url: URL) {
|
2020-07-15 03:15:56 +00:00
|
|
|
self.currentURL = url
|
2021-09-29 02:13:28 +00:00
|
|
|
self.currentHistoryEntry = HistoryEntry(url: url, title: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
public required init(from decoder: Decoder) throws {
|
|
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
self.currentHistoryEntry = try container.decode(HistoryEntry.self, forKey: .currentHistoryEntry)
|
|
|
|
self.currentURL = self.currentHistoryEntry.url
|
|
|
|
self.backStack = try container.decode([HistoryEntry].self, forKey: .backStack)
|
|
|
|
self.forwardStack = try container.decode([HistoryEntry].self, forKey: .forwardStack)
|
|
|
|
}
|
|
|
|
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
try container.encode(currentHistoryEntry, forKey: .currentHistoryEntry)
|
|
|
|
try container.encode(backStack, forKey: .backStack)
|
|
|
|
try container.encode(forwardStack, forKey: .forwardStack)
|
|
|
|
}
|
|
|
|
|
|
|
|
public func setTitleForCurrentURL(_ title: String?) {
|
|
|
|
currentHistoryEntry.title = title
|
2020-07-15 03:15:56 +00:00
|
|
|
}
|
|
|
|
|
2020-07-16 03:45:37 +00:00
|
|
|
public func changeURL(_ url: URL) {
|
2020-09-28 19:27:19 +00:00
|
|
|
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 = "/"
|
2020-09-28 02:26:44 +00:00
|
|
|
}
|
|
|
|
|
2020-09-29 20:28:17 +00:00
|
|
|
// Some Gemini servers break on empty paths
|
|
|
|
if components.path.isEmpty {
|
|
|
|
components.path = "/"
|
|
|
|
}
|
|
|
|
|
2020-09-28 19:27:19 +00:00
|
|
|
let url = components.url!
|
|
|
|
|
2021-09-29 02:13:28 +00:00
|
|
|
backStack.append(currentHistoryEntry)
|
2020-09-28 19:27:19 +00:00
|
|
|
currentURL = url
|
2021-09-29 02:13:28 +00:00
|
|
|
currentHistoryEntry = HistoryEntry(url: url, title: nil)
|
2020-07-15 13:18:37 +00:00
|
|
|
forwardStack = []
|
2020-12-18 03:27:23 +00:00
|
|
|
|
|
|
|
navigationOperation.send(.go)
|
2020-07-15 13:18:37 +00:00
|
|
|
}
|
|
|
|
|
2020-12-19 20:19:32 +00:00
|
|
|
@objc public func reload() {
|
2020-09-28 02:11:34 +00:00
|
|
|
let url = currentURL
|
|
|
|
currentURL = url
|
2020-12-21 03:27:59 +00:00
|
|
|
navigationOperation.send(.reload)
|
2020-09-28 02:11:34 +00:00
|
|
|
}
|
|
|
|
|
2020-12-19 20:19:32 +00:00
|
|
|
@objc public func goBack() {
|
2020-12-18 03:27:23 +00:00
|
|
|
back(count: 1)
|
2020-07-15 13:18:37 +00:00
|
|
|
}
|
|
|
|
|
2020-09-30 03:36:24 +00:00
|
|
|
public func back(count: Int) {
|
|
|
|
guard count <= backStack.count else { return }
|
|
|
|
var removed = backStack.suffix(count)
|
|
|
|
backStack.removeLast(count)
|
2021-09-29 02:13:28 +00:00
|
|
|
forwardStack.insert(currentHistoryEntry, at: 0)
|
|
|
|
currentHistoryEntry = removed.removeFirst()
|
|
|
|
currentURL = currentHistoryEntry.url
|
2020-09-30 03:36:24 +00:00
|
|
|
forwardStack.insert(contentsOf: removed, at: 0)
|
2020-12-18 03:27:23 +00:00
|
|
|
|
|
|
|
navigationOperation.send(.backward(count: count))
|
2020-09-30 03:36:24 +00:00
|
|
|
}
|
|
|
|
|
2020-12-19 20:19:32 +00:00
|
|
|
@objc public func goForward() {
|
2020-12-18 03:27:23 +00:00
|
|
|
forward(count: 1)
|
2020-07-15 03:15:56 +00:00
|
|
|
}
|
|
|
|
|
2020-09-30 03:36:24 +00:00
|
|
|
public func forward(count: Int) {
|
|
|
|
guard count <= forwardStack.count else { return }
|
|
|
|
var removed = forwardStack.prefix(count)
|
|
|
|
forwardStack.removeFirst(count)
|
2021-09-29 02:13:28 +00:00
|
|
|
backStack.append(currentHistoryEntry)
|
|
|
|
currentHistoryEntry = removed.removeLast()
|
|
|
|
currentURL = currentHistoryEntry.url
|
2020-09-30 03:36:24 +00:00
|
|
|
backStack.append(contentsOf: removed)
|
2020-12-18 03:27:23 +00:00
|
|
|
|
|
|
|
navigationOperation.send(.forward(count: count))
|
2020-09-30 03:36:24 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 03:15:56 +00:00
|
|
|
}
|
2020-12-18 03:27:23 +00:00
|
|
|
|
2021-09-29 02:13:28 +00:00
|
|
|
extension NavigationManager {
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
|
|
case currentHistoryEntry
|
|
|
|
case backStack
|
|
|
|
case forwardStack
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-18 03:27:23 +00:00
|
|
|
public extension NavigationManager {
|
|
|
|
enum Operation {
|
2020-12-21 03:27:59 +00:00
|
|
|
case go, reload, forward(count: Int), backward(count: Int)
|
2020-12-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
}
|
2021-09-29 02:13:28 +00:00
|
|
|
|
|
|
|
public extension NavigationManager {
|
|
|
|
struct HistoryEntry: Codable {
|
|
|
|
public let url: URL
|
|
|
|
public internal(set) var title: String?
|
|
|
|
}
|
|
|
|
}
|