Tagger: Saka-key like keyboard navigation link tagging
This commit is contained in:
@@ -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)
|
||||
|
||||
181
App/Resources/Tagger.js
Normal file
181
App/Resources/Tagger.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -32,6 +32,8 @@
|
||||
|
||||
_WKUserStyleSheet *_darkModeStyleSheet;
|
||||
WKUserScript *_readabilityScript;
|
||||
|
||||
NSArray<WKUserScript *> *_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<WKUserScript *> *)_userScripts
|
||||
{
|
||||
if (!_userScripts) {
|
||||
_userScripts = @[
|
||||
[self _loadScriptForResource:@"Tagger" withExtension:@"js"],
|
||||
];
|
||||
}
|
||||
|
||||
return _userScripts;
|
||||
}
|
||||
|
||||
#pragma mark <SBRWebProcessDelegate>
|
||||
|
||||
- (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];
|
||||
|
||||
Reference in New Issue
Block a user