|
|
|
@ -8,6 +8,7 @@ export default class Follow { |
|
|
|
|
this.doc = doc; |
|
|
|
|
this.hintElements = {}; |
|
|
|
|
this.keys = []; |
|
|
|
|
this.onActivatedCallbacks = []; |
|
|
|
|
|
|
|
|
|
// TODO activate input elements and push button elements
|
|
|
|
|
let links = Follow.getTargetElements(doc); |
|
|
|
@ -36,7 +37,7 @@ export default class Follow { |
|
|
|
|
} else if (keyCode === KeyboardEvent.DOM_VK_ENTER || |
|
|
|
|
keyCode === KeyboardEvent.DOM_VK_RETURN) { |
|
|
|
|
let chars = Follow.codeChars(this.keys); |
|
|
|
|
this.hintElements[chars].activate(); |
|
|
|
|
this.activate(this.hintElements[chars].target); |
|
|
|
|
return; |
|
|
|
|
} else if (Follow.availableKey(keyCode)) { |
|
|
|
|
this.keys.push(keyCode); |
|
|
|
@ -45,6 +46,9 @@ export default class Follow { |
|
|
|
|
this.keys.pop(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
e.stopPropagation(); |
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
|
|
this.refreshKeys(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -61,7 +65,7 @@ export default class Follow { |
|
|
|
|
return; |
|
|
|
|
} else if (shown.length === 1) { |
|
|
|
|
this.remove(); |
|
|
|
|
this.hintElements[chars].activate(); |
|
|
|
|
this.activate(this.hintElements[chars].target); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
shown.forEach((key) => { |
|
|
|
@ -72,7 +76,6 @@ export default class Follow { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
remove() { |
|
|
|
|
this.doc.removeEventListener('keydown', this.boundKeydown); |
|
|
|
|
Object.keys(this.hintElements).forEach((key) => { |
|
|
|
@ -80,6 +83,14 @@ export default class Follow { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
activate(element) { |
|
|
|
|
this.onActivatedCallbacks.forEach(f => f(element)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
onActivated(f) { |
|
|
|
|
this.onActivatedCallbacks.push(f); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static availableKey(keyCode) { |
|
|
|
|
return ( |
|
|
|
|
KeyboardEvent.DOM_VK_0 <= keyCode && keyCode <= KeyboardEvent.DOM_VK_9 || |
|
|
|
@ -113,21 +124,26 @@ export default class Follow { |
|
|
|
|
return chars; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static inWindow(window, element) { |
|
|
|
|
let { |
|
|
|
|
top, left, bottom, right |
|
|
|
|
} = element.getBoundingClientRect(); |
|
|
|
|
return ( |
|
|
|
|
top >= 0 && left >= 0 && |
|
|
|
|
bottom <= (window.innerHeight || document.documentElement.clientHeight) && |
|
|
|
|
right <= (window.innerWidth || document.documentElement.clientWidth) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static getTargetElements(doc) { |
|
|
|
|
let all = doc.querySelectorAll('a'); |
|
|
|
|
let filtered = Array.prototype.filter.call(all, (e) => { |
|
|
|
|
return Follow.isVisibleElement(e); |
|
|
|
|
let all = doc.querySelectorAll('a,button,input,textarea'); |
|
|
|
|
let filtered = Array.prototype.filter.call(all, (element) => { |
|
|
|
|
let style = window.getComputedStyle(element); |
|
|
|
|
return style.display !== 'none' && |
|
|
|
|
style.visibility !== 'hidden' && |
|
|
|
|
element.type !== 'hidden' && |
|
|
|
|
Follow.inWindow(window, element); |
|
|
|
|
}); |
|
|
|
|
return filtered; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static isVisibleElement(element) { |
|
|
|
|
let style = window.getComputedStyle(element); |
|
|
|
|
if (style.display === 'none') { |
|
|
|
|
return false; |
|
|
|
|
} else if (style.visibility === 'hidden') { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|