Improved tooltips for danbooru-based image boards

By SD-DAken Last update Oct 26, 2009 — Installed 196 times. Daily Installs: 1, 0, 1, 2, 2, 3, 1, 0, 2, 2, 0, 1, 1, 0, 1, 0, 0, 0, 4, 0, 0, 2, 0, 1, 0, 0, 0, 0, 2, 1, 0, 1

There are 3 previous versions of this script.

// ==UserScript==
// @author         Andreas Jung (sd-daken.deviantart.com)
// @name           Improved tooltips for danbooru-based image boards
// @namespace      http://www.w3.org/1999/xhtml 
// @description    Improves the info tooltips of thumbnails (makes them look prettier and adds further information).
// @include        *
// ==/UserScript==

// This file is licensed under Creative Commons Attribution License
//
// http://creativecommons.org/licenses/by/3.0/
//
// Initial Developer:
// Andreas Jung (sd-daken.deviantart.com)
//
// Contributor(s):
//

var images = document.getElementsByTagName('img');
var previews = new Array();
var style_added = false;
var preview_timeout;
var previews_found = false;


function getPreviews() {
   //Get the preview images
   for (i = 0; i < images.length; i++) {
      imgclass = images[i].getAttribute('class');
      if (!imgclass) {
         imgclass = '';
      }
      if (imgclass.indexOf('preview') != -1) {
         previews[previews.length] = images[i];
      }
   }
   //Continue only if there are previews...
   if (previews.length > 0) {
      addStyles();
      addEventListeners();
      previews_found = true;
   }
}

function addStyles() {
   if(!style_added) {
      //Add styles to the site header
      head = document.getElementsByTagName('head')[0];
      style = head.appendChild(document.createElement('style'));
      style.setAttribute('type','text/css');
      style.appendChild(document.createTextNode('#tooltip{background-color: rgb(17, 17, 17); -moz-border-radius: 5px; min-height: 50px; min-width: 50px; max-width: 200px; z-index: 100; position: absolute; border: 2px solid white; padding: 5px; opacity: 0.95;}'));
      style.appendChild(document.createTextNode('.tooltipheader{font-weight: bold; line-height: 150%;}'));
      style.appendChild(document.createTextNode('#tooltip > a{font-size: 75%; color: white; font-family: verdana, sans-serif !important;}'));
      style.appendChild(document.createTextNode('#imgurl{display:none;}'));
      style_added = true;
   }
}

function addEventListeners() {
   //Add EventListeners to the previews
   for (i = 0; i < previews.length; i++) {
      previews[i].addEventListener('mouseover', function(){showInfo(this)} ,false);
      previews[i].addEventListener('mousemove', function(e){moveInfo(e)} ,false);
      previews[i].addEventListener('mouseout', function(){removeInfo(this)} ,false);
      previews[i].setAttribute('title','');
   }
}

function showInfo(img) {
   //Extract information from alt-attribute
   imgtitle = img.getAttribute('alt');

   try {
      imgrating = imgtitle.match(/Rating..\w*/i);
      imgrating = imgrating.toString().slice(7);
   }
   catch (e) {
   }

   try {
      imgscore = imgtitle.match(/Score..\w*/i);
      imgscore = imgscore.toString().slice(6);
   }
   catch (e) {
   }

   try {
      imgtags = imgtitle.match(/Tags.*User/i);
      imgtags = imgtags.toString().slice(6,(imgtags.toString().length - 4));
      imgtags = imgtags.replace(/\s/g,"\n");
   }
   catch (e) {
      try {
         imgtags = imgtitle.match(/(.*)rating/i)[1];
      }
      catch (e) {
         if (!imgscore && !imgrating) {
            imgtags = imgtitle.match(/.*/i);
         }
      }
   }

   try {
      imguser = imgtitle.match(/User..\w*/i);
      imguser = imguser.toString().slice(5);
   }
   catch (e) {
   }

   //DEBUG: are all information extracted correctly?
   //alert(imgrating + "\n" + imgtags + "\n" + imgscore + "\n" + imguser);

   body = document.getElementsByTagName('body')[0];

   //if an old tooltip still exists, remove it first
   if (document.getElementById('tooltip')) {
      body.removeChild(document.getElementById('tooltip'));
      window.clearTimeout(timeoutid);
   }

   //add tooltip and information
   tooltip = body.appendChild(document.createElement('div'));
   tooltip.setAttribute('id', 'tooltip');

   ratingheader = tooltip.appendChild(document.createElement('a'));
   ratingheader.appendChild(document.createTextNode('Rating: '));
   ratingheader.setAttribute('class', 'tooltipheader');
   ratingtext = tooltip.appendChild(document.createElement('a'));
   ratingtext.appendChild(document.createTextNode(imgrating));
   tooltip.appendChild(document.createElement('br'));

   scoreheader = tooltip.appendChild(document.createElement('a'));
   scoreheader.appendChild(document.createTextNode('Score: '));
   scoreheader.setAttribute('class', 'tooltipheader');
   scoretext = tooltip.appendChild(document.createElement('a'));
   scoretext.appendChild(document.createTextNode(imgscore));
   tooltip.appendChild(document.createElement('br'));

   tagsheader = tooltip.appendChild(document.createElement('a'));
   tagsheader.appendChild(document.createTextNode('Tags: '));
   tagsheader.setAttribute('class', 'tooltipheader');
   tagstext = tooltip.appendChild(document.createElement('a'));
   tagstext.appendChild(document.createTextNode(imgtags));
   tooltip.appendChild(document.createElement('br'));

   userheader = tooltip.appendChild(document.createElement('a'));
   userheader.appendChild(document.createTextNode('User: '));
   userheader.setAttribute('class', 'tooltipheader');
   usertext = tooltip.appendChild(document.createElement('a'));
   usertext.appendChild(document.createTextNode(imguser));
   tooltip.appendChild(document.createElement('br'));

   //Check if the href attribute begins with a slash, if not add one...
   var href_attr = img.parentNode.getAttribute('href');
   if (href_attr.slice(0, 1) != "/") href_attr = "/" + href_attr;

   //This is needed later to test if the the tooltip displayed when the request for additional
   //details was made is the sames as the one displayed at the moment
   imgurl = "http://" + window.location.hostname + href_attr;

   //we request additional details only if the tooltip is displayed longer than a second
   //to avoid unecessary network traffic
   timeoutid2 = window.setTimeout(function() {showAdditionalDetails("http://" + window.location.hostname + href_attr)}, 1000);

   //remove the tooltip automatically after 60 seconds
   timeoutid = window.setTimeout(function() {removeInfo()}, 60000);
   
}

