Redirect Rules: implements redirect rules UI
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// PersonalRedirectRules.swift
|
|
||||||
// App
|
|
||||||
//
|
|
||||||
// Created by James Magahern on 9/30/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class PersonalRedirectRules
|
|
||||||
{
|
|
||||||
func redirectedURL(for url: URL) -> URL? {
|
|
||||||
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
|
|
||||||
|
|
||||||
// Rule for nitter.net
|
|
||||||
if url.host == "twitter.com" {
|
|
||||||
components.host = "nitter.net"
|
|
||||||
return components.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -97,7 +97,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
|
|||||||
preferences.allowsContentJavaScript = allowJavaScript
|
preferences.allowsContentJavaScript = allowJavaScript
|
||||||
|
|
||||||
if let url = navigationAction.request.url,
|
if let url = navigationAction.request.url,
|
||||||
let redirectedURL = redirectRules.redirectedURL(for: url)
|
let redirectedURL = Settings.shared.redirectRule(for: url)
|
||||||
{
|
{
|
||||||
tab.beginLoadingURL(redirectedURL)
|
tab.beginLoadingURL(redirectedURL)
|
||||||
decisionHandler(.cancel, preferences)
|
decisionHandler(.cancel, preferences)
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ class BrowserViewController: UIViewController
|
|||||||
internal let findOnPageController = FindOnPageViewController()
|
internal let findOnPageController = FindOnPageViewController()
|
||||||
|
|
||||||
internal let autocompleteViewController = AutocompleteViewController()
|
internal let autocompleteViewController = AutocompleteViewController()
|
||||||
internal let redirectRules = PersonalRedirectRules()
|
|
||||||
|
|
||||||
internal var policyManager: ResourcePolicyManager { tabController.policyManager }
|
internal var policyManager: ResourcePolicyManager { tabController.policyManager }
|
||||||
|
|
||||||
override var canBecomeFirstResponder: Bool { true }
|
override var canBecomeFirstResponder: Bool { true }
|
||||||
|
|||||||
@@ -29,17 +29,22 @@ class GenericContentView<View, Configuration> : UIView, UIContentView
|
|||||||
self.applicator = applicator
|
self.applicator = applicator
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
addSubview(view)
|
addSubview(view)
|
||||||
|
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
let guide = layoutMarginsGuide
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
view.leadingAnchor.constraint(equalTo: guide.leadingAnchor),
|
||||||
|
view.trailingAnchor.constraint(equalTo: guide.trailingAnchor),
|
||||||
|
view.topAnchor.constraint(equalTo: guide.topAnchor),
|
||||||
|
view.bottomAnchor.constraint(equalTo: guide.bottomAnchor),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
view.frame = bounds.inset(by: insets)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal func apply(_ configuration: Configuration) {
|
internal func apply(_ configuration: Configuration) {
|
||||||
applicator(configuration, view)
|
applicator(configuration, view)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,52 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class RedirectRulesSettingsViewController: UIViewController
|
class RedirectRulesSettingsViewController: UICollectionViewController
|
||||||
{
|
{
|
||||||
|
private enum Section {
|
||||||
|
case rules
|
||||||
|
case addButton
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Items: String {
|
||||||
|
case addButton
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var dataSource: UICollectionViewDiffableDataSource<Section, String> = {
|
||||||
|
let registry = UICollectionView.CellRegistration<UICollectionViewListCell, String> { [unowned self] cell, indexPath, item in
|
||||||
|
var config = cell.defaultContentConfiguration()
|
||||||
|
if item == Items.addButton.rawValue {
|
||||||
|
config.text = "Add Redirect Rule"
|
||||||
|
config.textProperties.font = .preferredFont(forTextStyle: .subheadline)
|
||||||
|
config.textProperties.alignment = .center
|
||||||
|
config.image = .init(systemName: "plus.circle")
|
||||||
|
|
||||||
|
cell.accessories = []
|
||||||
|
} else {
|
||||||
|
if let rule = Settings.shared.redirectRules[item] {
|
||||||
|
config.text = "\(item) → \(rule)"
|
||||||
|
}
|
||||||
|
|
||||||
|
let deleteOptions = UICellAccessory.DeleteOptions(isHidden: false, reservedLayoutWidth: nil, tintColor: .red, backgroundColor: nil)
|
||||||
|
cell.accessories = [
|
||||||
|
.delete(displayed: .always, options: deleteOptions, actionHandler: { [unowned self] in
|
||||||
|
deleteRedirectRule(fromHost: item)
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.contentConfiguration = config
|
||||||
|
}
|
||||||
|
|
||||||
|
return UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) { (collectionView, indexPath, itemIdentifier) in
|
||||||
|
collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: itemIdentifier)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(nibName: nil, bundle: nil)
|
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
|
||||||
|
let layout = UICollectionViewCompositionalLayout.list(using: config)
|
||||||
|
super.init(collectionViewLayout: layout)
|
||||||
|
|
||||||
tabBarItem.title = "Redirect Rules"
|
tabBarItem.title = "Redirect Rules"
|
||||||
tabBarItem.image = UIImage(systemName: "arrowshape.zigzag.forward")
|
tabBarItem.image = UIImage(systemName: "arrowshape.zigzag.forward")
|
||||||
@@ -21,7 +63,122 @@ class RedirectRulesSettingsViewController: UIViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
view.backgroundColor = .red
|
var snapshot = dataSource.snapshot()
|
||||||
|
snapshot.appendSections([ .rules, .addButton ])
|
||||||
|
snapshot.appendItems(Settings.shared.redirectRules.keys.sorted(), toSection: .rules)
|
||||||
|
snapshot.appendItems([ Items.addButton.rawValue ], toSection: .addButton)
|
||||||
|
dataSource.applySnapshotUsingReloadData(snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
let item = dataSource.itemIdentifier(for: indexPath)
|
||||||
|
if item == Items.addButton.rawValue {
|
||||||
|
let createRuleViewController = CreateRedirectRuleViewController()
|
||||||
|
createRuleViewController.finishedCreatingRule = { [unowned self] (fromHost, toHost) in
|
||||||
|
if fromHost.count > 0 && toHost.count > 0 {
|
||||||
|
addRedirectRule(fromHost: fromHost, toHost: toHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let navController = UINavigationController(rootViewController: createRuleViewController)
|
||||||
|
present(navController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addRedirectRule(fromHost: String, toHost: String) {
|
||||||
|
Settings.shared.redirectRules[fromHost] = toHost
|
||||||
|
|
||||||
|
var snapshot = dataSource.snapshot()
|
||||||
|
snapshot.appendItems([ fromHost ], toSection: .rules)
|
||||||
|
dataSource.apply(snapshot, animatingDifferences: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deleteRedirectRule(fromHost: String) {
|
||||||
|
Settings.shared.redirectRules.removeValue(forKey: fromHost)
|
||||||
|
|
||||||
|
var snapshot = dataSource.snapshot()
|
||||||
|
snapshot.deleteItems([ fromHost ])
|
||||||
|
dataSource.apply(snapshot, animatingDifferences: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateRedirectRuleViewController: UICollectionViewController
|
||||||
|
{
|
||||||
|
enum Section {
|
||||||
|
case hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Item: String {
|
||||||
|
case fromHost
|
||||||
|
case toHost
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TextFieldCellConfiguration: UIContentConfiguration {
|
||||||
|
var textField: UITextField
|
||||||
|
|
||||||
|
func makeContentView() -> UIView & UIContentView {
|
||||||
|
return GenericContentView<UITextField, TextFieldCellConfiguration>(configuration: self, view: textField) { _,_ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
func updated(for state: UIConfigurationState) -> TextFieldCellConfiguration {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var finishedCreatingRule: ((_ fromHost: String, _ toHost: String) -> Void)?
|
||||||
|
|
||||||
|
private let fromHostField = UITextField(frame: .zero)
|
||||||
|
private let toHostField = UITextField(frame: .zero)
|
||||||
|
|
||||||
|
private let doneButton = UIBarButtonItem(systemItem: .done, primaryAction: nil, menu: nil)
|
||||||
|
|
||||||
|
private lazy var dataSource: UICollectionViewDiffableDataSource<Section, Item> = {
|
||||||
|
let registry = UICollectionView.CellRegistration<UICollectionViewListCell, Item> { [unowned self] cell, indexPath, item in
|
||||||
|
if item == Item.fromHost {
|
||||||
|
cell.contentConfiguration = TextFieldCellConfiguration(textField: fromHostField)
|
||||||
|
} else if item == Item.toHost {
|
||||||
|
cell.contentConfiguration = TextFieldCellConfiguration(textField: toHostField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { (collectionView, indexPath, itemIdentifier) in
|
||||||
|
collectionView.dequeueConfiguredReusableCell(using: registry, for: indexPath, item: itemIdentifier)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
|
||||||
|
let layout = UICollectionViewCompositionalLayout.list(using: config)
|
||||||
|
super.init(collectionViewLayout: layout)
|
||||||
|
|
||||||
|
preferredContentSize = CGSize(width: -1, height: 320.0)
|
||||||
|
|
||||||
|
self.title = "Add Redirect Rule"
|
||||||
|
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction { [unowned self] _ in
|
||||||
|
dismiss(animated: true, completion: nil)
|
||||||
|
}, menu: nil)
|
||||||
|
|
||||||
|
doneButton.primaryAction = UIAction { [unowned self] _ in
|
||||||
|
finishedCreatingRule?(fromHostField.text!, toHostField.text!)
|
||||||
|
dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
navigationItem.rightBarButtonItem = doneButton
|
||||||
|
|
||||||
|
fromHostField.placeholder = "From Host"
|
||||||
|
toHostField.placeholder = "To Host"
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
var snapshot = dataSource.snapshot()
|
||||||
|
snapshot.appendSections([ Section.hosts ])
|
||||||
|
snapshot.appendItems([ Item.fromHost, Item.toHost ], toSection: .hosts)
|
||||||
|
dataSource.applySnapshotUsingReloadData(snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,19 @@ public struct SettingProperty<T: RawRepresentable>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Dictionary: RawRepresentable where Key == String, Value == String {
|
||||||
|
public typealias RawValue = [String: String]
|
||||||
|
|
||||||
|
public init?(rawValue: [String : String]) {
|
||||||
|
self.init()
|
||||||
|
self.merge(rawValue, uniquingKeysWith: { $1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
public var rawValue: [String : String] {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Settings
|
class Settings
|
||||||
{
|
{
|
||||||
static let shared = Settings()
|
static let shared = Settings()
|
||||||
@@ -50,4 +63,18 @@ class Settings
|
|||||||
|
|
||||||
@SettingProperty(key: "searchProvider")
|
@SettingProperty(key: "searchProvider")
|
||||||
public var searchProvider: SearchProviderSetting = .searxnor
|
public var searchProvider: SearchProviderSetting = .searxnor
|
||||||
|
|
||||||
|
@SettingProperty(key: "redirectRules")
|
||||||
|
public var redirectRules: [String: String] = [:]
|
||||||
|
|
||||||
|
func redirectRule(for url: URL) -> URL? {
|
||||||
|
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false), let host = url.host {
|
||||||
|
if let alternateHost = redirectRules[host] {
|
||||||
|
components.host = alternateHost
|
||||||
|
return components.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
1AB88EFD24D3BA560006F850 /* TabController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB88EFC24D3BA560006F850 /* TabController.swift */; };
|
1AB88EFD24D3BA560006F850 /* TabController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB88EFC24D3BA560006F850 /* TabController.swift */; };
|
||||||
1AB88EFF24D3BBA50006F850 /* TabPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB88EFE24D3BBA50006F850 /* TabPickerViewController.swift */; };
|
1AB88EFF24D3BBA50006F850 /* TabPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB88EFE24D3BBA50006F850 /* TabPickerViewController.swift */; };
|
||||||
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */; };
|
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */; };
|
||||||
1AD3103D252541E600A4A952 /* PersonalRedirectRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD3103C252541E600A4A952 /* PersonalRedirectRules.swift */; };
|
|
||||||
1AD31040252545BF00A4A952 /* FindOnPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD3103F252545BF00A4A952 /* FindOnPageView.swift */; };
|
1AD31040252545BF00A4A952 /* FindOnPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD3103F252545BF00A4A952 /* FindOnPageView.swift */; };
|
||||||
1AD3104325254FB900A4A952 /* FindOnPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD3104225254FB900A4A952 /* FindOnPageViewController.swift */; };
|
1AD3104325254FB900A4A952 /* FindOnPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD3104225254FB900A4A952 /* FindOnPageViewController.swift */; };
|
||||||
1AD310452525586B00A4A952 /* DocumentControlItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD310442525586B00A4A952 /* DocumentControlItemView.swift */; };
|
1AD310452525586B00A4A952 /* DocumentControlItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AD310442525586B00A4A952 /* DocumentControlItemView.swift */; };
|
||||||
@@ -112,7 +111,6 @@
|
|||||||
1AB88EFC24D3BA560006F850 /* TabController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabController.swift; sourceTree = "<group>"; };
|
1AB88EFC24D3BA560006F850 /* TabController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabController.swift; sourceTree = "<group>"; };
|
||||||
1AB88EFE24D3BBA50006F850 /* TabPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPickerViewController.swift; sourceTree = "<group>"; };
|
1AB88EFE24D3BBA50006F850 /* TabPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPickerViewController.swift; sourceTree = "<group>"; };
|
||||||
1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Actions.swift"; sourceTree = "<group>"; };
|
1AB88F0524D4D3A90006F850 /* UIGestureRecognizer+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Actions.swift"; sourceTree = "<group>"; };
|
||||||
1AD3103C252541E600A4A952 /* PersonalRedirectRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalRedirectRules.swift; sourceTree = "<group>"; };
|
|
||||||
1AD3103F252545BF00A4A952 /* FindOnPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindOnPageView.swift; sourceTree = "<group>"; };
|
1AD3103F252545BF00A4A952 /* FindOnPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindOnPageView.swift; sourceTree = "<group>"; };
|
||||||
1AD3104225254FB900A4A952 /* FindOnPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindOnPageViewController.swift; sourceTree = "<group>"; };
|
1AD3104225254FB900A4A952 /* FindOnPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindOnPageViewController.swift; sourceTree = "<group>"; };
|
||||||
1AD310442525586B00A4A952 /* DocumentControlItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentControlItemView.swift; sourceTree = "<group>"; };
|
1AD310442525586B00A4A952 /* DocumentControlItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentControlItemView.swift; sourceTree = "<group>"; };
|
||||||
@@ -328,7 +326,6 @@
|
|||||||
children = (
|
children = (
|
||||||
CD853BD224E77BEF00D2BDCC /* History */,
|
CD853BD224E77BEF00D2BDCC /* History */,
|
||||||
1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */,
|
1ADFF4AD24C8ED32006DC7AE /* ResourcePolicyManager.swift */,
|
||||||
1AD3103C252541E600A4A952 /* PersonalRedirectRules.swift */,
|
|
||||||
);
|
);
|
||||||
path = Backend;
|
path = Backend;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -593,7 +590,6 @@
|
|||||||
CDD0522425F8055700DD1771 /* SearchProvider.swift in Sources */,
|
CDD0522425F8055700DD1771 /* SearchProvider.swift in Sources */,
|
||||||
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */,
|
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */,
|
||||||
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
|
1ADFF48D24C8C176006DC7AE /* SBRProcessBundleBridge.m in Sources */,
|
||||||
1AD3103D252541E600A4A952 /* PersonalRedirectRules.swift in Sources */,
|
|
||||||
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */,
|
1AB88F0624D4D3A90006F850 /* UIGestureRecognizer+Actions.swift in Sources */,
|
||||||
CD7313E4270534B800053347 /* ScriptPolicyViewControllerDelegate.swift in Sources */,
|
CD7313E4270534B800053347 /* ScriptPolicyViewControllerDelegate.swift in Sources */,
|
||||||
1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */,
|
1ADFF46224C7DE53006DC7AE /* SceneDelegate.swift in Sources */,
|
||||||
|
|||||||
Reference in New Issue
Block a user