From 355da18d3d6232620ebd7548f8d41b6f1af5aa08 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 20 Aug 2017 21:19:42 +0900 Subject: add follow fot a tags --- src/content/follow.js | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/content/follow.js (limited to 'src/content/follow.js') diff --git a/src/content/follow.js b/src/content/follow.js new file mode 100644 index 0000000..c0b7a44 --- /dev/null +++ b/src/content/follow.js @@ -0,0 +1,110 @@ +import Hint from './hint'; +import HintKeyProducer from './hint-key-producer'; + +const DEFAULT_HINT_CHARSET = 'abcdefghijklmnopqrstuvwxyz' + +export default class Follow { + constructor(doc) { + this.doc = doc; + this.hintElements = {}; + this.keys = []; + + // TODO activate input elements and push button elements + let links = Follow.getTargetElements(doc); + + this.addHints(links); + + this.boundKeydown = this.handleKeydown.bind(this); + doc.addEventListener('keydown', this.boundKeydown); + } + + addHints(elements) { + let producer = new HintKeyProducer(DEFAULT_HINT_CHARSET); + Array.prototype.forEach.call(elements, (ele) => { + let keys = producer.produce(); + let hint = new Hint(ele, keys) + + this.hintElements[keys] = hint; + }); + } + + handleKeydown(e) { + let keyCode = e.keyCode; + if (keyCode === KeyboardEvent.DOM_VK_ESCAPE) { + this.remove(); + return; + } else if (keyCode === KeyboardEvent.DOM_VK_ENTER || + keyCode === KeyboardEvent.DOM_VK_RETURN) { + this.openUrl(this.keys); + return; + } else if (Follow.availableKey(keyCode)) { + this.keys.push(keyCode); + } else if (keyCode === KeyboardEvent.DOM_VK_BACK_SPACE || + keyCode === KeyboardEvent.DOM_VK_DELETE) { + this.keys.pop(); + } + + + let keysAsString = Follow.codeChars(this.keys); + let shown = Object.keys(this.hintElements).filter((key) => { + return key.startsWith(keysAsString); + }); + let hidden = Object.keys(this.hintElements).filter((key) => { + return !key.startsWith(keysAsString); + }); + if (shown.length == 0) { + this.remove(); + return; + } else if (shown.length == 1) { + this.openUrl(this.keys); + return; + } + + shown.forEach((key) => { + this.hintElements[key].show(); + }); + hidden.forEach((key) => { + this.hintElements[key].hide(); + }); + } + + + remove() { + this.doc.removeEventListener("keydown", this.boundKeydown); + Object.keys(this.hintElements).forEach((key) => { + this.hintElements[key].remove(); + }); + } + + openUrl(keys) { + let chars = Follow.codeChars(keys); + this.hintElements[chars].activate(); + } + + static availableKey(keyCode) { + return ( + KeyboardEvent.DOM_VK_0 <= keyCode && keyCode <= KeyboardEvent.DOM_VK_9 || + KeyboardEvent.DOM_VK_A <= keyCode && keyCode <= KeyboardEvent.DOM_VK_Z + ); + } + + static codeChars(codes) { + const CHARCODE_ZERO = '0'.charCodeAt(0); + const CHARCODE_A = 'a'.charCodeAt(0); + + let chars = ''; + + for (let code of codes) { + if (KeyboardEvent.DOM_VK_0 <= code && code <= KeyboardEvent.DOM_VK_9) { + chars += String.fromCharCode(code - KeyboardEvent.DOM_VK_0 + CHARCODE_ZERO); + } else if (KeyboardEvent.DOM_VK_A <= code && code <= KeyboardEvent.DOM_VK_Z) { + chars += String.fromCharCode(code - KeyboardEvent.DOM_VK_A + CHARCODE_A); + } + } + return chars; + } + + static getTargetElements(doc) { + return doc.querySelectorAll('a') + } +} -- cgit v1.2.3 From c50f463bc12da7e3a5de490b714b4ff1ea8d3e56 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Mon, 21 Aug 2017 21:01:29 +0900 Subject: add follow tests --- src/content/follow.js | 16 +++++++++++++++- test/content/follow.html | 9 +++++++++ test/content/follow.test.js | 11 +++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 test/content/follow.html (limited to 'src/content/follow.js') diff --git a/src/content/follow.js b/src/content/follow.js index c0b7a44..d678351 100644 --- a/src/content/follow.js +++ b/src/content/follow.js @@ -105,6 +105,20 @@ export default class Follow { } static getTargetElements(doc) { - return doc.querySelectorAll('a') + let all = doc.querySelectorAll('a'); + let filtered = Array.prototype.filter.call(all, (e) => { + return Follow.isVisibleElement(e); + }); + return filtered; + } + + static isVisibleElement(element) { + var style = window.getComputedStyle(element); + if (style.display === 'none') { + return false; + } else if (style.visibility === 'hidden') { + return false; + } + return true; } } diff --git a/test/content/follow.html b/test/content/follow.html new file mode 100644 index 0000000..6bd8f87 --- /dev/null +++ b/test/content/follow.html @@ -0,0 +1,9 @@ + + + + link + invisible 1 + invisible 2 + not link + + diff --git a/test/content/follow.test.js b/test/content/follow.test.js index eb3d679..fd4f0bc 100644 --- a/test/content/follow.test.js +++ b/test/content/follow.test.js @@ -11,4 +11,15 @@ describe('Follow class', () => { expect(Follow.codeChars([])).to.be.equal(''); }); }); + + describe('#getTargetElements', () => { + beforeEach(() => { + document.body.innerHTML = __html__['test/content/follow.html']; + }); + + it('returns visible links', () => { + let links = Follow.getTargetElements(window.document); + expect(links).to.have.lengthOf(1); + }); + }); }); -- cgit v1.2.3 From 685164629da8a28fae19128a198ba6b9a57e55f9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Tue, 22 Aug 2017 21:51:29 +0900 Subject: remove follow on activated --- src/content/follow.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'src/content/follow.js') diff --git a/src/content/follow.js b/src/content/follow.js index d678351..ffa16b9 100644 --- a/src/content/follow.js +++ b/src/content/follow.js @@ -35,7 +35,8 @@ export default class Follow { return; } else if (keyCode === KeyboardEvent.DOM_VK_ENTER || keyCode === KeyboardEvent.DOM_VK_RETURN) { - this.openUrl(this.keys); + let chars = Follow.codeChars(this.keys); + this.hintElements[chars].activate(); return; } else if (Follow.availableKey(keyCode)) { this.keys.push(keyCode); @@ -44,20 +45,23 @@ export default class Follow { this.keys.pop(); } + this.refreshKeys(); + } - let keysAsString = Follow.codeChars(this.keys); + refreshKeys() { + let chars = Follow.codeChars(this.keys); let shown = Object.keys(this.hintElements).filter((key) => { - return key.startsWith(keysAsString); + return key.startsWith(chars); }); let hidden = Object.keys(this.hintElements).filter((key) => { - return !key.startsWith(keysAsString); + return !key.startsWith(chars); }); if (shown.length == 0) { this.remove(); return; } else if (shown.length == 1) { - this.openUrl(this.keys); - return; + this.remove(); + this.hintElements[chars].activate(); } shown.forEach((key) => { @@ -76,11 +80,6 @@ export default class Follow { }); } - openUrl(keys) { - let chars = Follow.codeChars(keys); - this.hintElements[chars].activate(); - } - static availableKey(keyCode) { return ( KeyboardEvent.DOM_VK_0 <= keyCode && keyCode <= KeyboardEvent.DOM_VK_9 || -- cgit v1.2.3