function showAdditionalDetails(imgpg) {
   //DEBUG: is the correct URL provided?
   //alert(imgpg);

   //request additional details
   req = new XMLHttpRequest();
   req.open('GET', imgpg, true);
   req.onreadystatechange = function (aEvt) {
     if (req.readyState == 4) {
        if (req.status == 200) {
           response = req.responseText;
           stats = response.toString().match(/<li>.*?<\/li>/gim);

           imgid = "";
           imgdate = "";

           //extract additional details
           for (i = 0; i < stats.length; i++) {
              if (stats[i].toString().match(/Id.*/)) {
                 imgid = stats[i].toString();
                 imgid = imgid.slice(8, imgid.length - 5);
              }
              if (stats[i].toString().match(/Posted.*/)) {
                 try {
                    imgdate = stats[i].toString().match(/Posted.*/);
                    imgdate = imgdate.toString().match(/>.*<\/a> by/);
                    imgdate = imgdate.toString().slice(1, imgdate.toString().length - 7);
                 }
                 catch (e) {
                    try {
                       imgdate = stats[i].toString().match(/Posted.*/);
                       imgdate = imgdate.toString().match(/>(.*)<b/)[1];
                    }
                    catch (e) {
                    }
                 }
              }
           }

          if (imgid == "") imgid = imgpg.match(/post\/show\/([^/]*)/)[1];

           //DEBUG: are additional details extracted correctly?
           //alert(imgid + "\n" + imgdate);

           //Add details if the requesting tooltip is still displayed
           if (document.getElementById('tooltip') && (imgurl == imgpg)) {
              tooltip = document.getElementById('tooltip');

              idheader = tooltip.appendChild(document.createElement('a'));
              idheader.appendChild(document.createTextNode('Id: '));
              idheader.setAttribute('class', 'tooltipheader');
              idtext = tooltip.appendChild(document.createElement('a'));
              idtext.appendChild(document.createTextNode(imgid));
              tooltip.appendChild(document.createElement('br'));

              dateheader = tooltip.appendChild(document.createElement('a'));
              dateheader.appendChild(document.createTextNode('Added: '));
              dateheader.setAttribute('class', 'tooltipheader');
              datetext = tooltip.appendChild(document.createElement('a'));
              datetext.appendChild(document.createTextNode(imgdate));
              tooltip.appendChild(document.createElement('br'));
           }
           else {
              // DEBUG: requesting tooltip and displayed tooltip aren't the same.
              // This happens if the User stays long enough over the preview to start the request
              // but not long enough to have the information displayed. Nothing unusual.
              // The information might be intersting if the code fails unexpected.
              // alert(imgurl + "\n" + imgpg);
           }
        }
     }
   };
   req.send(null);
}

function moveInfo(coords) {
   X = coords.pageX;
   Y = coords.pageY;
   if (document.getElementById('tooltip')) {
      document.getElementById('tooltip').setAttribute('style', 'left:' + (X + 20) + 'px; top:' + (Y - 50) + 'px');
   }
}

function removeInfo(img) {
      if (document.getElementById('tooltip')) {
      body.removeChild(document.getElementById('tooltip'));
      window.clearTimeout(timeoutid);
      window.clearTimeout(timeoutid2);
   }
}

window.addEventListener("load", function() {
   getPreviews();
   if(previews_found) {
      window.addEventListener("DOMNodeInserted", function() {
         //This is needed if the content is loaded dynamically (e.g. by AutoPagerize)
         //The timeout is needed because else we will call getPreviews() way too often,
         //while the content is added (this would freeze the browser!).
         window.clearTimeout(preview_timeout);
         preview_timeout = window.setTimeout(getPreviews, 1500);
      }, false);
   }
}, false);