del.icio.us Anal Retentive Character Counter

By S Waters Last update Mar 1, 2007 — Installed 328 times.
// ==UserScript==
// @name           del.icio.us Anal Retentive Character Counter
// @description    As-you-type counters for description and notes fields of the posting page and a check for characters that, once encoded, take up extra space and reduce the max character limit.
// @include        http://del.icio.us/*url=*
// ==/UserScript==
   
// This script is for people who like to cram their del.icio.us notes right up to the character limit and rip out 
// their hair in rage and frustration whenever their precious words get chopped off during save. It adds to the 
// posting page straight-up, regular character counters for both the description and notes fields, but it also 
// provides some more sophisticated functionality.

// del.icio.us allows 255 characters in the description and notes fields, but some characters, once encoded, take up 
// more space than regular characters. So even though you may have typed exactly 255 characters in the notes field, 
// when you save your post some chars will get cut off if one or more of those characters requires extra space. By 
// clicking the "check for large chars" link that this script adds to the posting page, you can see which, if any, of 
// your characters are larger than the regular characters, and then go in and remove/replace them if you want. When 
// you click save, the script does a final check to see if your description or notes field is too large and if so, it 
// offers you a chance to cancel the save and edit the fields.

// Also, by default it automatically replaces curly quotes (Unicode code points 8216, 8217, 8220, 8221, which are 
// equivalent to 3 chars when encoded) with straight quotes. (Because I think curly quotes are a waste of valuable 
// space.) You can turn off this option by unchecking the "replace curly quotes" checkbox. Note that the quote 
// replacement, when enabled, takes place anytime you click the "check for large chars" link or the "save" button.

