From 103d0347c9b983d1a33e7a8dc62a27ad1e2d46b8 Mon Sep 17 00:00:00 2001 From: nieldm Date: Sun, 15 Dec 2019 14:09:38 +0100 Subject: [PATCH 01/19] Enabled option to edit configuration file Closes televator-apps/vimari#153 --- Vimari Extension/ConfigurationModel.swift | 118 ++++++++++++++++ Vimari Extension/Info.plist | 4 - Vimari Extension/SafariExtensionHandler.swift | 131 +++++++++++++++--- Vimari Extension/js/injected.js | 49 +++++-- Vimari Extension/js/settings.js | 33 ----- Vimari Extension/json/defaultSettings.json | 25 ++++ Vimari.xcodeproj/project.pbxproj | 20 ++- Vimari/Base.lproj/Main.storyboard | 86 ++++++++++-- Vimari/ViewController.swift | 43 +++++- 9 files changed, 417 insertions(+), 92 deletions(-) create mode 100644 Vimari Extension/ConfigurationModel.swift delete mode 100644 Vimari Extension/js/settings.js create mode 100644 Vimari Extension/json/defaultSettings.json diff --git a/Vimari Extension/ConfigurationModel.swift b/Vimari Extension/ConfigurationModel.swift new file mode 100644 index 0000000..d5954f9 --- /dev/null +++ b/Vimari Extension/ConfigurationModel.swift @@ -0,0 +1,118 @@ +// +// ConfigurationModel.swift +// Vimari Extension +// +// Created by Daniel Mendez on 12/15/19. +// Copyright © 2019 net.televator. All rights reserved. +// + +protocol ConfigurationModelProtocol { + func editConfigFile() throws + func resetConfigFile() throws + func getSettings() throws -> [String: Any] + func getUserSettings() throws -> [String : Any] +} + +import Foundation +import SafariServices + +class ConfigurationModel: ConfigurationModelProtocol { + + private enum Constant { + static let settingsFileName = "defaultSettings" + static let userSettingsFileName = "userSettings" + static let defaultEditor = "TextEdit" + } + + func editConfigFile() throws { + let settingsFilePath = try findOrCreateUserSettings() + NSWorkspace.shared.openFile( + settingsFilePath, + withApplication: Constant.defaultEditor + ) + } + + func resetConfigFile() throws { + let settingsFilePath = try overwriteUserSettings() + NSWorkspace.shared.openFile( + settingsFilePath, + withApplication: Constant.defaultEditor + ) + } + + func getSettings() throws -> [String : Any] { + return try loadSettings(fromFile: Constant.settingsFileName) + } + + func getUserSettings() throws -> [String : Any] { + let userFilePath = try findOrCreateUserSettings() + let urlSettingsFile = URL(fileURLWithPath: userFilePath) + let settingsData = try Data(contentsOf: urlSettingsFile) + return try settingsData.toJSONObject() + } + + private func loadSettings(fromFile file: String) throws -> [String : Any] { + let settingsData = try Bundle.main.getJSONData(from: file) + return try settingsData.toJSONObject() + } + + private func findOrCreateUserSettings() throws -> String { + let url = FileManager.documentDirectoryURL + .appendingPathComponent(Constant.userSettingsFileName) + .appendingPathExtension("json") + let urlString = url.path + if FileManager.default.fileExists(atPath: urlString) { + return urlString + } + let data = try Bundle.main.getJSONData(from: Constant.settingsFileName) + try data.write(to: url) + return urlString + } + + private func overwriteUserSettings() throws -> String { + let url = FileManager.documentDirectoryURL + .appendingPathComponent(Constant.userSettingsFileName) + .appendingPathExtension("json") + let urlString = url.path + let data = try Bundle.main.getJSONData(from: Constant.settingsFileName) + try data.write(to: url) + return urlString + } +} + +enum DataError: Error { + case unableToParse + case notFound +} + +private extension Data { + func toJSONObject() throws -> [String: Any] { + let serialized = try JSONSerialization.jsonObject(with: self, options: []) + guard let result = serialized as? [String: Any] else { + throw DataError.unableToParse + } + return result + } +} + +private extension Bundle { + func getJSONPath(for file: String) throws -> String { + guard let result = self.path(forResource: file, ofType: ".json") else { + throw DataError.notFound + } + return result + } + + func getJSONData(from file: String) throws -> Data { + let settingsPath = try self.getJSONPath(for: file) + let urlSettingsFile = URL(fileURLWithPath: settingsPath) + return try Data(contentsOf: urlSettingsFile) + } +} + +private extension FileManager { + static var documentDirectoryURL: URL { + let documentDirectoryURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) + return documentDirectoryURL + } +} diff --git a/Vimari Extension/Info.plist b/Vimari Extension/Info.plist index 455d752..575d587 100644 --- a/Vimari Extension/Info.plist +++ b/Vimari Extension/Info.plist @@ -30,10 +30,6 @@ $(PRODUCT_MODULE_NAME).SafariExtensionHandler SFSafariContentScript - - Script - settings.js - Script keyboard-utils.js diff --git a/Vimari Extension/SafariExtensionHandler.swift b/Vimari Extension/SafariExtensionHandler.swift index 1e8eab9..f41c8c5 100644 --- a/Vimari Extension/SafariExtensionHandler.swift +++ b/Vimari Extension/SafariExtensionHandler.swift @@ -6,6 +6,12 @@ enum ActionType: String { case tabForward case tabBackward case closeTab + case updateSettings +} + +enum InputAction: String { + case openSettings + case resetSettings } enum TabDirection: String { @@ -13,14 +19,33 @@ enum TabDirection: String { case backward } -func mod(_ a: Int, _ n: Int) -> Int { - // https://stackoverflow.com/questions/41180292/negative-number-modulo-in-swift - precondition(n > 0, "modulus must be positive") - let r = a % n - return r >= 0 ? r : r + n -} - class SafariExtensionHandler: SFSafariExtensionHandler { + + private enum Constant { + static let mainAppName = "Vimari" + static let newTabPageURL = "https://duckduckgo.com" //Try it :D + } + + let configuration: ConfigurationModelProtocol = ConfigurationModel() + + //MARK: Overrides + + override func messageReceivedFromContainingApp(withName messageName: String, userInfo: [String : Any]? = nil) { + do { + switch InputAction(rawValue: messageName) { + case .openSettings: + try configuration.editConfigFile() + case .resetSettings: + try configuration.resetConfigFile() + default: + NSLog("Input not supported " + messageName) + } + } catch { + NSLog(error.localizedDescription) + } + + } + override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]?) { guard let action = ActionType(rawValue: messageName) else { NSLog("Received message with unsupported type: \(messageName)") @@ -40,10 +65,29 @@ class SafariExtensionHandler: SFSafariExtensionHandler { changeTab(withDirection: .backward, from: page) case .closeTab: closeTab(from: page) + case .updateSettings: + updateSettings() } } - func openInNewTab(url: URL) { + override func toolbarItemClicked(in _: SFSafariWindow) { + // This method will be called when your toolbar item is clicked. + NSLog("The extension's toolbar item was clicked") + NSWorkspace.shared.launchApplication(Constant.mainAppName) + } + + override func validateToolbarItem(in _: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { + // This is called when Safari's state changed in some way that would require the extension's toolbar item to be validated again. + validationHandler(true, "") + } + + override func popoverViewController() -> SFSafariExtensionViewController { + return SafariExtensionViewController.shared + } + + // MARK: Tabs Methods + + private func openInNewTab(url: URL) { SFSafariApplication.getActiveWindow { activeWindow in activeWindow?.openTab(with: url, makeActiveIfPossible: false, completionHandler: { _ in // Perform some action here after the page loads @@ -51,9 +95,9 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } } - func openNewTab() { + private func openNewTab() { // Ideally this URL would be something that represents an empty tab better than localhost - let url = URL(string: "http://localhost")! + let url = URL(string: Constant.newTabPageURL)! SFSafariApplication.getActiveWindow { activeWindow in activeWindow?.openTab(with: url, makeActiveIfPossible: true, completionHandler: { _ in // Perform some action here after the page loads @@ -61,7 +105,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } } - func changeTab(withDirection direction: TabDirection, from page: SFSafariPage, completionHandler: (() -> Void)? = nil ) { + private func changeTab(withDirection direction: TabDirection, from page: SFSafariPage, completionHandler: (() -> Void)? = nil ) { page.getContainingTab(completionHandler: { currentTab in currentTab.getContainingWindow(completionHandler: { window in window?.getAllTabs(completionHandler: { tabs in @@ -81,25 +125,68 @@ class SafariExtensionHandler: SFSafariExtensionHandler { }) } - func closeTab(from page: SFSafariPage) { + private func closeTab(from page: SFSafariPage) { page.getContainingTab { tab in tab.close() } } - - override func toolbarItemClicked(in _: SFSafariWindow) { - // This method will be called when your toolbar item is clicked. - NSLog("The extension's toolbar item was clicked") - NSWorkspace.shared.launchApplication("Vimari") + + // MARK: Settings + + private func updateSettings() { + do { + let settings: [String: Any] + if let userSettings = try? configuration.getUserSettings() { + settings = userSettings + } else { + settings = try configuration.getSettings() + } + SFSafariApplication.getActivePage { + $0?.dispatch(settings: settings) + } + } catch { + NSLog(error.localizedDescription) + } } + + private func fallbackSettings() { + do { + let settings = try configuration.getUserSettings() + SFSafariApplication.getActivePage { + $0?.dispatch(settings: settings) + } + } catch { + NSLog(error.localizedDescription) + } + } +} - override func validateToolbarItem(in _: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { - // This is called when Safari's state changed in some way that would require the extension's toolbar item to be validated again. - validationHandler(true, "") +// MARK: Helpers + +private func mod(_ a: Int, _ n: Int) -> Int { + // https://stackoverflow.com/questions/41180292/negative-number-modulo-in-swift + precondition(n > 0, "modulus must be positive") + let r = a % n + return r >= 0 ? r : r + n +} + +private extension SFSafariPage { + func dispatch(settings: [String: Any]) { + self.dispatchMessageToScript( + withName: "updateSettingsEvent", + userInfo: settings + ) } +} - override func popoverViewController() -> SFSafariExtensionViewController { - return SafariExtensionViewController.shared +private extension SFSafariApplication { + static func getActivePage(completionHandler: @escaping (SFSafariPage?) -> Void) { + SFSafariApplication.getActiveWindow { + $0?.getActiveTab { + $0?.getActivePage(completionHandler: completionHandler) + } + } } } + diff --git a/Vimari Extension/js/injected.js b/Vimari Extension/js/injected.js index e2aab1d..3537e69 100644 --- a/Vimari Extension/js/injected.js +++ b/Vimari Extension/js/injected.js @@ -106,10 +106,14 @@ Mousetrap.stopCallback = function(e, element, combo) { }; // Set up key codes to event handlers -function bindKeyCodesToActions() { +function bindKeyCodesToActions(settings) { + var excludedUrl = false + if (typeof settings != "undefined") { + excludedUrl = isExcludedUrl(settings.excludedUrls, document.URL) + } // Only add if topWindow... not iframe - if (topWindow && !isExcludedUrl(settings.excludedUrls, document.URL)) { - Mousetrap.reset(); + Mousetrap.reset(); + if (topWindow && !excludedUrl) { Mousetrap.bind('esc', enterNormalMode); Mousetrap.bind('ctrl+[', enterNormalMode); Mousetrap.bind('i', enterInsertMode); @@ -162,10 +166,13 @@ function unbindKeyCodes() { // Adds an optional modifier to the configured key code for the action function getKeyCode(actionName) { var keyCode = ''; - if(settings.modifier) { - keyCode += settings.modifier + '+'; - } - return keyCode + settings[actionName]; + if (typeof settings != 'undefined') { + if(settings.modifier) { + keyCode += settings.modifier + '+'; + } + return keyCode + settings[actionName]; + } + return keyCode; } @@ -225,7 +232,7 @@ function handleMessage(msg) { */ function setSettings(msg) { settings = msg; - bindKeyCodesToActions(); + bindKeyCodesToActions(msg); } /* @@ -250,7 +257,7 @@ function isExcludedUrl(storedExcludedUrls, currentUrl) { for (_i = 0, _len = excludedUrls.length; _i < _len; _i++) { url = excludedUrls[_i]; formattedUrl = stripProtocolAndWww(url); - formattedUrl = formattedUrl.toLowerCase(); + formattedUrl = formattedUrl.toLowerCase().trim(); regexp = new RegExp('((.*)?(' + formattedUrl + ')+(.*))'); if (currentUrl.toLowerCase().match(regexp)) { return true; @@ -277,13 +284,27 @@ function stripProtocolAndWww(url) { return url; } -// Bootstrap extension -setSettings(window.getSettings()); // Add event listener -// safari.self.addEventListener("message", handleMessage, false); -// Retrieve settings -// safari.self.tab.dispatchMessage('getSettings', ''); +function inIframe () { + try { + return window.self !== window.top; + } + catch (e) { + return true; + } +} + +if(!inIframe()){ + safari.self.addEventListener("message", messageHandler); + safari.extension.dispatchMessage("updateSettings"); +} +function messageHandler(event){ + if (event.name == "updateSettingsEvent") { + setSettings(event.message); + } +} + // Export to make it testable window.isExcludedUrl = isExcludedUrl; window.stripProtocolAndWww = stripProtocolAndWww; diff --git a/Vimari Extension/js/settings.js b/Vimari Extension/js/settings.js deleted file mode 100644 index 1b931cc..0000000 --- a/Vimari Extension/js/settings.js +++ /dev/null @@ -1,33 +0,0 @@ -function getSettings() { - return { - 'modifier': undefined, - 'excludedUrls': '', - - 'hintToggle': 'f', - 'newTabHintToggle': 'shift+f', - 'linkHintCharacters': 'asdfjklqwerzxc', - 'detectByCursorStyle': false, - - 'scrollUp': 'k', - 'scrollDown': 'j', - 'scrollLeft': 'h', - 'scrollRight': 'l', - 'scrollSize': 50, - 'scrollUpHalfPage': 'u', - 'scrollDownHalfPage': 'd', - 'goToPageTop': 'g g', - 'goToPageBottom': 'shift+g', - - 'goBack': 'shift+h', - 'goForward': 'shift+l', - 'reload': 'r', - 'tabForward': 'w', - 'tabBack': 'q', - 'closeTab': 'x', - 'closeTabReverse': 'shift+x', - - 'openTab': 't', - }; -} - -window.getSettings = getSettings; diff --git a/Vimari Extension/json/defaultSettings.json b/Vimari Extension/json/defaultSettings.json new file mode 100644 index 0000000..744cfc8 --- /dev/null +++ b/Vimari Extension/json/defaultSettings.json @@ -0,0 +1,25 @@ +{ + "modifier": "", + "excludedUrls": "", + "hintToggle": "f", + "newTabHintToggle": "shift+f", + "linkHintCharacters": "asdfjklqwerzxc", + "detectByCursorStyle": false, + "scrollUp": "k", + "scrollDown": "j", + "scrollLeft": "h", + "scrollRight": "l", + "scrollSize": 50, + "scrollUpHalfPage": "u", + "scrollDownHalfPage": "d", + "goToPageTop": "g g", + "goToPageBottom": "shift+g", + "goBack": "shift+h", + "goForward": "shift+l", + "reload": "r", + "tabForward": "w", + "tabBack": "q", + "closeTab": "x", + "closeTabReverse": "shift+x", + "openTab": "t" +} diff --git a/Vimari.xcodeproj/project.pbxproj b/Vimari.xcodeproj/project.pbxproj index e5f95cb..ad51723 100644 --- a/Vimari.xcodeproj/project.pbxproj +++ b/Vimari.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + B1E3C17023A65ED400A56807 /* ConfigurationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E3C16F23A65ED400A56807 /* ConfigurationModel.swift */; }; + B1FD3B9923A588DE00677A52 /* defaultSettings.json in Resources */ = {isa = PBXBuildFile; fileRef = B1FD3B9823A588DE00677A52 /* defaultSettings.json */; }; E320D0662337FC9800F2C3A4 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = E320D0652337FC9800F2C3A4 /* Credits.rtf */; }; E320D06823397C5C00F2C3A4 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E320D06723397C5C00F2C3A4 /* CHANGELOG.md */; }; E380F24A2331806400640547 /* Vimari.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = E380F2492331806400640547 /* Vimari.entitlements */; }; @@ -27,7 +29,6 @@ E380F287233183EF00640547 /* keyboard-utils.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F27C233183EE00640547 /* keyboard-utils.js */; }; E380F288233183EF00640547 /* mousetrap.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F27E233183EE00640547 /* mousetrap.js */; }; E380F289233183EF00640547 /* vimium-scripts.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F27F233183EE00640547 /* vimium-scripts.js */; }; - E380F28A233183EF00640547 /* settings.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F280233183EE00640547 /* settings.js */; }; E380F28B233183EF00640547 /* injected.css in Resources */ = {isa = PBXBuildFile; fileRef = E380F282233183EE00640547 /* injected.css */; }; /* End PBXBuildFile section */ @@ -56,6 +57,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + B1E3C16F23A65ED400A56807 /* ConfigurationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationModel.swift; sourceTree = ""; }; + B1FD3B9823A588DE00677A52 /* defaultSettings.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = defaultSettings.json; sourceTree = ""; }; E320D0652337FC9800F2C3A4 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; E320D06723397C5C00F2C3A4 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; E380F2462331806400640547 /* Vimari.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Vimari.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -80,7 +83,6 @@ E380F27C233183EE00640547 /* keyboard-utils.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "keyboard-utils.js"; sourceTree = ""; }; E380F27E233183EE00640547 /* mousetrap.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mousetrap.js; sourceTree = ""; }; E380F27F233183EE00640547 /* vimium-scripts.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "vimium-scripts.js"; sourceTree = ""; }; - E380F280233183EE00640547 /* settings.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = settings.js; sourceTree = ""; }; E380F282233183EE00640547 /* injected.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = injected.css; sourceTree = ""; }; /* End PBXFileReference section */ @@ -103,6 +105,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + B1FD3B9723A588DE00677A52 /* json */ = { + isa = PBXGroup; + children = ( + B1FD3B9823A588DE00677A52 /* defaultSettings.json */, + ); + path = json; + sourceTree = ""; + }; E380F23D2331806300640547 = { isa = PBXGroup; children = ( @@ -149,8 +159,10 @@ isa = PBXGroup; children = ( E380F2612331806500640547 /* SafariExtensionHandler.swift */, + B1E3C16F23A65ED400A56807 /* ConfigurationModel.swift */, E380F2632331806500640547 /* SafariExtensionViewController.swift */, E380F2652331806500640547 /* SafariExtensionViewController.xib */, + B1FD3B9723A588DE00677A52 /* json */, E380F281233183EE00640547 /* css */, E380F277233183EE00640547 /* js */, E380F2682331806500640547 /* Info.plist */, @@ -169,7 +181,6 @@ E380F27B233183EE00640547 /* mocks.js */, E380F27C233183EE00640547 /* keyboard-utils.js */, E380F27D233183EE00640547 /* lib */, - E380F280233183EE00640547 /* settings.js */, ); path = js; sourceTree = ""; @@ -285,7 +296,7 @@ buildActionMask = 2147483647; files = ( E380F26C2331806500640547 /* ToolbarItemIcon.pdf in Resources */, - E380F28A233183EF00640547 /* settings.js in Resources */, + B1FD3B9923A588DE00677A52 /* defaultSettings.json in Resources */, E380F28B233183EF00640547 /* injected.css in Resources */, E380F285233183EF00640547 /* injected.js in Resources */, E380F287233183EF00640547 /* keyboard-utils.js in Resources */, @@ -316,6 +327,7 @@ files = ( E380F2642331806500640547 /* SafariExtensionViewController.swift in Sources */, E380F2622331806500640547 /* SafariExtensionHandler.swift in Sources */, + B1E3C17023A65ED400A56807 /* ConfigurationModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Vimari/Base.lproj/Main.storyboard b/Vimari/Base.lproj/Main.storyboard index c8ca3a9..00f64b7 100644 --- a/Vimari/Base.lproj/Main.storyboard +++ b/Vimari/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -99,15 +99,15 @@ - - + + - + - + @@ -116,7 +116,7 @@ - + @@ -124,10 +124,10 @@ - + - + @@ -145,7 +145,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -173,7 +173,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Vimari/ViewController.swift b/Vimari/ViewController.swift index b3295c7..6434e5e 100644 --- a/Vimari/ViewController.swift +++ b/Vimari/ViewController.swift @@ -1,10 +1,17 @@ import Cocoa import SafariServices.SFSafariApplication +import OSLog class ViewController: NSViewController { @IBOutlet var appNameLabel: NSTextField! @IBOutlet var extensionStatus: NSTextField! @IBOutlet var spinner: NSProgressIndicator! + + private enum Constant { + static let extensionIdentifier = "net.televator.Vimari.SafariExtension" + static let openSettings = "openSettings" + static let resetSettings = "resetSettings" + } func refreshExtensionStatus() { NSLog("Refreshing extension status") @@ -12,7 +19,8 @@ class ViewController: NSViewController { extensionStatus.stringValue = "Checking extension status" if SFSafariServicesAvailable() { - SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: "net.televator.Vimari.SafariExtension") { + SFSafariExtensionManager.getStateOfSafariExtension( + withIdentifier: Constant.extensionIdentifier) { state, error in print("State", state as Any, "Error", error as Any, state?.isEnabled as Any) @@ -51,10 +59,41 @@ class ViewController: NSViewController { } @IBAction func openSafariExtensionPreferences(_: AnyObject?) { - SFSafariApplication.showPreferencesForExtension(withIdentifier: "net.televator.Vimari.SafariExtension") { error in + SFSafariApplication.showPreferencesForExtension( + withIdentifier: Constant.extensionIdentifier) { error in if let _ = error { // Insert code to inform the user that something went wrong. } } } + + @IBAction func openSettingsAction(_ sender: Any) { + dispatchOpenSettings() + } + + @IBAction func resetSettingsAction(_ sender: Any) { + dispatchResetSettings() + } + + func dispatchOpenSettings() { + SFSafariApplication.dispatchMessage( + withName: Constant.openSettings, + toExtensionWithIdentifier: Constant.extensionIdentifier, + userInfo: nil) { (error) in + if let error = error { + print(error.localizedDescription) + } + } + } + + func dispatchResetSettings() { + SFSafariApplication.dispatchMessage( + withName: Constant.resetSettings, + toExtensionWithIdentifier: Constant.extensionIdentifier, + userInfo: nil) { (error) in + if let error = error { + print(error.localizedDescription) + } + } + } } From aba403c17c45960743372cdd30df1f6ba87265dd Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 18 Jul 2020 17:18:54 +0200 Subject: [PATCH 02/19] Update Vimari application layout Due to the extra options introduced by @nieldm the layout of the application panel became increasingly vertical. This change introduces a two column layout that shows the icon and app name on the left side while showing the actionable buttons on the right side. --- Vimari/Base.lproj/Main.storyboard | 306 ++++++++++++++++-------------- 1 file changed, 166 insertions(+), 140 deletions(-) diff --git a/Vimari/Base.lproj/Main.storyboard b/Vimari/Base.lproj/Main.storyboard index 00f64b7..2052547 100644 --- a/Vimari/Base.lproj/Main.storyboard +++ b/Vimari/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -78,7 +78,7 @@ - + @@ -99,172 +99,198 @@ - - + + - - + + - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + @@ -280,6 +306,6 @@ DQ - + From 263f42481b6e591c6a88544fe8af2b7963139223 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 18 Jul 2020 17:26:07 +0200 Subject: [PATCH 03/19] Remove name from application interface As the application name is provided in the title bar it can be removed from the ui in the helper application to reduce clutter. --- Vimari/Base.lproj/Main.storyboard | 40 +++++++------------------------ Vimari/ViewController.swift | 2 -- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/Vimari/Base.lproj/Main.storyboard b/Vimari/Base.lproj/Main.storyboard index 2052547..1676e97 100644 --- a/Vimari/Base.lproj/Main.storyboard +++ b/Vimari/Base.lproj/Main.storyboard @@ -100,17 +100,17 @@ - + - + - - + + - - + + @@ -118,37 +118,16 @@ - - - - - - - - - - - - - - - - - - - - - - - + + @@ -225,7 +204,7 @@ DQ - + @@ -294,7 +273,6 @@ DQ - diff --git a/Vimari/ViewController.swift b/Vimari/ViewController.swift index 6434e5e..2b246b3 100644 --- a/Vimari/ViewController.swift +++ b/Vimari/ViewController.swift @@ -3,7 +3,6 @@ import SafariServices.SFSafariApplication import OSLog class ViewController: NSViewController { - @IBOutlet var appNameLabel: NSTextField! @IBOutlet var extensionStatus: NSTextField! @IBOutlet var spinner: NSProgressIndicator! @@ -53,7 +52,6 @@ class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - appNameLabel.stringValue = "Vimari" refreshExtensionStatus() } From 8b7acacbd1ebbe291ed14743a433d7ab87a4c585 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 18 Jul 2020 19:37:18 +0200 Subject: [PATCH 04/19] Fix inability to switch in pinned tabs Due to not being able to request the containing tab of a pinned tab (as they don't have one), a bug was introduced that did not allow the user to switch tabs if the active tab was a pinned one. This change introduces some additional logic that in this case returns the current active window. --- Vimari Extension/SafariExtensionHandler.swift | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/Vimari Extension/SafariExtensionHandler.swift b/Vimari Extension/SafariExtensionHandler.swift index f41c8c5..afae65a 100644 --- a/Vimari Extension/SafariExtensionHandler.swift +++ b/Vimari Extension/SafariExtensionHandler.swift @@ -106,9 +106,11 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } private func changeTab(withDirection direction: TabDirection, from page: SFSafariPage, completionHandler: (() -> Void)? = nil ) { - page.getContainingTab(completionHandler: { currentTab in - currentTab.getContainingWindow(completionHandler: { window in - window?.getAllTabs(completionHandler: { tabs in + page.getContainingTab() { currentTab in + // Using .currentWindow instead of .containingWindow, this prevents the window being nil in the case of a pinned tab. + self.currentWindow(from: page) { window in + window?.getAllTabs() { tabs in + tabs.forEach { tab in NSLog(tab.description) } if let currentIndex = tabs.firstIndex(of: currentTab) { let indexStep = direction == TabDirection.forward ? 1 : -1 @@ -116,13 +118,28 @@ class SafariExtensionHandler: SFSafariExtensionHandler { // % calculates the remainder, not the modulus, so we need a // custom function. let newIndex = mod(currentIndex + indexStep, tabs.count) - + tabs[newIndex].activate(completionHandler: completionHandler ?? {}) - + } - }) - }) - }) + } + } + } + } + + /** + Returns the containing window of a SFSafariPage, if not available default to the current active window. + */ + private func currentWindow(from page: SFSafariPage, completionHandler: @escaping ((SFSafariWindow?) -> Void)) { + page.getContainingTab() { $0.getContainingWindow() { window in + if window != nil { + return completionHandler(window) + } else { + SFSafariApplication.getActiveWindow() { window in + return completionHandler(window) + } + } + }} } private func closeTab(from page: SFSafariPage) { From 3972d3d0982c9bfd750b91c344b021579a38dad8 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sun, 19 Jul 2020 21:03:29 +0200 Subject: [PATCH 05/19] Remove duplicate code --- Vimari Extension/ConfigurationModel.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Vimari Extension/ConfigurationModel.swift b/Vimari Extension/ConfigurationModel.swift index d5954f9..f760f54 100644 --- a/Vimari Extension/ConfigurationModel.swift +++ b/Vimari Extension/ConfigurationModel.swift @@ -23,6 +23,10 @@ class ConfigurationModel: ConfigurationModelProtocol { static let userSettingsFileName = "userSettings" static let defaultEditor = "TextEdit" } + + let userSettingsUrl: URL = FileManager.documentDirectoryURL + .appendingPathComponent(Constant.userSettingsFileName) + .appendingPathExtension("json") func editConfigFile() throws { let settingsFilePath = try findOrCreateUserSettings() @@ -57,9 +61,7 @@ class ConfigurationModel: ConfigurationModelProtocol { } private func findOrCreateUserSettings() throws -> String { - let url = FileManager.documentDirectoryURL - .appendingPathComponent(Constant.userSettingsFileName) - .appendingPathExtension("json") + let url = userSettingsUrl let urlString = url.path if FileManager.default.fileExists(atPath: urlString) { return urlString @@ -70,10 +72,8 @@ class ConfigurationModel: ConfigurationModelProtocol { } private func overwriteUserSettings() throws -> String { - let url = FileManager.documentDirectoryURL - .appendingPathComponent(Constant.userSettingsFileName) - .appendingPathExtension("json") - let urlString = url.path + let url = userSettingsUrl + let urlString = userSettingsUrl.path let data = try Bundle.main.getJSONData(from: Constant.settingsFileName) try data.write(to: url) return urlString From 35cd1d9fac4852a0093ddffc9861e87ff22211c8 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sun, 19 Jul 2020 22:22:43 +0200 Subject: [PATCH 06/19] Unify message handling switch statement --- Vimari Extension/SafariExtensionHandler.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Vimari Extension/SafariExtensionHandler.swift b/Vimari Extension/SafariExtensionHandler.swift index afae65a..80e2a98 100644 --- a/Vimari Extension/SafariExtensionHandler.swift +++ b/Vimari Extension/SafariExtensionHandler.swift @@ -37,7 +37,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { try configuration.editConfigFile() case .resetSettings: try configuration.resetConfigFile() - default: + case .none: NSLog("Input not supported " + messageName) } } catch { @@ -47,13 +47,9 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]?) { - guard let action = ActionType(rawValue: messageName) else { - NSLog("Received message with unsupported type: \(messageName)") - return - } NSLog("Received message: \(messageName)") - switch action { + switch ActionType(rawValue: messageName) { case .openLinkInTab: let url = URL(string: userInfo?["url"] as! String) openInNewTab(url: url!) @@ -67,6 +63,8 @@ class SafariExtensionHandler: SFSafariExtensionHandler { closeTab(from: page) case .updateSettings: updateSettings() + case .none: + NSLog("Received message with unsupported type: \(messageName)") } } From 6f42e2c2aafca2bafd26ad1a25196222d0c43b0a Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sun, 19 Jul 2020 22:23:19 +0200 Subject: [PATCH 07/19] Add method behaviour clarification As there are twoo distinct methods that both handle messages I added documentation to clarify the distinction between the two. --- Vimari Extension/SafariExtensionHandler.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Vimari Extension/SafariExtensionHandler.swift b/Vimari Extension/SafariExtensionHandler.swift index 80e2a98..0cac347 100644 --- a/Vimari Extension/SafariExtensionHandler.swift +++ b/Vimari Extension/SafariExtensionHandler.swift @@ -29,7 +29,8 @@ class SafariExtensionHandler: SFSafariExtensionHandler { let configuration: ConfigurationModelProtocol = ConfigurationModel() //MARK: Overrides - + + // This method handles messages from the Vimari App (located /Vimari in the repository) override func messageReceivedFromContainingApp(withName messageName: String, userInfo: [String : Any]? = nil) { do { switch InputAction(rawValue: messageName) { @@ -45,9 +46,9 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } } - - override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]?) { + // This method handles messages from the extension (in the browser page) + override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]?) { NSLog("Received message: \(messageName)") switch ActionType(rawValue: messageName) { case .openLinkInTab: From f49ba3396e8e73179e1134d7535142d24e35f385 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sun, 19 Jul 2020 22:44:37 +0200 Subject: [PATCH 08/19] Rename getSettings to getDefaultSettings The use of .getSettings could be confusing in the sense that it would seem to return the current user preferences, the implementation however loads the default settings. Hence the rename of the function. --- Vimari Extension/ConfigurationModel.swift | 4 ++-- Vimari Extension/SafariExtensionHandler.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Vimari Extension/ConfigurationModel.swift b/Vimari Extension/ConfigurationModel.swift index f760f54..55bdd10 100644 --- a/Vimari Extension/ConfigurationModel.swift +++ b/Vimari Extension/ConfigurationModel.swift @@ -9,7 +9,7 @@ protocol ConfigurationModelProtocol { func editConfigFile() throws func resetConfigFile() throws - func getSettings() throws -> [String: Any] + func getDefaultSettings() throws -> [String: Any] func getUserSettings() throws -> [String : Any] } @@ -44,7 +44,7 @@ class ConfigurationModel: ConfigurationModelProtocol { ) } - func getSettings() throws -> [String : Any] { + func getDefaultSettings() throws -> [String : Any] { return try loadSettings(fromFile: Constant.settingsFileName) } diff --git a/Vimari Extension/SafariExtensionHandler.swift b/Vimari Extension/SafariExtensionHandler.swift index 0cac347..f160286 100644 --- a/Vimari Extension/SafariExtensionHandler.swift +++ b/Vimari Extension/SafariExtensionHandler.swift @@ -156,7 +156,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { if let userSettings = try? configuration.getUserSettings() { settings = userSettings } else { - settings = try configuration.getSettings() + settings = try configuration.getDefaultSettings() } SFSafariApplication.getActivePage { $0?.dispatch(settings: settings) From 31d101297220b45bb529011003a09410fd4a8748 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sun, 19 Jul 2020 23:23:46 +0200 Subject: [PATCH 09/19] Add setting for default url on new tab --- Vimari Extension/SafariExtensionHandler.swift | 17 +++++++++++++++-- Vimari Extension/json/defaultSettings.json | 3 ++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Vimari Extension/SafariExtensionHandler.swift b/Vimari Extension/SafariExtensionHandler.swift index f160286..e80d5ac 100644 --- a/Vimari Extension/SafariExtensionHandler.swift +++ b/Vimari Extension/SafariExtensionHandler.swift @@ -95,8 +95,11 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } private func openNewTab() { - // Ideally this URL would be something that represents an empty tab better than localhost - let url = URL(string: Constant.newTabPageURL)! + var newPageUrl: String? = getSetting("openTabUrl") as? String + if newPageUrl == nil || newPageUrl!.isEmpty { + newPageUrl = Constant.newTabPageURL + } + let url = URL(string: newPageUrl!)! SFSafariApplication.getActiveWindow { activeWindow in activeWindow?.openTab(with: url, makeActiveIfPossible: true, completionHandler: { _ in // Perform some action here after the page loads @@ -149,6 +152,16 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } // MARK: Settings + + private func getSetting(_ settingKey: String) -> Any? { + do { + let settings = try configuration.getUserSettings() + return settings[settingKey] + } catch { + NSLog("Was not able to retrieve the user settings\n\(error.localizedDescription)") + return nil + } + } private func updateSettings() { do { diff --git a/Vimari Extension/json/defaultSettings.json b/Vimari Extension/json/defaultSettings.json index 744cfc8..58ba563 100644 --- a/Vimari Extension/json/defaultSettings.json +++ b/Vimari Extension/json/defaultSettings.json @@ -21,5 +21,6 @@ "tabBack": "q", "closeTab": "x", "closeTabReverse": "shift+x", - "openTab": "t" + "openTab": "t", + "openTabUrl": "https://duckduckgo.com/" } From 93582533086ba4ff3ebd1de8c5b89a637f02db61 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Mon, 20 Jul 2020 12:04:02 +0200 Subject: [PATCH 10/19] Remove unsupported closeTabReverse from settings --- Vimari Extension/js/injected.js | 3 --- Vimari Extension/json/defaultSettings.json | 1 - 2 files changed, 4 deletions(-) diff --git a/Vimari Extension/js/injected.js b/Vimari Extension/js/injected.js index 3537e69..45ba8c6 100644 --- a/Vimari Extension/js/injected.js +++ b/Vimari Extension/js/injected.js @@ -69,9 +69,6 @@ var actionMap = { 'closeTab': function() { safari.extension.dispatchMessage("closeTab"); }, - 'closeTabReverse': - function() { safari.self.tab.dispatchMessage('closeTab', 1); }, - 'scrollDownHalfPage': function() { window.scrollBy(0, window.innerHeight / 2); }, diff --git a/Vimari Extension/json/defaultSettings.json b/Vimari Extension/json/defaultSettings.json index 58ba563..b78003e 100644 --- a/Vimari Extension/json/defaultSettings.json +++ b/Vimari Extension/json/defaultSettings.json @@ -20,7 +20,6 @@ "tabForward": "w", "tabBack": "q", "closeTab": "x", - "closeTabReverse": "shift+x", "openTab": "t", "openTabUrl": "https://duckduckgo.com/" } From 790c41f201743a84a729abf5789c73c79cd5e7e7 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Mon, 20 Jul 2020 12:59:03 +0200 Subject: [PATCH 11/19] Group bindings in settings json --- Vimari Extension/js/injected.js | 2 +- Vimari Extension/json/defaultSettings.json | 40 ++++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Vimari Extension/js/injected.js b/Vimari Extension/js/injected.js index 45ba8c6..90f70af 100644 --- a/Vimari Extension/js/injected.js +++ b/Vimari Extension/js/injected.js @@ -167,7 +167,7 @@ function getKeyCode(actionName) { if(settings.modifier) { keyCode += settings.modifier + '+'; } - return keyCode + settings[actionName]; + return keyCode + settings["bindings"][actionName]; } return keyCode; } diff --git a/Vimari Extension/json/defaultSettings.json b/Vimari Extension/json/defaultSettings.json index b78003e..06fb864 100644 --- a/Vimari Extension/json/defaultSettings.json +++ b/Vimari Extension/json/defaultSettings.json @@ -1,25 +1,27 @@ { - "modifier": "", "excludedUrls": "", - "hintToggle": "f", - "newTabHintToggle": "shift+f", "linkHintCharacters": "asdfjklqwerzxc", "detectByCursorStyle": false, - "scrollUp": "k", - "scrollDown": "j", - "scrollLeft": "h", - "scrollRight": "l", "scrollSize": 50, - "scrollUpHalfPage": "u", - "scrollDownHalfPage": "d", - "goToPageTop": "g g", - "goToPageBottom": "shift+g", - "goBack": "shift+h", - "goForward": "shift+l", - "reload": "r", - "tabForward": "w", - "tabBack": "q", - "closeTab": "x", - "openTab": "t", - "openTabUrl": "https://duckduckgo.com/" + "openTabUrl": "https://duckduckgo.com/", + "modifier": "", + "bindings": { + "hintToggle": "f", + "newTabHintToggle": "shift+f", + "scrollUp": "k", + "scrollDown": "j", + "scrollLeft": "h", + "scrollRight": "l", + "scrollUpHalfPage": "u", + "scrollDownHalfPage": "d", + "goToPageTop": "g g", + "goToPageBottom": "shift+g", + "goBack": "shift+h", + "goForward": "shift+l", + "reload": "r", + "tabForward": "w", + "tabBack": "q", + "closeTab": "x", + "openTab": "t" + } } From 62cc47f28f7184af81be265a84ec6622973dd32c Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Mon, 20 Jul 2020 13:04:14 +0200 Subject: [PATCH 12/19] Remove deprecated global.js The Vimari Extension is used as the backend for the Extension, therefore no longer requiring the use of the global.js file. --- Vimari Extension/js/global.js | 155 ------------------------------- Vimari.xcodeproj/project.pbxproj | 4 - 2 files changed, 159 deletions(-) delete mode 100644 Vimari Extension/js/global.js diff --git a/Vimari Extension/js/global.js b/Vimari Extension/js/global.js deleted file mode 100644 index 695c50f..0000000 --- a/Vimari Extension/js/global.js +++ /dev/null @@ -1,155 +0,0 @@ -// Function to handle messages... all messages are sent to this function -function handleMessage(msg) { - // Attempt to call a function with the same name as the message name - switch (msg.name) { - case 'getSettings' : - getSettings(msg); - break; - case 'openTab' : - openTab(); - break; - case 'closeTab': - closeTab(msg.message); - break; - case 'changeTab' : - changeTab(msg.message); - break; - } -} - -// Dispatch a message to a tab's page or reader view -function dispatchMessage(target, name, message) { - if (target) { - // Do some checks on the target to make sure we aren't trying to send a - // message to inaccessible tabs (e.g. Top Sites) - if (target.page && typeof target.page.dispatchMessage === "function") { - target.page.dispatchMessage(name, message); - } else if (typeof target.dispatchMessage === "function") { - target.dispatchMessage(name, message); - } - } -} - -// Pass the settings on to the injected script -function getSettings(event) { - var settings = { - 'linkHintCharacters': safari.extension.settings.linkHintCharacters, - 'hintToggle': safari.extension.settings.hintToggle, - 'newTabHintToggle': safari.extension.settings.newTabHintToggle, - 'tabForward': safari.extension.settings.tabForward, - 'tabBack': safari.extension.settings.tabBack, - 'scrollDown': safari.extension.settings.scrollDown, - 'scrollUp': safari.extension.settings.scrollUp, - 'scrollLeft': safari.extension.settings.scrollLeft, - 'scrollRight': safari.extension.settings.scrollRight, - 'goBack': safari.extension.settings.goBack, - 'goForward': safari.extension.settings.goForward, - 'reload': safari.extension.settings.reload, - 'scrollDownHalfPage': safari.extension.settings.scrollDownHalfPage, - 'scrollUpHalfPage': safari.extension.settings.scrollUpHalfPage, - 'goToPageBottom': safari.extension.settings.goToPageBottom, - 'goToPageTop': safari.extension.settings.goToPageTop, - 'closeTab': safari.extension.settings.closeTab, - 'closeTabReverse': safari.extension.settings.closeTabReverse, - 'openTab': safari.extension.settings.openTab, - 'modifier': safari.extension.settings.modifier, - 'scrollSize': safari.extension.settings.scrollSize, - 'excludedUrls': safari.extension.settings.excludedUrls, - 'detectByCursorStyle': safari.extension.settings.detectByCursorStyle - }; - - dispatchMessage(event.target, 'setSettings', settings); -} - -/* - * Changes to the next avail tab - * - * dir - 1 forwards, 0 backwards - */ -function changeTab(dir) { - var tabs = safari.application.activeBrowserWindow.tabs, - i; - - for (i = 0; i < tabs.length; i++) { - if (tabs[i] === safari.application.activeBrowserWindow.activeTab) { - if (dir === 1) { - if ((i + 1) === tabs.length) { - tabs[0].activate(); - } else { - tabs[i + 1].activate(); - } - } else { - if (i === 0) { - tabs[tabs.length - 1].activate(); - } else { - tabs[i - 1].activate(); - } - } - return; - } - } -} - -/* - * Closes to current tab - * - * dir - 1 forwards, 0 backwards - */ -function closeTab(dir) { - var tab = safari.application.activeBrowserWindow.activeTab; - changeTab(dir); - tab.close(); -} - -/* - * Opens a new tab - */ -function openTab() { - var win = safari.application.activeBrowserWindow; - win.openTab(); -} - -/* - * Get the active tab - * - */ -function getActiveTab() { - var tabs = safari.application.activeBrowserWindow.tabs, - i; - - for (i = 0; i < tabs.length; i++) { - if (tabs[i] === safari.application.activeBrowserWindow.activeTab) { - return i; - } - } -} - -/* - * Disable extension on non active tabs, - * enable on active tab - * - * Need to do it in 2 seperate loops to make sure all tabs are disabled first - */ -function activateTab() { - var tabs = safari.application.activeBrowserWindow.tabs, - i; - - for (i = 0; i < tabs.length; i++) { - dispatchMessage(safari.application.activeBrowserWindow.tabs[i], 'setActive', false); - } - - for (i = 0; i < tabs.length; i++) { - if (tabs[i] === safari.application.activeBrowserWindow.activeTab) { - dispatchMessage(safari.application.activeBrowserWindow.tabs[i], 'setActive', true); - } - } - -} - -safari.application.addEventListener('message', handleMessage, false); - -// Need to detect if a new tab becomes active and if so, reload the extension -safari.application.addEventListener('activate', function (event) { - activateTab(); - getSettings(event); -}, true); diff --git a/Vimari.xcodeproj/project.pbxproj b/Vimari.xcodeproj/project.pbxproj index ad51723..40f2116 100644 --- a/Vimari.xcodeproj/project.pbxproj +++ b/Vimari.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ E380F2672331806500640547 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E380F2652331806500640547 /* SafariExtensionViewController.xib */; }; E380F26C2331806500640547 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = E380F26B2331806500640547 /* ToolbarItemIcon.pdf */; }; E380F283233183EF00640547 /* link-hints.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F278233183EE00640547 /* link-hints.js */; }; - E380F284233183EF00640547 /* global.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F279233183EE00640547 /* global.js */; }; E380F285233183EF00640547 /* injected.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F27A233183EE00640547 /* injected.js */; }; E380F286233183EF00640547 /* mocks.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F27B233183EE00640547 /* mocks.js */; }; E380F287233183EF00640547 /* keyboard-utils.js in Resources */ = {isa = PBXBuildFile; fileRef = E380F27C233183EE00640547 /* keyboard-utils.js */; }; @@ -77,7 +76,6 @@ E380F26B2331806500640547 /* ToolbarItemIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ToolbarItemIcon.pdf; sourceTree = ""; }; E380F26D2331806500640547 /* Vimari_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Vimari_Extension.entitlements; sourceTree = ""; }; E380F278233183EE00640547 /* link-hints.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "link-hints.js"; sourceTree = ""; }; - E380F279233183EE00640547 /* global.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = global.js; sourceTree = ""; }; E380F27A233183EE00640547 /* injected.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = injected.js; sourceTree = ""; }; E380F27B233183EE00640547 /* mocks.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = mocks.js; sourceTree = ""; }; E380F27C233183EE00640547 /* keyboard-utils.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "keyboard-utils.js"; sourceTree = ""; }; @@ -176,7 +174,6 @@ isa = PBXGroup; children = ( E380F278233183EE00640547 /* link-hints.js */, - E380F279233183EE00640547 /* global.js */, E380F27A233183EE00640547 /* injected.js */, E380F27B233183EE00640547 /* mocks.js */, E380F27C233183EE00640547 /* keyboard-utils.js */, @@ -300,7 +297,6 @@ E380F28B233183EF00640547 /* injected.css in Resources */, E380F285233183EF00640547 /* injected.js in Resources */, E380F287233183EF00640547 /* keyboard-utils.js in Resources */, - E380F284233183EF00640547 /* global.js in Resources */, E380F289233183EF00640547 /* vimium-scripts.js in Resources */, E380F2672331806500640547 /* SafariExtensionViewController.xib in Resources */, E380F286233183EF00640547 /* mocks.js in Resources */, From 7d1ac9bf78474e99670073a0d8b9aed37872f02a Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Mon, 20 Jul 2020 13:13:42 +0200 Subject: [PATCH 13/19] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87982bf..69512ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Changelog ------------- ### Unreleased +* Add user customisation (based on the work of @nieldm [#163](https://github.com/televator-apps/vimari/pull/163)). +* Update Vimari interface to allow users access to their configuration. +* Remove `closeTabReverse` action. ### 2.0.3 (2019-09-26) From ebeb3af3701d0240068b1da708125f707029060f Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 25 Jul 2020 10:39:16 +0200 Subject: [PATCH 14/19] Remove ambiguity about message dispatch The .updateSettings and .fallbackSettings methods used the activePage as target for their dispatch. However we have access to the requesting page through the .messageRecieved function which should be used instead. --- Vimari Extension/SafariExtensionHandler.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Vimari Extension/SafariExtensionHandler.swift b/Vimari Extension/SafariExtensionHandler.swift index e80d5ac..f238730 100644 --- a/Vimari Extension/SafariExtensionHandler.swift +++ b/Vimari Extension/SafariExtensionHandler.swift @@ -63,7 +63,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { case .closeTab: closeTab(from: page) case .updateSettings: - updateSettings() + updateSettings(page: page) case .none: NSLog("Received message with unsupported type: \(messageName)") } @@ -163,7 +163,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } } - private func updateSettings() { + private func updateSettings(page: SFSafariPage) { do { let settings: [String: Any] if let userSettings = try? configuration.getUserSettings() { @@ -171,20 +171,16 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } else { settings = try configuration.getDefaultSettings() } - SFSafariApplication.getActivePage { - $0?.dispatch(settings: settings) - } + page.dispatch(settings: settings) } catch { NSLog(error.localizedDescription) } } - private func fallbackSettings() { + private func fallbackSettings(page: SFSafariPage) { do { let settings = try configuration.getUserSettings() - SFSafariApplication.getActivePage { - $0?.dispatch(settings: settings) - } + page.dispatch(settings: settings) } catch { NSLog(error.localizedDescription) } From 240ffc7f908c0c71317a5525541e84b44ef15103 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 25 Jul 2020 12:04:40 +0200 Subject: [PATCH 15/19] Extract extension communication The communication between the safari extension and the actual JavaScript can be difficult to understand at first, therefore I decided to abstract the logic into a separate class/file. Thereby giving a name to logic indicating the communication with a separate entity. --- Vimari Extension/Info.plist | 4 +++ .../js/SafariExtensionCommunicator.js | 27 +++++++++++++++++++ Vimari Extension/js/injected.js | 10 +++---- Vimari.xcodeproj/project.pbxproj | 4 +++ 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 Vimari Extension/js/SafariExtensionCommunicator.js diff --git a/Vimari Extension/Info.plist b/Vimari Extension/Info.plist index 575d587..8a2de28 100644 --- a/Vimari Extension/Info.plist +++ b/Vimari Extension/Info.plist @@ -30,6 +30,10 @@ $(PRODUCT_MODULE_NAME).SafariExtensionHandler SFSafariContentScript + + Script + SafariExtensionCommunicator.js + Script keyboard-utils.js diff --git a/Vimari Extension/js/SafariExtensionCommunicator.js b/Vimari Extension/js/SafariExtensionCommunicator.js new file mode 100644 index 0000000..8814ad4 --- /dev/null +++ b/Vimari Extension/js/SafariExtensionCommunicator.js @@ -0,0 +1,27 @@ +var SafariExtensionCommunicator = (function () { + 'use strict' + var publicAPI = {} + + var sendMessage = function(msgName) { + safari.extension.dispatchMessage(msgName) + } + + publicAPI.requestSettingsUpdate = function() { + sendMessage("updateSettings") + } + publicAPI.requestNewTab = function() { + sendMessage("openNewTab") + } + publicAPI.requestTabForward = function() { + sendMessage("tabForward") + } + publicAPI.requestTabBackward = function() { + sendMessage("tabBackward") + } + publicAPI.requestCloseTab = function () { + sendMessage("closeTab") + } + + // Return only the public methods. + return publicAPI; +})(); diff --git a/Vimari Extension/js/injected.js b/Vimari Extension/js/injected.js index 90f70af..95bf40d 100644 --- a/Vimari Extension/js/injected.js +++ b/Vimari Extension/js/injected.js @@ -37,10 +37,10 @@ var actionMap = { activateLinkHintsMode(true, false); }, 'tabForward': - function() { safari.extension.dispatchMessage("tabForward"); }, + function() { SafariExtensionCommunicator.requestTabForward(); }, 'tabBack': - function() { safari.extension.dispatchMessage("tabBackward"); }, + function() { SafariExtensionCommunicator.requestTabBackward() }, 'scrollDown': function() { window.scrollBy(0, settings.scrollSize); }, @@ -67,7 +67,7 @@ var actionMap = { function() { openNewTab(); }, 'closeTab': - function() { safari.extension.dispatchMessage("closeTab"); }, + function() { SafariExtensionCommunicator.requestCloseTab(); }, 'scrollDownHalfPage': function() { window.scrollBy(0, window.innerHeight / 2); }, @@ -265,7 +265,7 @@ function isExcludedUrl(storedExcludedUrls, currentUrl) { function openNewTab() { console.log("-- Open new empty tab --"); - safari.extension.dispatchMessage("openNewTab"); + SafariExtensionCommunicator.requestNewTab() } // These formations removes the protocol and www so that @@ -293,7 +293,7 @@ function inIframe () { if(!inIframe()){ safari.self.addEventListener("message", messageHandler); - safari.extension.dispatchMessage("updateSettings"); + SafariExtensionCommunicator.requestSettingsUpdate() } function messageHandler(event){ diff --git a/Vimari.xcodeproj/project.pbxproj b/Vimari.xcodeproj/project.pbxproj index 40f2116..56635a7 100644 --- a/Vimari.xcodeproj/project.pbxproj +++ b/Vimari.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 65E444F324CC3A1B008EA1DC /* SafariExtensionCommunicator.js in Resources */ = {isa = PBXBuildFile; fileRef = 65E444F224CC3A1B008EA1DC /* SafariExtensionCommunicator.js */; }; B1E3C17023A65ED400A56807 /* ConfigurationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E3C16F23A65ED400A56807 /* ConfigurationModel.swift */; }; B1FD3B9923A588DE00677A52 /* defaultSettings.json in Resources */ = {isa = PBXBuildFile; fileRef = B1FD3B9823A588DE00677A52 /* defaultSettings.json */; }; E320D0662337FC9800F2C3A4 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = E320D0652337FC9800F2C3A4 /* Credits.rtf */; }; @@ -56,6 +57,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 65E444F224CC3A1B008EA1DC /* SafariExtensionCommunicator.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = SafariExtensionCommunicator.js; sourceTree = ""; }; B1E3C16F23A65ED400A56807 /* ConfigurationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationModel.swift; sourceTree = ""; }; B1FD3B9823A588DE00677A52 /* defaultSettings.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = defaultSettings.json; sourceTree = ""; }; E320D0652337FC9800F2C3A4 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; @@ -178,6 +180,7 @@ E380F27B233183EE00640547 /* mocks.js */, E380F27C233183EE00640547 /* keyboard-utils.js */, E380F27D233183EE00640547 /* lib */, + 65E444F224CC3A1B008EA1DC /* SafariExtensionCommunicator.js */, ); path = js; sourceTree = ""; @@ -294,6 +297,7 @@ files = ( E380F26C2331806500640547 /* ToolbarItemIcon.pdf in Resources */, B1FD3B9923A588DE00677A52 /* defaultSettings.json in Resources */, + 65E444F324CC3A1B008EA1DC /* SafariExtensionCommunicator.js in Resources */, E380F28B233183EF00640547 /* injected.css in Resources */, E380F285233183EF00640547 /* injected.js in Resources */, E380F287233183EF00640547 /* keyboard-utils.js in Resources */, From 6554ad9a13ffff03c9ab8b21d9fc6801002ce076 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 25 Jul 2020 12:19:31 +0200 Subject: [PATCH 16/19] Incorporate message handler in SafariExtensionCommunicator In line with the previous commit (240ffc7) we move the assignment of an event listener for the messages from the Extension to the SafariExtensionCommunicator. --- Vimari Extension/js/SafariExtensionCommunicator.js | 7 +++++-- Vimari Extension/js/injected.js | 14 +++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Vimari Extension/js/SafariExtensionCommunicator.js b/Vimari Extension/js/SafariExtensionCommunicator.js index 8814ad4..9afe592 100644 --- a/Vimari Extension/js/SafariExtensionCommunicator.js +++ b/Vimari Extension/js/SafariExtensionCommunicator.js @@ -1,7 +1,10 @@ -var SafariExtensionCommunicator = (function () { +var SafariExtensionCommunicator = (function (msgHandler) { 'use strict' var publicAPI = {} + // Connect the provided message handler to the received messages. + safari.self.addEventListener("message", msgHandler) + var sendMessage = function(msgName) { safari.extension.dispatchMessage(msgName) } @@ -24,4 +27,4 @@ var SafariExtensionCommunicator = (function () { // Return only the public methods. return publicAPI; -})(); +}); diff --git a/Vimari Extension/js/injected.js b/Vimari Extension/js/injected.js index 95bf40d..f016c52 100644 --- a/Vimari Extension/js/injected.js +++ b/Vimari Extension/js/injected.js @@ -25,7 +25,8 @@ var topWindow = (window.top === window), extensionActive = true, insertMode = false, shiftKeyToggle = false, - hudDuration = 5000; + hudDuration = 5000, + extensionCommunicator = SafariExtensionCommunicator(messageHandler); var actionMap = { 'hintToggle' : function() { @@ -37,10 +38,10 @@ var actionMap = { activateLinkHintsMode(true, false); }, 'tabForward': - function() { SafariExtensionCommunicator.requestTabForward(); }, + function() { extensionCommunicator.requestTabForward(); }, 'tabBack': - function() { SafariExtensionCommunicator.requestTabBackward() }, + function() { extensionCommunicator.requestTabBackward() }, 'scrollDown': function() { window.scrollBy(0, settings.scrollSize); }, @@ -67,7 +68,7 @@ var actionMap = { function() { openNewTab(); }, 'closeTab': - function() { SafariExtensionCommunicator.requestCloseTab(); }, + function() { extensionCommunicator.requestCloseTab(); }, 'scrollDownHalfPage': function() { window.scrollBy(0, window.innerHeight / 2); }, @@ -265,7 +266,7 @@ function isExcludedUrl(storedExcludedUrls, currentUrl) { function openNewTab() { console.log("-- Open new empty tab --"); - SafariExtensionCommunicator.requestNewTab() + extensionCommunicator.requestNewTab() } // These formations removes the protocol and www so that @@ -292,8 +293,7 @@ function inIframe () { } if(!inIframe()){ - safari.self.addEventListener("message", messageHandler); - SafariExtensionCommunicator.requestSettingsUpdate() + extensionCommunicator.requestSettingsUpdate() } function messageHandler(event){ From 0186995aee063558cc03389747920c6827d64558 Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 25 Jul 2020 12:34:59 +0200 Subject: [PATCH 17/19] Remove legacy handleMessage function --- Vimari Extension/js/injected.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Vimari Extension/js/injected.js b/Vimari Extension/js/injected.js index f016c52..52b7173 100644 --- a/Vimari Extension/js/injected.js +++ b/Vimari Extension/js/injected.js @@ -210,21 +210,6 @@ function isEmbed(element) { return ["EMBED", "OBJECT"].indexOf(element.tagName) // Message handling functions // ========================== -/* - * All messages are handled by this function - */ -function handleMessage(msg) { - // Attempt to call a function with the same name as the message name - switch(msg.name) { - case 'setSettings': - setSettings(msg.message); - break; - case 'setActive': - setActive(msg.message); - break; - } -} - /* * Callback to pass settings to injected script */ From b517250854f7abadc4ef5ce434e47aa2783278ea Mon Sep 17 00:00:00 2001 From: Nick Belzer Date: Sat, 25 Jul 2020 12:35:12 +0200 Subject: [PATCH 18/19] Remove redundant openNewTab function Likely because of the previous working the code for opening a new tab was extracted into a separate function. However now that we call the Vimari Extension for this behaviour the code should be called in the same way as for example the close tab action. Therefore it is placed directly in the actionmap. --- Vimari Extension/js/injected.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Vimari Extension/js/injected.js b/Vimari Extension/js/injected.js index 52b7173..b1bd6cc 100644 --- a/Vimari Extension/js/injected.js +++ b/Vimari Extension/js/injected.js @@ -65,7 +65,7 @@ var actionMap = { function() { window.location.reload(); }, 'openTab': - function() { openNewTab(); }, + function() { extensionCommunicator.requestNewTab(); }, 'closeTab': function() { extensionCommunicator.requestCloseTab(); }, @@ -249,11 +249,6 @@ function isExcludedUrl(storedExcludedUrls, currentUrl) { return false; } -function openNewTab() { - console.log("-- Open new empty tab --"); - extensionCommunicator.requestNewTab() -} - // These formations removes the protocol and www so that // the regexp can catch less AND more specific excluded // domains than the current URL. From aa06c1f64a3a23ca4c693fd60438b70e34f88170 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 31 Jul 2020 11:39:13 +0200 Subject: [PATCH 19/19] Update Vimari Extension/ConfigurationModel.swift Co-authored-by: Daniel Compton --- Vimari Extension/ConfigurationModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vimari Extension/ConfigurationModel.swift b/Vimari Extension/ConfigurationModel.swift index 55bdd10..879b677 100644 --- a/Vimari Extension/ConfigurationModel.swift +++ b/Vimari Extension/ConfigurationModel.swift @@ -112,7 +112,7 @@ private extension Bundle { private extension FileManager { static var documentDirectoryURL: URL { - let documentDirectoryURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) + let documentDirectoryURL = try! FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) return documentDirectoryURL } }