diff options
| author | jez | 2011-01-03 04:29:12 +0800 | 
|---|---|---|
| committer | jez | 2011-01-03 04:31:30 +0800 | 
| commit | baf3dd02d5891df9a35af7d2f8f2eaa42ac6bb03 (patch) | |
| tree | 82e23247feff463e24033529ad56217c47846147 | |
| parent | ca2996d06c883a1f8449e8c77b7168345dbdb529 (diff) | |
| download | vimium-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.js | 32 | ||||
| -rw-r--r-- | linkHints.js | 289 | ||||
| -rw-r--r-- | manifest.json | 3 | ||||
| -rw-r--r-- | options.html | 1 | 
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> | 
