aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjez2011-01-03 04:29:12 +0800
committerjez2011-01-03 04:31:30 +0800
commitbaf3dd02d5891df9a35af7d2f8f2eaa42ac6bb03 (patch)
tree82e23247feff463e24033529ad56217c47846147
parentca2996d06c883a1f8449e8c77b7168345dbdb529 (diff)
downloadvimium-baf3dd02d5891df9a35af7d2f8f2eaa42ac6bb03.tar.bz2
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.
-rw-r--r--lib/utils.js32
-rw-r--r--linkHints.js289
-rw-r--r--manifest.json3
-rw-r--r--options.html1
4 files changed, 183 insertions, 142 deletions
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": ["<all_urls>"],
- "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 @@
<html>
<head>
<title>Vimium Options</title>
+ <script src="lib/utils.js"></script>
<script src="lib/keyboardUtils.js"></script>
<script src="linkHints.js"></script>
<script src="lib/clipboard.js"></script>