diff --git a/App/Browser View/BrowserViewController+Keyboard.swift b/App/Browser View/BrowserViewController+Keyboard.swift index 2e03789..ca86b07 100644 --- a/App/Browser View/BrowserViewController+Keyboard.swift +++ b/App/Browser View/BrowserViewController+Keyboard.swift @@ -16,6 +16,7 @@ protocol VIMBindings extension BrowserViewController: VIMBindings { + static let keyboardScrollingEnabled = false // this is tricky... static let keyboardScrollAmount: CGFloat = 33.0 override var keyCommands: [UIKeyCommand]? { @@ -29,7 +30,7 @@ extension BrowserViewController: VIMBindings override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if action == #selector(VIMBindings.scrollDownPressed) || action == #selector(VIMBindings.scrollUpPressed) { - return webView._contentViewIsFirstResponder && webView._currentContentView().isFocusingElement == false + return Self.keyboardScrollingEnabled && webView._contentViewIsFirstResponder && webView._currentContentView().isFocusingElement == false } return super.canPerformAction(action, withSender: sender) diff --git a/App/Resources/Tagger.js b/App/Resources/Tagger.js new file mode 100644 index 0000000..66ea511 --- /dev/null +++ b/App/Resources/Tagger.js @@ -0,0 +1,181 @@ + +class ElementTagger { + constructor() { + this.isTagged = false; + this.tagList = {}; + + const baseMnemonics = [ + 'a', 's', 'd', /* 'f', */ 'g', + 'q', 'w', 'e', 'r', 't', + 'z', 'x', 'c', 'v', 'b', + 'y', 'u', 'i', 'o', 'p', + 'h', 'j', 'k', 'l', + 'n', 'm' + ]; + + let additionalMnemonics = []; + baseMnemonics.forEach( (letter) => { + additionalMnemonics.push("" + letter.toUpperCase() + letter); + }); + + let mnemonics = baseMnemonics.concat(additionalMnemonics); + + mnemonics.forEach( (letter) => { + this.tagList[letter] = undefined; + }); + } + + tagElement(elem) { + if (!this.isBoundedByViewport(elem)) { + return; + } + + let keys = Object.keys(this.tagList); + for (let key_i in keys) { + let key = keys[key_i]; + if (this.tagList[key] === undefined) { + if ( this.addOverlay(elem, key) ) { + this.tagList[key] = elem; + } + + break; + } + } + } + + isBoundedByViewport(elem) { + const viewport = window.visualViewport; + const rect = elem.getClientRects()[0]; + + if (rect == undefined) { + return false; + } + + const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + const elemTop = (scrollTop + rect.top); + if (elemTop > viewport.pageTop && elemTop < (viewport.pageTop + viewport.height)) { + return true; + } + + return false; + } + + addOverlay(parentElem, mnemonic) { + var rects = parentElem.getClientRects(); + const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; + + if (parentElem._overlayElem == undefined) { + var rect = undefined; + for (let i = 0; i < rects.length; i++) { + if (rects[i] !== undefined) { + rect = rects[i]; + break; + } + } + + if (rect === undefined) { + return false; + } + + let elem = document.createElement('div'); + elem.style.position = 'absolute'; + elem.style.border = '1px solid blue'; + elem.style.borderRadius = '5px'; + elem.style.background = 'rgba(0, 0, 255, 0.8)'; + elem.style.color = 'white'; + elem.style.font = '12px Helvetica bold'; + elem.style.padding = '2px'; + + + elem.innerText = "[" + mnemonic + "]"; + elem.style.top = (rect.top + scrollTop) + 'px'; + elem.style.left = (rect.left + scrollLeft) + 'px'; + document.body.appendChild(elem); + + parentElem._overlayElem = elem; + + return true; + } + + return false; + } + + removeOverlay(elem) { + let overlayElem = elem._overlayElem; + if (overlayElem != undefined) { + document.body.removeChild(overlayElem); + elem._overlayElem = undefined; + } + } + + tagDocument() { + var elt = document.getElementsByTagName("a"); + for (let i = 0; i < elt.length; i++) { + this.tagElement(elt[i]); + } + + this.isTagged = true; + } + + untagDocument() { + const keys = Object.keys(this.tagList); + for (let key_i in keys) { + let elem = this.tagList[keys[key_i]]; + if (elem !== undefined) { + this.removeOverlay(elem); + this.tagList[keys[key_i]] = undefined; + } + } + + this.isTagged = false; + } + + clickLinkWithTag(tag) { + if (this.tagList[tag] !== undefined) { + this.tagList[tag].click(); + } + } +} + +(function() { + let tagger = new ElementTagger(); + + var counter = 0; + var tagAccum = []; + document.addEventListener('keypress', (event) => { + if (document.activeElement !== document.body) { + return; + } + + const keyName = String.fromCharCode(event.charCode); + if (keyName == undefined) { + return; + } + + if (keyName == 'f') { + if (++counter == 2) { + counter = 0; + if (tagger.isTagged) { + tagger.untagDocument(); + } else { + tagger.tagDocument(); + } + } + } else { + // Uppercase + if (tagAccum.length > 0 || keyName.toUpperCase() == keyName) { + tagAccum.push(keyName); + } + + if (tagAccum.length == 2) { + tagger.clickLinkWithTag(tagAccum.join('')); + tagger.untagDocument(); + tagAccum = []; + } else if (tagAccum.length == 0) { + tagger.clickLinkWithTag(keyName); + tagger.untagDocument(); + } + } + }); +})(); diff --git a/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m b/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m index 821ba2b..5a379aa 100644 --- a/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m +++ b/App/Web Process Bundle Bridge/SBRProcessBundleBridge.m @@ -32,6 +32,8 @@ _WKUserStyleSheet *_darkModeStyleSheet; WKUserScript *_readabilityScript; + + NSArray *_userScripts; } - (void)tearDown @@ -91,6 +93,12 @@ _webViewConfiguration = webViewConfiguration; + // User scripts + WKUserContentController *userContentController = [_webViewConfiguration userContentController]; + for (WKUserScript *script in [self _userScripts]) { + [userContentController addUserScript:script]; + } + // Instantiate web view WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:webViewConfiguration]; @@ -106,6 +114,25 @@ return self; } +- (WKUserScript *)_loadScriptForResource:(NSString *)resourceName withExtension:(NSString *)extension +{ + NSURL *url = [[NSBundle mainBundle] URLForResource:resourceName withExtension:extension]; + NSString *source = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + + return [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; +} + +- (NSArray *)_userScripts +{ + if (!_userScripts) { + _userScripts = @[ + [self _loadScriptForResource:@"Tagger" withExtension:@"js"], + ]; + } + + return _userScripts; +} + #pragma mark - (void)webProcessDidConnect @@ -168,10 +195,7 @@ WKUserContentController *userContentController = [_webViewConfiguration userContentController]; if (!_readabilityScript) { - NSURL *readabilityJSURL = [[NSBundle mainBundle] URLForResource:@"Readability" withExtension:@"js"]; - NSString *readabilityJSSource = [NSString stringWithContentsOfURL:readabilityJSURL encoding:NSUTF8StringEncoding error:nil]; - - _readabilityScript = [[WKUserScript alloc] initWithSource:readabilityJSSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; + _readabilityScript = [self _loadScriptForResource:@"Readability" withExtension:@"js"]; } [userContentController _addUserScriptImmediately:_readabilityScript]; diff --git a/SBrowser.xcodeproj/project.pbxproj b/SBrowser.xcodeproj/project.pbxproj index 19326f2..9c80507 100644 --- a/SBrowser.xcodeproj/project.pbxproj +++ b/SBrowser.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */; }; CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BD324E77BF900D2BDCC /* HistoryItem.swift */; }; CD97CF9225D5BE6F00288FEE /* NavigationControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */; }; + CDC4A1CF25E9D8F7007D33C6 /* Tagger.js in Resources */ = {isa = PBXBuildFile; fileRef = CDC4A1CE25E9D8F7007D33C6 /* Tagger.js */; }; CDC5DA3A25DB774D00BA8D99 /* Readability.js in Resources */ = {isa = PBXBuildFile; fileRef = CDC5DA3925DB774D00BA8D99 /* Readability.js */; }; CDC5DA3E25DB7C2C00BA8D99 /* ReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC5DA3D25DB7C2C00BA8D99 /* ReaderViewController.swift */; }; CDC5DA4025DB7EAC00BA8D99 /* reader.html in Resources */ = {isa = PBXBuildFile; fileRef = CDC5DA3F25DB7EAC00BA8D99 /* reader.html */; }; @@ -138,6 +139,7 @@ CD853BD024E778B800D2BDCC /* History.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = History.xcdatamodel; sourceTree = ""; }; CD853BD324E77BF900D2BDCC /* HistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItem.swift; sourceTree = ""; }; CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControlsView.swift; sourceTree = ""; }; + CDC4A1CE25E9D8F7007D33C6 /* Tagger.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Tagger.js; sourceTree = ""; }; CDC5DA3925DB774D00BA8D99 /* Readability.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Readability.js; sourceTree = ""; }; CDC5DA3D25DB7C2C00BA8D99 /* ReaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderViewController.swift; sourceTree = ""; }; CDC5DA3F25DB7EAC00BA8D99 /* reader.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = reader.html; sourceTree = ""; }; @@ -182,6 +184,7 @@ children = ( 1A14FC2524D251BD009B3F83 /* darkmode.css */, CDC5DA3925DB774D00BA8D99 /* Readability.js */, + CDC4A1CE25E9D8F7007D33C6 /* Tagger.js */, CDC5DA3F25DB7EAC00BA8D99 /* reader.html */, ); path = Resources; @@ -465,6 +468,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CDC4A1CF25E9D8F7007D33C6 /* Tagger.js in Resources */, 1A14FC2624D251BD009B3F83 /* darkmode.css in Resources */, CDC5DA4025DB7EAC00BA8D99 /* reader.html in Resources */, 1ADFF46C24C7DE54006DC7AE /* LaunchScreen.storyboard in Resources */,