From baf3dd02d5891df9a35af7d2f8f2eaa42ac6bb03 Mon Sep 17 00:00:00 2001 From: jez Date: Mon, 3 Jan 2011 04:29:12 +0800 Subject: Revise how the linkHints object is generated. Add lib/utils.js. Extend a template object rather than using prototype inheritance. The previous method created confusion about whether a given property resided in the base or in the specialization. --- lib/utils.js | 32 +++++++ linkHints.js | 289 ++++++++++++++++++++++++++++++---------------------------- manifest.json | 3 +- options.html | 1 + 4 files changed, 183 insertions(+), 142 deletions(-) create mode 100644 lib/utils.js diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 00000000..86c868b8 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,32 @@ +var utils = { + deepcopy: function(original) { + var result; + if (typeof original == 'object') { + if (original === null) { + result = null; + } else { + result = original.constructor === Array ? [] : {}; + for (var i in original) + if (original.hasOwnProperty(i)) + result[i] = this.deepcopy(original[i]); + } + } else { + result = original; + } + + return result; + }, + extendWithSuper: function(original, ext) { + var result = this.deepcopy(original); + var tmpSuper = result._super; + result._superFunctions = {}; + result._super = function(fname) { return this._superFunctions[fname].bind(this); } + for (var i in ext) + if (ext.hasOwnProperty(i)) { + if (typeof ext[i] == 'function' && typeof original[i] == 'function') + result._superFunctions[i] = this.deepcopy(original[i]); + result[i] = this.deepcopy(ext[i]); + } + return result; + }, +}; diff --git a/linkHints.js b/linkHints.js index e1d08376..a301846d 100644 --- a/linkHints.js +++ b/linkHints.js @@ -15,7 +15,7 @@ * A set of common operations shared by any link-hinting system. Some methods * are stubbed. */ -var linkHintsPrototype = { +var linkHintsBase = { hintMarkers: [], hintMarkerContainingDiv: null, // The characters that were typed in while in "link hints" mode. @@ -28,8 +28,10 @@ var linkHintsPrototype = { // Whether we have added to the page the CSS needed to display link hints. cssAdded: false, + /* + * To be called after linkHints has been generated from linkHintsBase. + */ init: function() { - // bind the event handlers to the appropriate instance of the prototype this.onKeyDownInMode = this.onKeyDownInMode.bind(this); this.onKeyUpInMode = this.onKeyUpInMode.bind(this); }, @@ -338,164 +340,169 @@ var linkHints; * Create the instance of linkHints, specialized based on the user settings. */ function initializeLinkHints() { - linkHints = Object.create(linkHintsPrototype); - linkHints.init(); if (settings.get('filterLinkHints') != "true") { // the default hinting system - linkHints['digitsNeeded'] = 1; - - linkHints['logXOfBase'] = function(x, base) { return Math.log(x) / Math.log(base); }; - - linkHints['initHintStringGenerator'] = function(visibleElements) { - this.digitsNeeded = Math.ceil(this.logXOfBase( - visibleElements.length, settings.get('linkHintCharacters').length)); - }; - - linkHints['hintStringGenerator'] = function(linkHintNumber) { - return this.numberToHintString(linkHintNumber, this.digitsNeeded); - }; - - /* - * Converts a number like "8" into a hint string like "JK". This is used to sequentially generate all of - * the hint text. The hint string will be "padded with zeroes" to ensure its length is equal to numHintDigits. - */ - linkHints['numberToHintString'] = function(number, numHintDigits) { - var base = settings.get('linkHintCharacters').length; - var hintString = []; - var remainder = 0; - do { - remainder = number % base; - hintString.unshift(settings.get('linkHintCharacters')[remainder]); - number -= remainder; - number /= Math.floor(base); - } while (number > 0); - - // Pad the hint string we're returning so that it matches numHintDigits. - var hintStringLength = hintString.length; - for (var i = 0; i < numHintDigits - hintStringLength; i++) - hintString.unshift(settings.get('linkHintCharacters')[0]); - return hintString.join(""); - }; - - linkHints['normalKeyDownHandler'] = function (event) { - var keyChar = getKeyChar(event); - if (!keyChar) - return; - - if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { - if (this.hintKeystrokeQueue.length == 0) { - this.deactivateMode(); - } else { - this.hintKeystrokeQueue.pop(); + linkHints = utils.extendWithSuper(linkHintsBase, { + + digitsNeeded: 1, + + logXOfBase: function(x, base) { return Math.log(x) / Math.log(base); }, + + initHintStringGenerator: function(visibleElements) { + this.digitsNeeded = Math.ceil(this.logXOfBase( + visibleElements.length, settings.get('linkHintCharacters').length)); + }, + + hintStringGenerator: function(linkHintNumber) { + return this.numberToHintString(linkHintNumber, this.digitsNeeded); + }, + + /* + * Converts a number like "8" into a hint string like "JK". This is used to sequentially generate all of + * the hint text. The hint string will be "padded with zeroes" to ensure its length is equal to numHintDigits. + */ + numberToHintString: function(number, numHintDigits) { + var base = settings.get('linkHintCharacters').length; + var hintString = []; + var remainder = 0; + do { + remainder = number % base; + hintString.unshift(settings.get('linkHintCharacters')[remainder]); + number -= remainder; + number /= Math.floor(base); + } while (number > 0); + + // Pad the hint string we're returning so that it matches numHintDigits. + var hintStringLength = hintString.length; + for (var i = 0; i < numHintDigits - hintStringLength; i++) + hintString.unshift(settings.get('linkHintCharacters')[0]); + return hintString.join(""); + }, + + normalKeyDownHandler: function (event) { + var keyChar = getKeyChar(event); + if (!keyChar) + return; + + if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { + if (this.hintKeystrokeQueue.length == 0) { + this.deactivateMode(); + } else { + this.hintKeystrokeQueue.pop(); + var matchString = this.hintKeystrokeQueue.join(""); + this.hintMarkers.filter(this.toggleHighlights.bind(this, matchString)); + } + } else if (settings.get('linkHintCharacters').indexOf(keyChar) >= 0) { + this.hintKeystrokeQueue.push(keyChar); var matchString = this.hintKeystrokeQueue.join(""); - this.hintMarkers.filter(this.toggleHighlights.bind(this, matchString)); + linksMatched = this.hintMarkers.filter(this.toggleHighlights.bind(this, matchString)); + if (linksMatched.length == 0) + this.deactivateMode(); + else if (linksMatched.length == 1) + this.activateLink(linksMatched[0].clickableItem); } - } else if (settings.get('linkHintCharacters').indexOf(keyChar) >= 0) { - this.hintKeystrokeQueue.push(keyChar); - var matchString = this.hintKeystrokeQueue.join(""); - linksMatched = this.hintMarkers.filter(this.toggleHighlights.bind(this, matchString)); - if (linksMatched.length == 0) - this.deactivateMode(); - else if (linksMatched.length == 1) - this.activateLink(linksMatched[0].clickableItem); } - }; + }); } else { - linkHints['linkTextKeystrokeQueue'] = []; + linkHints = utils.extendWithSuper(linkHintsBase, { + + linkTextKeystrokeQueue: [], - linkHints['hintStringGenerator'] = function(linkHintNumber) { - return (linkHintNumber + 1).toString(); - }; + hintStringGenerator: function(linkHintNumber) { + return (linkHintNumber + 1).toString(); + }, - linkHints['normalKeyDownHandler'] = function(event) { - if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { - if (this.linkTextKeystrokeQueue.length == 0 && this.hintKeystrokeQueue.length == 0) { - this.deactivateMode(); + normalKeyDownHandler: function(event) { + if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { + if (this.linkTextKeystrokeQueue.length == 0 && this.hintKeystrokeQueue.length == 0) { + this.deactivateMode(); + } else { + // backspace clears hint key queue first, then acts on link text key queue + if (this.hintKeystrokeQueue.pop()) + this.filterLinkHints(); + else { + this.linkTextKeystrokeQueue.pop(); + this.filterLinkHints(); + } + } + } else if (event.keyCode == keyCodes.enter) { + // activate the lowest-numbered link hint + for (var i = 0; i < this.hintMarkers.length; i++) + if (this.hintMarkers[i].getAttribute('filtered') != 'true') { + this.activateLink(this.hintMarkers[i].clickableItem); + break; + } } else { - // backspace clears hint key queue first, then acts on link text key queue - if (this.hintKeystrokeQueue.pop()) - this.filterLinkHints(); - else { - this.linkTextKeystrokeQueue.pop(); - this.filterLinkHints(); + var keyChar = getKeyChar(event); + if (!keyChar) + return; + + var linksMatched, matchString; + if (/[0-9]/.test(keyChar)) { + this.hintKeystrokeQueue.push(keyChar); + matchString = this.hintKeystrokeQueue.join(""); + linksMatched = this.hintMarkers.filter((function(linkMarker) { + if (linkMarker.getAttribute('filtered') == 'true') + return false; + return this.toggleHighlights(matchString, linkMarker); + }).bind(this)); + } else { + // since we might renumber the hints, the current hintKeyStrokeQueue + // should be rendered invalid (i.e. reset). + this.hintKeystrokeQueue = []; + this.linkTextKeystrokeQueue.push(keyChar); + matchString = this.linkTextKeystrokeQueue.join(""); + linksMatched = this.filterLinkHints(matchString); } - } - } else if (event.keyCode == keyCodes.enter) { - // activate the lowest-numbered link hint - for (var i = 0; i < this.hintMarkers.length; i++) - if (this.hintMarkers[i].getAttribute('filtered') != 'true') { - this.activateLink(this.hintMarkers[i].clickableItem); - break; - } - } else { - var keyChar = getKeyChar(event); - if (!keyChar) - return; - var linksMatched, matchString; - if (/[0-9]/.test(keyChar)) { - this.hintKeystrokeQueue.push(keyChar); - matchString = this.hintKeystrokeQueue.join(""); - linksMatched = this.hintMarkers.filter((function(linkMarker) { - if (linkMarker.getAttribute('filtered') == 'true') - return false; - return this.toggleHighlights(matchString, linkMarker); - }).bind(this)); - } else { - // since we might renumber the hints, the current hintKeyStrokeQueue - // should be rendered invalid (i.e. reset). - this.hintKeystrokeQueue = []; - this.linkTextKeystrokeQueue.push(keyChar); - matchString = this.linkTextKeystrokeQueue.join(""); - linksMatched = this.filterLinkHints(matchString); + if (linksMatched.length == 0) + this.deactivateMode(); + else if (linksMatched.length == 1) + this.activateLink(linksMatched[0].clickableItem); } + }, - if (linksMatched.length == 0) - this.deactivateMode(); - else if (linksMatched.length == 1) - this.activateLink(linksMatched[0].clickableItem); - } - }; - - /* - * Hides the links that do not match the linkText search string and marks - * them with the 'filtered' DOM property. Renumbers the remainder. Should - * only be called when there is a change in linkTextKeystrokeQueue, to - * avoid undesired renumbering. - */ - linkHints['filterLinkHints'] = function(searchString) { - var linksMatched = []; - var linkSearchString = this.linkTextKeystrokeQueue.join(""); - - for (var i = 0; i < this.hintMarkers.length; i++) { - var linkMarker = this.hintMarkers[i]; - var matchedLink = linkMarker.getAttribute("linkText").toLowerCase().indexOf(linkSearchString.toLowerCase()) >= 0; - - if (!matchedLink) { - linkMarker.style.display = "none"; - linkMarker.setAttribute("filtered", "true"); - } else { - if (linkMarker.style.display == "none") - linkMarker.style.display = ""; - var newHintText = (linksMatched.length+1).toString(); - linkMarker.innerHTML = this.spanWrap(newHintText); - linkMarker.setAttribute("hintString", newHintText); - linkMarker.setAttribute("filtered", "false"); - linksMatched.push(linkMarker); + /* + * Hides the links that do not match the linkText search string and marks + * them with the 'filtered' DOM property. Renumbers the remainder. Should + * only be called when there is a change in linkTextKeystrokeQueue, to + * avoid undesired renumbering. + */ + filterLinkHints: function(searchString) { + var linksMatched = []; + var linkSearchString = this.linkTextKeystrokeQueue.join(""); + + for (var i = 0; i < this.hintMarkers.length; i++) { + var linkMarker = this.hintMarkers[i]; + var matchedLink = linkMarker.getAttribute("linkText").toLowerCase().indexOf(linkSearchString.toLowerCase()) >= 0; + + if (!matchedLink) { + linkMarker.style.display = "none"; + linkMarker.setAttribute("filtered", "true"); + } else { + if (linkMarker.style.display == "none") + linkMarker.style.display = ""; + var newHintText = (linksMatched.length+1).toString(); + linkMarker.innerHTML = this.spanWrap(newHintText); + linkMarker.setAttribute("hintString", newHintText); + linkMarker.setAttribute("filtered", "false"); + linksMatched.push(linkMarker); + } } + return linksMatched; + }, + + deactivateMode: function() { + this.linkTextKeystrokeQueue = []; + this._super('deactivateMode')(); } - return linksMatched; - }; - linkHints['deactivateMode'] = function() { - this.linkTextKeystrokeQueue = []; - // call(this) is necessary to make deactivateMode reset - // the variables in linkHints instead of linkHintsPrototype - Object.getPrototypeOf(this).deactivateMode.call(this); - }; + }); } + + linkHints.init(); } diff --git a/manifest.json b/manifest.json index 161bdb30..181c8739 100644 --- a/manifest.json +++ b/manifest.json @@ -15,7 +15,8 @@ "content_scripts": [ { "matches": [""], - "js": ["lib/keyboardUtils.js", + "js": ["lib/utils.js", + "lib/keyboardUtils.js", "lib/clipboard.js", "linkHints.js", "vimiumFrontend.js" diff --git a/options.html b/options.html index f752abcd..44b1d3e2 100644 --- a/options.html +++ b/options.html @@ -1,6 +1,7 @@ Vimium Options + -- cgit v1.2.3