(function() {

  var descId = "description";
  var descLength = 255;
  var notesId = "notes";
  var notesLength = 255;

  // Set up the class that does all the work

  function CharChecker(elementId, elementLimit) {
    this.checkElement = document.getElementById(elementId);
    this.counterElement = document.createElement('span');
    this.resultsElement = document.createElement('div');
    this.limit = elementLimit;
    this.encodedCount = 0;
    this.quotesReplaced = false;
    this.styledText = "";
    this.largeChars = false;
    
    this.counterElement.id = elementId + 'counter';
    this.counterElement.className = 'smaller';
    // insert counter 
    if (elementId == descId) {
      this.checkElement.parentNode.nextSibling.insertBefore(this.counterElement, this.checkElement.parentNode.nextSibling.firstChild.nextSibling);
      this.checkElement.parentNode.nextSibling.style.verticalAlign = 'top';
    }
    else if (elementId == notesId) {
      this.checkElement.parentNode.nextSibling.nextSibling.appendChild(this.counterElement);
      this.checkElement.parentNode.nextSibling.nextSibling.style.verticalAlign = 'top';
    }

    this.resultsElement.id = elementId + 'results';
    this.resultsElement.className = 'verysmall';   
    this.resultsElement.style.backgroundColor = '#dcdcdc';
    this.resultsElement.style.paddingLeft = '2px';
    this.resultsElement.style.paddingRight = '2px';
    this.resultsElement.style.lineHeight = '1.8em';
    // insert results
    if (elementId == descId) {
      this.checkElement.parentNode.insertBefore(this.resultsElement, this.checkElement.parentNode.firstChild.nextSibling);
    }
    else if (elementId == notesId) {        
      this.checkElement.parentNode.insertBefore(this.resultsElement, this.checkElement.parentNode.firstChild.nextSibling.nextSibling);
    }

    // setup counter to respond to keyups
    this.checkElement.addEventListener("keyup", updateLength, true);
  }

  CharChecker.prototype.updateCounter = function() {
    var counter = this.counterElement;
    var element = this.checkElement;  
    counter.innerHTML = element.value.length + '/' + this.limit;
    if (element.value.length > this.limit) {
      counter.style.fontWeight = "bold";
    }
    else {
      counter.style.fontWeight = "normal";
    }
  }

  CharChecker.prototype.doCheck = function() {
    this.encodedCount = 0;
    this.quotesReplaced = false;
    this.styledText = "";
    this.largeChars = false;

    var useStraight = GM_getValue("useStraightQuotes", true);
    var element = this.checkElement;

    for (i = 0; i < element.value.length; i++) {
      // Swap out curly quotes for straight quotes
      if (useStraight) {
        if ((element.value.charCodeAt(i) == 8216) || (element.value.charCodeAt(i) == 8217)) {
          element.value = element.value.substring(0, i) + '\'' + element.value.substring(i+1);
          this.quotesReplaced = true;
        }
        else if ((element.value.charCodeAt(i) == 8220) || (element.value.charCodeAt(i) == 8221)) {
          element.value = element.value.substring(0, i) + '"' + element.value.substring(i+1);
          this.quotesReplaced = true;
        }
      }

      // Once encoded, some chars take up extra space
      if (Math.ceil(encodeURIComponent(element.value.charAt(i)).length / 3) > 1) {
        this.largeChars = true;
        this.styledText = this.styledText + '<span style="border: 1px solid red; margin-left: 1px; margin-right: 1px; padding-left: 3px; padding-right: 3px;">'  + element.value.charAt(i) + '</span>';
      }
      else {
        this.styledText = this.styledText + element.value.charAt(i);
      }
      this.encodedCount = this.encodedCount + Math.ceil(encodeURIComponent(element.value.charAt(i)).length / 3);
    }
  }

  CharChecker.prototype.showLargeChars = function() {
    var checked = this.checkElement;
    var results = this.resultsElement;

    results.innerHTML = '';
    if (this.largeChars) {
      if (this.quotesReplaced) {
        results.innerHTML = '<p>Curly quotes replaced with straight quotes.</p>';
      }
      if (this.overLimit() > 0) {
        results.innerHTML = results.innerHTML + '<p style="font-weight: bold;">' + this.encodedCount + '/' + this.limit + ' encoded characters (' + this.overLimit() + ' too many)</p>';
      }
      else {
        results.innerHTML = results.innerHTML + '<p>' + this.encodedCount + '/' + this.limit + ' encoded characters</p>';
      }
      results.innerHTML = results.innerHTML + '<p>' + this.styledText + '</p>';
    }
    else {
      var other = "";
      if (this.quotesReplaced) {
        results.innerHTML = '<p>Curly quotes replaced with straight quotes. No other large characters detected.</p>';
      }
      else {
        results.innerHTML = '<p>No large characters detected.</p>';
      }
    }

    // recalculate the suggest drop down position
    var suggestpopup = document.getElementById('suggest');
    var tagsfield = document.getElementById('tags');
    suggestpopup.style.top = getYPos(tagsfield) + tagsfield.offsetHeight - 1 + 'px';
  }

  CharChecker.prototype.overLimit = function() {
    return this.encodedCount - this.limit;  
  }


  // Start actually doing stuff 

  var descChecker = new CharChecker(descId, descLength);
  var notesChecker = new CharChecker(notesId, notesLength);
  descChecker.updateCounter();
  notesChecker.updateCounter();

  // Align all label table cells to the top
  GM_addStyle("td.rs {vertical-align: top;}");

  // Add a link that triggers a check for large characters, and a
  // checkbox that specifies whether the check automatically converts
  // curly quotes to straight quotes.
  var checkCharsLink = createCheckCharsLink();
  var replaceQuotesBox = createReplaceQuotesBox();
  var quotesText = document.createElement("span");
  quotesText.innerHTML = "replace curly quotes";
  quotesText.className = 'verysmall';

  var delForm = document.getElementById("delForm");
  var pLink = document.createElement("p");
  pLink.style.marginTop = "10px";
  pLink.appendChild(checkCharsLink);
  pLink.appendChild(replaceQuotesBox);
  pLink.appendChild(quotesText);
  delForm.parentNode.insertBefore(pLink, delForm.nextSibling);

  function createCheckCharsLink() {
    var checkLink = document.createElement("a");
    checkLink.href= "";
    checkLink.innerHTML = "check for large chars";
    checkLink.id = "checkcharslink";

    checkLink.addEventListener("click", function(event) {
      event.stopPropagation();    
      event.preventDefault();
      descChecker.doCheck();
      notesChecker.doCheck();
      descChecker.showLargeChars();
      notesChecker.showLargeChars();
    }, false);

    return checkLink; 
  }

  function createReplaceQuotesBox() {
    var quotesCheckbox = document.createElement("input");
    quotesCheckbox.type= "checkbox";
    quotesCheckbox.id = "replacequotesbox";   
    quotesCheckbox.style.marginLeft = "5px";  
    quotesCheckbox.checked = GM_getValue('useStraightQuotes', true);

    quotesCheckbox.addEventListener("click", function(event) {
      GM_setValue('useStraightQuotes', this.checked);     
    }, false);

    return quotesCheckbox; 
  }

  // Make the save button check to see if chars will get cut off before saving
  var inputElements = delForm.getElementsByTagName("input");
  var submitButton;
  for (var i = 0; i < inputElements.length; i++) {
    if (inputElements[i].type == "submit") {
      submitButton = inputElements[i];
      submitButton.addEventListener("click", function(event) {
        if (countBeforeSave() == false) {
          event.preventDefault();
          descChecker.showLargeChars();  
          notesChecker.showLargeChars(); 
        }
      }, false);
    }
  }

  function countBeforeSave() {
    descChecker.doCheck();
    notesChecker.doCheck();

    var error = "";
    var and = "";
    if (descChecker.overLimit() > 0) {
       error = "description";
       and = " and ";
    }
    if (notesChecker.overLimit() > 0) {
      error = error + and + "notes";
    }
    if (error != "") {
      error = "Your " + error + " will be cut off. Proceed anyway?";
      return confirm(error);
    }
    return true;
  }

  // update a counter (triggered by a keyup)
  function updateLength() {
    if (this.id == descId) {
      descChecker.updateCounter();
    }
    else if (this.id == notesId) {
      notesChecker.updateCounter();
    }
  }

  // get vertical pixel position of an object
  function getYPos(o) {
    var y = 0;
    if (o.offsetParent) {
      while (o.offsetParent) {
        y += o.offsetTop;
        o = o.offsetParent;
      }
    }
    return y;
  }

})();