diff --git a/App/Settings/CodeEditorSettingsViewController.swift b/App/Settings/CodeEditorSettingsViewController.swift new file mode 100644 index 0000000..ea68eff --- /dev/null +++ b/App/Settings/CodeEditorSettingsViewController.swift @@ -0,0 +1,82 @@ +// +// CodeEditorSettingsViewController.swift +// SBrowser +// +// Copyright © 2021 Apple Inc. All rights reserved. +// + +import UIKit + +class CodeEditorSettingsView: UIView +{ + public let textView = UITextView() + + override init(frame: CGRect) { + super.init(frame: frame) + + textView.font = .monospacedSystemFont(ofSize: 12.0, weight: .regular) + textView.autocorrectionType = .no + textView.autocapitalizationType = .none + textView.smartDashesType = .no + textView.smartQuotesType = .no + addSubview(textView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + textView.frame = bounds.inset(by: layoutMargins).insetBy(dx: 0.0, dy: 18.0) + } +} + +class CodeEditorSettingsViewController: UIViewController, UITextViewDelegate +{ + private let settingsKeypath: ReferenceWritableKeyPath + + private var saveTimer: Timer? + private let settingsView = CodeEditorSettingsView(frame: .zero) + + public init(settingsKeypath: ReferenceWritableKeyPath) { + self.settingsKeypath = settingsKeypath + super.init(nibName: nil, bundle: nil) + + if settingsKeypath == \.userStylesheet { + tabBarItem.title = "Stylesheet" + tabBarItem.image = UIImage(systemName: "newspaper") + } else if settingsKeypath == \.userScript { + tabBarItem.title = "Script" + tabBarItem.image = UIImage(systemName: "applescript") + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + self.view = settingsView + } + + override func viewDidLoad() { + super.viewDidLoad() + settingsView.textView.delegate = self + settingsView.textView.text = Settings.shared[keyPath: settingsKeypath] + } + + private func saveContents() { + Settings.shared[keyPath: settingsKeypath] = settingsView.textView.text! + } + + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + if self.saveTimer?.isValid == true { return true } + + self.saveTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { [weak self] timer in + self?.saveContents() + }) + + return true + } +} diff --git a/App/Settings/Settings.swift b/App/Settings/Settings.swift index fc09bed..caafd13 100644 --- a/App/Settings/Settings.swift +++ b/App/Settings/Settings.swift @@ -28,6 +28,8 @@ public struct SettingProperty } } +// - These coercions into RawRepresentable are stupid. How do I write specializations for each type instead? + extension Dictionary: RawRepresentable where Key == String, Value == String { public typealias RawValue = [String: String] @@ -41,6 +43,18 @@ extension Dictionary: RawRepresentable where Key == String, Value == String { } } +extension String: RawRepresentable { + public typealias RawValue = String + + public init?(rawValue: String) { + self.init(rawValue) + } + + public var rawValue: String { + return self + } +} + class Settings { static let shared = Settings() @@ -80,4 +94,10 @@ class Settings return nil } + + @SettingProperty(key: "userScript") + public var userScript: String = "" + + @SettingProperty(key: "userStylesheet") + public var userStylesheet: String = "" } diff --git a/App/Settings/SettingsViewController.swift b/App/Settings/SettingsViewController.swift index ec5622c..0501f8b 100644 --- a/App/Settings/SettingsViewController.swift +++ b/App/Settings/SettingsViewController.swift @@ -30,6 +30,8 @@ class SettingsViewController: UITabBarController, NSToolbarDelegate self.viewControllers = [ GeneralSettingsViewController(), RedirectRulesSettingsViewController(), + CodeEditorSettingsViewController(settingsKeypath: \.userScript), + CodeEditorSettingsViewController(settingsKeypath: \.userStylesheet), ] navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction { [unowned self] _ in diff --git a/App/Web Process Bundle Bridge/SBRProcessBundleBridge.h b/App/Web Process Bundle Bridge/SBRProcessBundleBridge.h index a86d5a2..0f6a693 100644 --- a/App/Web Process Bundle Bridge/SBRProcessBundleBridge.h +++ b/App/Web Process Bundle Bridge/SBRProcessBundleBridge.h @@ -45,6 +45,8 @@ NS_SWIFT_NAME(ProcessBundleBridge) - (void)parseDocumentForReaderMode:(void(^)(NSString *))completionBlock NS_SWIFT_NAME(parseDocumentForReaderMode(completion:)); +- (void)reloadCustomizedUserScriptsAndStylesheets; + - (instancetype)init NS_UNAVAILABLE; @end diff --git a/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m b/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m index 6475ea7..47c5318 100644 --- a/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m +++ b/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m @@ -34,6 +34,10 @@ WKUserScript *_readabilityScript; NSArray *_userScripts; + + // These come from settings. + _WKUserStyleSheet *_customizedUserStylesheet; + WKUserScript *_customizedUserScript; } - (void)tearDown @@ -101,6 +105,9 @@ [userContentController addUserScript:script]; } + // Reload customized user scripts/stylesheets from settings + [self reloadCustomizedUserScriptsAndStylesheets]; + // Instantiate web view WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:webViewConfiguration]; @@ -135,6 +142,30 @@ return _userScripts; } +- (void)reloadCustomizedUserScriptsAndStylesheets +{ + WKUserContentController *userContentController = [_webViewConfiguration userContentController]; + if (_customizedUserScript) { + [userContentController _removeUserScript:_customizedUserScript]; + } + + NSString *scriptSource = [[NSUserDefaults standardUserDefaults] stringForKey:@"userScript"]; + if ([scriptSource length]) { + _customizedUserScript = [[WKUserScript alloc] initWithSource:scriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; + [userContentController addUserScript:_customizedUserScript]; + } + + if (_customizedUserStylesheet) { + [userContentController _removeUserStyleSheet:_customizedUserStylesheet]; + } + + NSString *stylesheetSource = [[NSUserDefaults standardUserDefaults] stringForKey:@"userStylesheet"]; + if ([stylesheetSource length]) { + _customizedUserStylesheet = [[_WKUserStyleSheet alloc] initWithSource:stylesheetSource forMainFrameOnly:YES]; + [userContentController _addUserStyleSheet:_customizedUserStylesheet]; + } +} + #pragma mark - (void)webProcessDidConnect diff --git a/SBrowser.xcodeproj/project.pbxproj b/SBrowser.xcodeproj/project.pbxproj index 92bf487..33bfe9d 100644 --- a/SBrowser.xcodeproj/project.pbxproj +++ b/SBrowser.xcodeproj/project.pbxproj @@ -74,6 +74,7 @@ CDE6A30625F023EA00E912A4 /* AmberSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE6A30525F023EA00E912A4 /* AmberSettingsView.swift */; }; CDEDD8AA25D62ADB00862605 /* UITraitCollection+MacLike.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */; }; CDF3468E276C105900FB3141 /* SettingsSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF3468D276C105900FB3141 /* SettingsSceneDelegate.swift */; }; + CDF34690276C14BD00FB3141 /* CodeEditorSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -177,6 +178,7 @@ CDE6A30525F023EA00E912A4 /* AmberSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmberSettingsView.swift; sourceTree = ""; }; CDEDD8A925D62ADB00862605 /* UITraitCollection+MacLike.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITraitCollection+MacLike.swift"; sourceTree = ""; }; CDF3468D276C105900FB3141 /* SettingsSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSceneDelegate.swift; sourceTree = ""; }; + CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditorSettingsViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -460,6 +462,7 @@ CD7A7E9C2686A9A500E20BA3 /* SettingsViewController.swift */, CD7A7E9E2686B29100E20BA3 /* GeneralSettingsViewController.swift */, CD7A7EA02686B2E600E20BA3 /* RedirectRulesSettingsViewController.swift */, + CDF3468F276C14BD00FB3141 /* CodeEditorSettingsViewController.swift */, ); path = Settings; sourceTree = ""; @@ -615,6 +618,7 @@ CDE6A30425F023BC00E912A4 /* AmberSettingsViewController.swift in Sources */, 1A14FC2824D26749009B3F83 /* Tab.swift in Sources */, CD7A7E9F2686B29100E20BA3 /* GeneralSettingsViewController.swift in Sources */, + CDF34690276C14BD00FB3141 /* CodeEditorSettingsViewController.swift in Sources */, CD7313E22705349700053347 /* ScriptPolicyViewController.swift in Sources */, CD01D5AB254A206D00189CDC /* TabBarViewController.swift in Sources */, 1ADFF47924C7DFF8006DC7AE /* BrowserView.swift in Sources */, diff --git a/SBrowser.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/SBrowser.xcodeproj/xcshareddata/xcschemes/App.xcscheme index c358d78..9c37fa1 100644 --- a/SBrowser.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/SBrowser.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -32,8 +32,8 @@