docs: function handleVote(link) {
      if (!opts.voting) {
        showError("You'll need to login to vote.");
        return;
      }
  
      var id = link.attr('id');
      if (!id) {
        // Didn't click on one of the voting arrows.
        return;
      }
      // If it is an unvote, the new vote value is 0,
      // Otherwise it's 1 for an upvote, or -1 for a downvote.
      var value = 0;
      if (id.charAt(1) != 'u') {
        value = id.charAt(0) == 'u' ? 1 : -1;
      }
      // The data to be sent to the server.
      var d = {
        comment_id: id.substring(2),
        value: value
      };
  
      // Swap the vote and unvote links.
      link.hide();
      $('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
        .show();
  
      // The div the comment is displayed in.
      var div = $('div#cd' + d.comment_id);
      var data = div.data('comment');
  
      // If this is not an unvote, and the other vote arrow has
      // already been pressed, unpress it.
      if ((d.value !== 0) && (data.vote === d.value * -1)) {
        $('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
        $('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
      }
  
      // Update the comments rating in the local data.
      data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
      data.vote = d.value;
      div.data('comment', data);
  
      // Change the rating text.
      div.find('.rating:first')
        .text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
  
      // Send the vote information to the server.
      $.ajax({
        type: "POST",
        url: opts.processVoteURL,
        data: d,
        error: function(request, textStatus, error) {
          showError('Oops, there was a problem casting that vote.');
        }
      });
    }