// ==UserScript== // @name Delicious Link Tooltop // @namespace GPM // @description Shows Delicious info for the target page in a tooltip when you hover over a link. // @downstreamURL http://userscripts.org/scripts/source/60831.user.js // @version 1.0.0 // @include * // @exclude http://facebook.com/* // @exclude https://facebook.com/* // @exclude http://*.facebook.com/* // @exclude https://*.facebook.com/* // @exclude http://images.google.*/* // @exclude http://*.google.*/images?* // @grant none // @downloadURL https://update.greasyfork.icu/scripts/7659/Delicious%20Link%20Tooltop.user.js // @updateURL https://update.greasyfork.icu/scripts/7659/Delicious%20Link%20Tooltop.meta.js // ==/UserScript== // Some older browsers may need JSON, but this link is broken: // @require http://json.org/json2.js // == Config == var showTooltips = true; // Make little info boxes pop up when you hover over a link. var lookupCurrentPage = true; // Display count for this page shown in top-right corner. var annotateAllLinks = false; // Lookup every link on the page (almost), and display its count in a blue box after the link. // Delicious may occasionally block this for spamming (temporarily). var enableJSONPonHTTPS = false; // JSONP only works on https pages in Chrome if the user confirms each page. This is a hassle so it is disabled by default. var maxCacheSize = 2400; // Global cache limit for Greasemonkey/GM_setValue. var maxCacheSizeForLocalStorage = 1000; // Per-site cache limit for Chrome/localStorage. var showProgress = true; // Highlights each link in yellow while we are making a delicious request on it. Messy but informative if lookup fails! var logRequests = false; var logResponses = true; var displayCleanupInTitle = true; // To debug if (display when) the cleanup is lagging your browser. /* == Changelog == 2012-02-20 Prevented interfering with top bar of Google. 2012-02-05 Added enableJSONPonHTTPS. If you turn it on in Chrome, you will need to answer the browser's "Load Insecure Content?" popup. (Do this only if you trust Delicious.com!) Visit github to test. Does not affect Firefox. Greasemonkey's GM_xmlhttpRequest still works fine on https pages. 2012-01-31 Noticed that lookups are not working on https pages. Surprised I hadn't seen this before. 2012-01-30 Uploaded new version to userscripts.org. 2011-11-?? I think I fixed lookup bugs by generating the md5sum here (big lib). Earlier versions without the md5sum may still be usable! */ // TODO: Move this to its own script!! if (this.GM_addStyle) { GM_addStyle("a:visited { color: #440066; }"); } var secondRun = ( window.DLT_loaded ? true : (window.DLT_loaded=true) && false ); // I want to switch annotateAllLinks on if I run the userscript twice. if (secondRun) { annotateAllLinks = true; lookupCurrentPage = true; } //// NOTES for Delicious Network Add: // GET http://delicious.com/settings/networkadd?networkadd=EHKNIGHT&.crumb=7FsAGAcj8szIc7Pt_37ua1qsjMM- // GET http://delicious.com/network?add=EHKNIGHT // GET http://delicious.com/EHKNIGHT?networkaction=1 //// When user hits OK: // POST http://delicious.com/settings/networkadd?.crumb=7FsAGAcj8szIc7Pt_37ua1qsjMM-&jump=%2FEHKNIGHT%3Fnetworkaction%3D1&network-action-ok=&networkadd=EHKNIGHT // redirects to: http://delicious.com/EHKNIGHT?networkaddconfirm=EHKNIGHT // TODO: Don't annotate images, duplicates of last, or anything on delicious.com //// Some old stats: // Cleanup of cache size 2048 took 27 seconds in Firefox 4. // Cleanup of cache size 1024 took 6.7 seconds in Firefox 4. // In Chrome 512 means my cache cleanup takes 2-5 seconds. // log = (unsafeWindow.console && unsafeWindow.console.log) ? unsafeWindow.console.log : GM_log; // log = GM_log; function log(x,y,z) { if (this.GM_log) { GM_log(x,y,z); } else if (this.console && console.log) { console.log(x,y,z); } window.status = ""+x+" "+y+" "+z; // window.status = Array.prototype.join(arguments," "); // Didn't work for me (GM in FF) } /* // == Silly scrolling logger == // var scrollText = ""; function showScroller() { window.status = scrollText.substring(0,240).replace(/\s/,' ','g'); } function animateScroller() { showScroller(); if (scrollText.length > 0) setTimeout(animateScroller,200); scrollText = scrollText.substring(10); } var oldLog = log; log = function(x) { oldLog(x); if (scrollText == "") // if not, there is probably already a timeout running setTimeout(animateScroller,2000); scrollText = scrollText + " " + x; showScroller(); }; */ // == Notes == // // DONE: Rename script "Get Link Meta" or "Get Delicious Info for Links" or // "Delicious Link Tooltip" with an accidental typo then upload it. // Some code adapted from Hover Links userscript. // Thanks to: http://delicious.com/developers // The delicious JSON url we use is: // http://feeds.delicious.com/v2/json/urlinfo?url=http://www.google.co.uk/ // DONE: Since bookmarklets can't make CD-XHR, we could use a JSONP proxy like: // http://hwi.ath.cx/json_plus_p?url=http://feeds.delicious.com/... ? // DONE: onclick should cancel the lookup (user has followed link, no need to query) // DONE: If the XHR request calls callback *after* we have done mouseout, link still // appears. Related problem if two requests are being made and neither has run // callback yet. Recommend setting/unsetting/checking var targettingLink = // null or elem; Or maybe we can use stillFocused. // DONE: We prevent a second XHR being made if one is already running for a // given link, by saving the "working..." string in the dataCache. // DONE: Give all tags an automatic color by hash, but not too vivid. May help // us to track common tags. // TODO: Many Mediawiki sites are configured to show a tooltip for external // links, and this can conflict with our tooltip! // We could try to stop this in all cases, by doing evt.preventDefault() on all // mouseovers/outs, but that is a bit strong. // At least a fix for the Mediawiki problem would make us (me) happy. // Tried 100x larger zIndex but no success. // Maybe it's the browser's display of the alt or title property. // DONE: Activate for links added to the document later. Should add a // DOMNodeInserted event listener. Or maybe neater, add listeners to the top // of the doc, but have them only activate on A elements. // I made some of the info in the tooltip into links, but to access them we // have to allow mouseover the tooltip without it disappearing. I tried to // implement that but things haven't been 100% smooth since... // DONE: Sometimes if we move quickly over to the link, no tooltip will appear. // DONE: The tooltip should appear wherever we last mousemoved-ed over the // link, (current cursor pos) rather than where we mouseover-ed (entered it). // HMMM it seems to me that this is happening when we mouseover the part // of a Google result but works fine on the non-EM parts. So maybe we should // be using capture? // DONE: The right-floating popBar/Cont is working for Chrome and Konq, but not FF! // Forget CSS float, I used a TABLE and all works fine. :) // TODO: Tidy up the aftermath. // Unfortunately the table appears to override our popWidth, but maybe we don't // need/want that anymore anyway. // TODO: Automatic annotation's decision about which links to scan could be improved. // e.g. some search ? links could be checked if they are short. Some same-host links // we may also want to checked. But some URLs we definitely won't want to, e.g. if // they appear to contain a (large) session ID. // TODO BUG: The addition of the blue number "flag" by annotateAllLinks will // often cause parts of the page to grow, breaking the intended CSS layout. We // should seek to mitigate this. // // Proposal: If flag is being added in the middle of some text, then expand the // text by adding it inline. But if flag is being added at the end (last // child) of a layout element, then instead float it above the element to avoid // changing the size of the container. This will require determination of the // real offset of the element. Note the link may be inside a which we must // step outside to determine whether it is at the end of a container or not. // == Config == // var max_width = 300; var getWebsiteInfoOnly = false; // Shows info for target website, not target page. var bg_color = "#EAEAEA"; var warn_color = "#FF4444"; var border_color = "#AAAAAA"; var font_color = "#000000"; var font_face = ""; // "tahoma"; var font_size = "11px"; // == Library functions == // function hsv2rgbString(h,s,v) { // hsv input values range 0 - 1, rgb output values range 0 - 255 // Adapted from http://www.easyrgb.com/math.html var red, green, blue; if(s == 0) { red = green = blue = Math.round(v*255); } else { // h should be < 1 var var_h = h * 6; if (var_h == 6) var_h = 0; // TODO: get offset if h<0 or h>1 var var_i = Math.floor( var_h ); var var_1 = v*(1-s); var var_2 = v*(1-s*(var_h-var_i)); var var_3 = v*(1-s*(1-(var_h-var_i))); if (var_i==0) { red = v; green = var_3; blue = var_1; } else if(var_i==1) { red = var_2; green = v; blue = var_1; } else if(var_i==2) { red = var_1; green = v; blue = var_3 } else if(var_i==3) { red = var_1; green = var_2; blue = v; } else if (var_i==4) { red = var_3; green = var_1; blue = v; } else { red = v; green = var_1; blue = var_2 } red = Math.round(red * 255); green = Math.round(green * 255); blue = Math.round(blue * 255); } return "rgb("+red+","+green+","+blue+")"; } function getHash(str) { var sum = 0; for (var i=0;i=0 if (typeof GM_setValue === 'undefined' || window.navigator.vendor.match(/Google/)) { /* var storage = this.globalStorage || this.localStorage || this.sessionStorage; var name = "some unlabeled"; */ // Removed "globalStorage" storage because in FF4 it offers it but errors when we try to use it. // Chrome doesn't offer it ATM. var storageTypes = ["localStorage","sessionStorage"]; var storage,name; for (var i=0;i= 0 ? names[i] : "unknown"); */ GM_log("[DLT] Implementing GM_get/setValue using "+name+" storage."); GM_setValue = function(name, value) { value = (typeof value)[0] + value; storage.setItem(name, value); }; GM_getValue = function(name, defaultValue) { var value = storage.getItem(name); // GM_log("[DLT] GM_get("+name+")"); // GM_log("[DLT] gave: "+value); if (!value) return defaultValue; var type = value[0]; value = value.substring(1); switch (type) { case 'b': return value == 'true'; case 'n': return Number(value); default: return value; } }; GM_deleteValue = function(name) { storage.removeItem(name); }; GM_listValues = function() { var list = []; for (var i=0;i 512) { var i = parseInt(Math.random() * cacheList.length); // log("Deleting "+cacheList[i]+" length is currently "+cacheList.length); GM_deleteValue(cacheList[i]); // delete cacheList[i]; cacheList[i] = cacheList[cacheList.length-1]; cacheList.length--; } */ function getScoreFor(crURL) { var url = crURL.replace(/^CachedResponse/,''); if (dataCache[url] == null) { dataCache[url] = JSON_parse(GM_getValue(crURL,"null")); } if (dataCache[url] == null) { log("Warning! Empty cache entry for "+url); dataCache[url] = {}; } // All cached urls have a minimum score of 2. // log("Generating score form cacheCount="+dataCache[url].cacheCount+" and age="+age(dataCache[url])); var thisScore = (dataCache[url].cacheCount+2) / age(dataCache[url]); if (isNaN(thisScore)) // e.g. if dataCache[url] == null thisScore = 0.00001; return thisScore; } // Inefficient method: find the least valuable entry, remove it, then // repeat. /* // Delete the oldest and least-used, keep new/popular entries function cleanupOne() { if (cacheList.length > maxCacheSize) { var poorestScore = 99999; var poorestScorer = null; for (var i=0;i max_width) { tooltipDiv.style.width = max_width + "px"; } var scrollbarWidth = 20; // If the cursor is so low in the window that the tooltip would appear // off the bottom, then we move it to above the cursor if (parseInt(tooltipDiv.style.top) + tooltipDiv.clientHeight + scrollbarWidth > window.innerHeight + window.pageYOffset) { tooltipDiv.style.top = (posy - 7 - tooltipDiv.clientHeight) + "px"; } // Similar for the right-hand-side, but beware the browser might have // reduced clientWidth in an attempt to make everything fit, so instead // we use divWidth. var divWidth = ( tooltipDiv.clientWidth > max_width ? tooltipDiv.clientWidth : max_width ) + 20; if (parseInt(tooltipDiv.style.left) + divWidth + scrollbarWidth > window.innerWidth + window.pageXOffset) { // tooltipDiv.style.left = (posx - 15 - divWidth) + "px"; tooltipDiv.style.left = ''; tooltipDiv.style.right = (window.innerWidth - posx + 5) + "px"; // tooltipDiv.style.width = divWidth; if (tooltipDiv.clientWidth > max_width) { tooltipDiv.style.width = max_width + "px"; } // TODO: Can move the tooltip too far left, if it is naturally narrow. } // The +scrollbarWidth deals with the case when there is a scrollbar // which we don't want to be hidden behind! // TODO: To prevent the flashing of scrollbar (visible in Konq, invisible // but slow in FF), we should use desiredWidth/Height to check the // situation before setting bad left/top in the first place. // This does still occasionally occur, but seems to go away after the // first move-to-left. } } function showResultsTooltip(resultObj,subjectUrl,evt) { // if (stillFocused == link) { // log("Displaying results for "+subjectUrl+": "+uneval(resultObj)); hideTooltip(); tooltipDiv = document.createElement("div"); tooltipDiv.id = "DLTtooltip"; // tooltipDiv.setAttribute("style", "background:" + bg_color + ";border:1px solid " + border_color + ";padding:2px;color:" + font_color + ";font-family:" + font_face + ";font-size:" + font_size + ";position:absolute;z-index:100000;") tooltipDiv.style.backgroundColor = bg_color; tooltipDiv.style.border = "1px solid "+border_color; tooltipDiv.style.padding = '6px'; tooltipDiv.style.color = font_color; tooltipDiv.style.fontFamily = font_face; tooltipDiv.style.fontSize = font_size; tooltipDiv.style.position = "absolute"; tooltipDiv.style.zIndex = 100000; tooltipDiv.style.textAlign = 'left'; tooltipDiv.style.lineHeight = ''; tooltipDiv.addEventListener('mouseover',function(evt){ rolledOverTooltip = true; },false); tooltipDiv.addEventListener('mouseout',function(evt){ hideTooltipMomentarily(); },false); // Sometimes Delicious returns a result with nothing but the hash and // total_posts=1. These days I am getting this more often than // resultObj==null, but it is about as informative, so: if (resultObj && resultObj.total_posts==1 /*&& resultObj.hash*/ && resultObj.url=="" && resultObj.title=="" && resultObj.top_tags.length==0) { GM_log("[DLT] Got boring response: "+JSON.stringify(resultObj)); resultObj = null; } // However I think this may be due to the fact that there are *private* // bookmarks for the URL. If so, Delicious is a little bit naughty in // admitting that they even exist in its database! // TODO: Test this theory! if (resultObj && resultObj.total_posts) { if (unescapeHTML(resultObj.url) != subjectUrl) { var warningSpan = document.createElement("SPAN"); if (unescapeHTML(resultObj.url) == getHostnameOfUrl(subjectUrl)) { warningSpan.appendChild(document.createTextNode("Results for website")); } else { // Sometimes Delicious returns a different URL from the one requested. // So far this has always seemed fine to me, either adding or // removing index.html, or changing a few irrelevant CGI params. // TODO/CONSIDER: So maybe the warning colors/message are not needed? // Hmm no there is an example where it's not fine. The "Shopping" // link along the top of Google's search results page. It's // actually just a bunch of CGI parameters. But Delicious drops // all the parameters and gives me the results for the top domain // page (http://www.google.com/). This is not the link I wanted // info for, so the warnings are needed! warningSpan.appendChild(document.createTextNode("Results for: "+resultObj.url)); } warningSpan.style.backgroundColor = warn_color; // TODO: Oddly this does not get applied in Konqueror! warningSpan.style.color = 'white'; warningSpan.style.padding = '3px 6px'; warningSpan.style.fontWeight = 'bold'; tooltipDiv.appendChild(warningSpan); tooltipDiv.appendChild(document.createElement('BR')); } var titleCont = document.createElement("TD"); titleCont.style.backgroundColor = bg_color; // titleCont.appendChild(document.createTextNode("Title: ")); // titleCont.appendChild(boldTextElement(resultObj.title)); // titleCont.appendChild(document.createElement('BR')); //// The title can contain HTML-encoded chars, so we must decode/present accordingly // titleCont.innerHTML += "" + resultObj.title + "
"; var titleElem = boldTextElement(unescapeHTML(resultObj.title)); // Make it a link? var link = document.createElement("A"); link.href = 'http://delicious.com/url/view?url='+encodeURIComponent(resultObj.url)+'&show=notes_only'; link.target = "_blank"; link.style.paddingRight = '8px'; link.appendChild(titleElem); titleElem = link; titleCont.appendChild(titleElem); //// For some reason Firefox refuses to notice the addition of this //// style, so we do it with a CSS class instead. // titleCont.className = 'dlttLeft'; // titleCont.appendChild(document.createTextNode("Popularity: ")); // titleCont.appendChild(boldTextElement(""+resultObj.total_posts)); // var popWidth = Math.log(3 + parseInt(resultObj.total_posts))*30; var popWidth = Math.log(Number(resultObj.total_posts)/40)*max_width/8; if (!popWidth || popWidth<=10) popWidth = 10; if (popWidth>max_width) popWidth = max_width; var popBar = document.createElement('A'); popBar.href = link.href; popBar.style.backgroundColor = getColorForCount(resultObj.total_posts); popBar.style.color = 'white'; popBar.style.width = popWidth+'px'; popBar.style.margin = '2px 0px 2px 0px'; popBar.style.padding = '3px 8px 2px 8px'; popBar.style.textAlign = 'right'; popBar.style.textDecoration = 'none'; // do not underline popularity (although it is a link) // popBar.appendChild(document.createTextNode(" ")); // popBar.style.float = 'right'; //// Ahhh apparently .float was renamed .cssFloat or .styleFloat for IE popBar.appendChild(boldTextElement(addCommasToNumber(resultObj.total_posts))); var popBarCont = document.createElement("TD"); popBarCont.appendChild(popBar); // popBarCont.style.float = 'right'; // popBarCont.className = 'dlttRight'; // popBarCont.style.position = 'fixed'; // popBarCont.style.right = '0px'; popBarCont.style.textAlign = 'right'; // popBarCont.align = 'right'; popBarCont.align = 'right'; // titleCont.appendChild(popBarCont); var topTable = document.createElement("TABLE"); topTable.width = '100%'; var topRow = document.createElement("TR"); topTable.appendChild(topRow); topRow.appendChild(titleCont); topRow.appendChild(popBarCont); tooltipDiv.appendChild(topTable); topTable.style.backgroundColor = bg_color; /* titleCont.style.float = 'left'; popBarCont.style.float = 'right'; tooltipDiv.appendChild(titleCont); tooltipDiv.appendChild(popBarCont); tooltipDiv.style.overflow = 'auto'; // Fix floating problems? */ /* top_tags is a hashtable, it has no .length */ if (resultObj.top_tags /* && resultObj.top_tags.length>0 */ ) { // tooltipDiv.appendChild(document.createElement("BR")); var tagsCont = document.createElement("P"); tagsCont.style.marginTop = '4px'; tagsCont.style.marginBottom = '1px'; // tagsCont.style.float = 'right'; var tagsDiv = document.createElement("DIV"); tagsDiv.style.textAlign = 'right'; /* //// Simple list var tags = ""; for (var tag in resultObj.top_tags) { tags += (tags==""?"":", ") + tag; } tagsDiv.appendChild(document.createTextNode("Tags: "+tags+"")); */ //// List with colored tags var first = true; for (var tag in resultObj.top_tags) { if (!first) tagsDiv.appendChild(document.createTextNode(", ")); first = false; var tagSpan = document.createElement("SPAN"); tagSpan.appendChild(document.createTextNode(tag)); tagSpan.style.color = hsv2rgbString( (getHash(tag)%256)/256, 1.0, 0.5 ); // Make it a link? var link = document.createElement("A"); link.href = "http://delicious.com/tag/"+tag; link.target = "_blank"; link.title = resultObj.top_tags[tag]; link.style.textDecoration = 'none'; // do not underline tags link.appendChild(tagSpan); tagSpan = link; tagsCont.appendChild(tagsDiv); tagsDiv.appendChild(tagSpan); } tooltipDiv.appendChild(tagsCont); } } else { tooltipDiv.appendChild(document.createTextNode("No delicious tags for "+subjectUrl)); } document.body.appendChild(tooltipDiv); positionTooltip(evt); // log("tooltipDiv.innerHTML = "+tooltipDiv.innerHTML); } // Old: These functions make use of the scope vars link and subjectUrl. // Note: These functions make use of the scope var dataCache. function createTooltip(evt) { hideTooltip(); var link = evt.target; lastMoveEvent = evt; stillFocused = link; // Links we do not want to act on: if (link.href.match(/^javascript:/)) return; if (document.location.hostname == link.hostname && document.location.pathname == link.pathname) return; // Either a link to self, or a # link to an anchor in self var subjectUrl = getCanonicalUrl(link.href); // Remove any #anchor from the url subjectUrl = ""+subjectUrl.match(/^[^#]*/); if (dataCache[subjectUrl] == "working...") { return; // We can't do anything useful here. We must wait for the XHR to respond. } var waitTime = ( dataCache[subjectUrl] != null ? 300 : 1000 ); timer = setTimeout(function(){ if (stillFocused==link) { initiateDoubleLookup(subjectUrl,function(foundResults,foundUrl) { showResultsTooltip(foundResults,subjectUrl,lastMoveEvent || evt); }); } },waitTime); } function hideTooltipMomentarily() { rolledOverTooltip = false; stillFocused = null; setTimeout(function(){ if (stillFocused == null && !rolledOverTooltip) hideTooltip(); },200); } function hideTooltip() { stillFocused = null; if (timer) { clearTimeout(timer); timer = null; } // TODO: iff we are mousingoff the link, we should delay before closing, to see if the user is mousing onto the tooltip, and if so not hide. if (tooltipDiv) { if (tooltipDiv.parentNode) { tooltipDiv.parentNode.removeChild(tooltipDiv); } tooltipDiv = null; } rolledOverTooltip = false; } function initialiseTooltipListeners() { // GM_addStyle(".dlttLeft { float: left; }"); // GM_addStyle(".dlttRight { float: right; }"); /* for (var i=0; i links! if (node.tagName == "A" && node.href) return node; node = node.parentNode; } return node; }; addGlobalConditionalEventListener("mouseover",createTooltip,linksOnly); addGlobalConditionalEventListener("mouseout",hideTooltipMomentarily,linksOnly); // TODO: We should only really enable the mousemove/out/down events when we have done a mouseover! // addGlobalConditionalEventListener("mousemove",positionTooltip,linksOnly); addGlobalConditionalEventListener("mousemove",function(evt){if (evt.target == stillFocused) { lastMoveEvent=evt; } },linksOnly); // If user clicks either button on the link, then we hide it addGlobalConditionalEventListener("mousedown",hideTooltip,linksOnly); function addGlobalConditionalEventListener(evType,handlerFn,whereToFireFn) { document.body.addEventListener(evType,function(evt){ // if (conditionFn(evt)) { var finalTarget = whereToFireFn(evt); if (finalTarget) { var fakeEvt = ({}); // Maybe better to do a for (var prop in evt) to construct fakeEvt? // Hmm no that acts weird, only showing properties for evt which I // have already read! // OK then let's just set the ones we know we need: fakeEvt.target = finalTarget; fakeEvt.clientX = evt.clientX; fakeEvt.clientY = evt.clientY; /* if (evType != "mousemove") { log("Performing "+evType+" on "+fakeEvt.target); } */ return handlerFn(fakeEvt); } },true); } } // == Initialise Tooltips == // if (showTooltips) { initialiseTooltipListeners(); } // == Initialise current-page auto-lookup == // function getColorForCount(tot) { /* //// Colorful version (light cyan to dull dark blue) var thru = Math.log(Number(tot)/40)/8; // popBar.style.backgroundColor = 'rgb(128,'+parseInt(127+128*thru)+','+parseInt(255-128*thru)+')'; // var hue = 2/3 - 1/3*thru; // blue -> cyan -> green // var hue = thru/3; // red -> yellow -> green // var saturation = 0.4; // var variance = 0.9; var hue = 1.8/3; var saturation = 0.6+0.3*thru; var variance = 0.9-0.4*thru; return hsv2rgbString(hue, saturation, variance); */ //// One-hue version, faint light to strong dark blue. var greatness = Math.min(1.0,Math.log(tot) / Math.log(10000)); var saturation = 40 + 60*greatness; var brightness = 70 - 30*greatness; // scoreSpan.style.backgroundColor = hsv2rgbString(2/3,0.3+0.5*greatness,0.8); return "hsl(240,"+saturation+"%,"+brightness+"%)"; } function createScoreSpan(resultObj) { var scoreSpan = document.createElement("span"); if (resultObj.total_posts) { var text = addCommasToNumber(resultObj.total_posts); scoreSpan.appendChild(boldTextElement(text)); scoreSpan.style.backgroundColor = getColorForCount(resultObj.total_posts); } scoreSpan.style.color = 'white'; scoreSpan.style.fontWeight = 'bold'; if (resultObj.top_tags) { var tagArray = []; for (var tag in resultObj.top_tags) { tagArray.push(tag); } // Not needed - they are already sorted tagArray.sort( function(a,b) { return resultObj.top_tags[a] < resultObj.top_tags[b]; }); scoreSpan.title = tagArray.join(", "); // scoreSpan.title = "Popularity: "+addCommasToNumber(resultObj.total_posts) + " Tags: "+tagArray.join(", "); // scoreSpan.title = addCommasToNumber(resultObj.total_posts)+" "+tagArray.join(", "); } return scoreSpan; } if (lookupCurrentPage) { try { tryLookup(document.location.href,function(resultObj,subjectUrl,evt){ if (resultObj.total_posts) { var lc_div = createScoreSpan(resultObj); lc_div.style.position = 'fixed'; lc_div.style.top = '20px'; lc_div.style.right = '20px'; lc_div.style.padding = '4px'; lc_div.style.fontSize = '2.0em'; lc_div.style.zIndex = 1209; document.body.appendChild(lc_div); } },function(){ /* Do nothing on failure. */ }); } catch (e) { log("DLT On "+document.location); log("DLT trying current page lookup, caught exception: "+e); log(e); } } // == Initialise all-links auto-lookup == // var lookupSpeed = 50; var lastHref = null; function addLabel(link) { var url = link.href; var sameAsLast = (url == lastHref); var sameHost = (link.host==document.location.host); var badHost = (link.host=="webcache.googleusercontent.com") || (link.host.match(".google.com")); var goodUrl = startsWith(url,"http://") || startsWith(url,"https://") || url.indexOf(":")==-1; // skip any "about:config" or "javascript:blah" links var hasHash = (url.indexOf("#") >= 0); var samePage = (url.split("#")[0] == document.location.href.split("#")[0]); // Some links are just too common to spam Deliciouos for all of them. // e.g. searches maybe not, but page 3 of the search result no! var isGoogleSearch = ( url.indexOf("/search?")>-1 || url.indexOf("/setprefs?")>-1 ); var isCommonSearch = ( url.indexOf('?q=')>=0 || url.indexOf('&q=')>=0 ); var isSearch = isCommonSearch || isGoogleSearch; if (isCommonSearch) { // GM_log("[DLT] Skipping due to ?q= or &q= in "+url); } var isImage = false; // isImageAndWhitespace(link); var isBlacklisted = url.match(/fbcdn.net\//) || url.match(/facebook.com\//); var causesDivGrowthOnGoogle = link.parentNode.className=='gbt' || link.className=='gbzt'; if (sameAsLast || badHost || isSearch || samePage || isImage || isBlacklisted || causesDivGrowthOnGoogle) { return; } if (url && goodUrl) { // && !sameHost function addAnnotationLabel(resultObj,subjectUrl,evt){ // log("Adding annotation for "+url+" with result "+JSON.stringify(resultObj)); if (resultObj && resultObj.total_posts) { var newDiv = createScoreSpan(resultObj); var text = " " + (resultObj && addCommasToNumber(resultObj.total_posts)) + " "; // BUG TODO: Sometimes the left or right space gets ignored, because we are in a span, and it merges with outer text newDiv.textContent = text; newDiv.style.marginLeft = '0.2em'; newDiv.style.marginRight = '0.4em'; newDiv.style.padding = '0.1em 0.2em 0.1em 0.2em'; // newDiv.style.padding = '0'; newDiv.style.verticalAlign = "middle"; // newDiv.style.verticalAlign = "super"; // newDiv.style.marginTop = '-1.5em'; // For "super", sometimes works. // newDiv.style.marginBottom = '-1.5em'; // Try to not make our parent line grow taller newDiv.style.opacity = 0.7; // newDiv.style.fontSize = parseInt((greatness+0.1)*10)/10 + 'em'; newDiv.style.fontSize = '0.6em'; // newDiv.style.paddingBottom = "0.2em"; // newDiv.firstChild.style.backgroundColor = "hsv("+2/3+","+greatness+",0.8)"; link.parentNode.insertBefore(newDiv,link.nextSibling); // link.parentNode.appendChild(newDiv); // newDiv.addEventListener('mouseover',function(evt){ showResultsTooltip(resultObj,subjectUrl,evt); },false); // newDiv.addEventListener('mouseout',hideTooltipMomentarily,false); } resetColor(); } var resetColor; if (showProgress) { var oldbgcol = link.style.backgroundColor; link.style.backgroundColor = '#ffff44'; resetColor = function() { // log("Resetting color of "+link); link.style.backgroundColor = oldbgcol; }; } else { resetColor = function() { }; } lastHref = url; tryLookup(url,addAnnotationLabel,resetColor); } } // Let's not auto-annotate links on delicious pages - it is likely to be redundant information! if (annotateAllLinks && document.location.host != "www.delicious.com") { (function(){ var links = document.getElementsByTagName("A"); var i = 0; function doOne() { if (i < links.length) { addLabel(links[i]); i++; if (lookupSpeed <= 50 && Math.random()<0.8) { doOne(); } else { setTimeout(doOne,lookupSpeed); lookupSpeed = 20; } } else { log("Considered "+i+" links on "+document.location+" for annotation with Delicious labels."); } } doOne(); })(); } // == Cleanup old cached data == // if (Math.random() < 0.1) { setTimeout(cleanupCache,1000 * (120+120*Math.random())); // 3 minutes +/- 1 minute, to avoid e.g. overlap when opening many pages at the same moment } /* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as defined in RFC 1321. * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet * Distributed under the BSD License * See http://pajhome.org.uk/crypt/md5 for more info. */ /* * Configurable variables. You may need to tweak these to be compatible with * the server-side, but the defaults work in most cases. */ var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ /* * These are the functions you'll usually want to call * They take string arguments and return either hex or base-64 encoded strings */ function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));} function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));} function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));} function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); } function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); } function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); } /* * Perform a simple self-test to see if the VM is working */ function md5_vm_test() { return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72"; } /* * Calculate the MD5 of an array of little-endian words, and a bit length */ function core_md5(x, len) { /* append padding */ x[len >> 5] |= 0x80 << ((len) % 32); x[(((len + 64) >>> 9) << 4) + 14] = len; var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; for(var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); c = md5_ff(c, d, a, b, x[i+10], 17, -42063); b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); } return Array(a, b, c, d); } /* * These functions implement the four basic operations the algorithm uses. */ function md5_cmn(q, a, b, x, s, t) { return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); } function md5_ff(a, b, c, d, x, s, t) { return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); } function md5_gg(a, b, c, d, x, s, t) { return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); } function md5_hh(a, b, c, d, x, s, t) { return md5_cmn(b ^ c ^ d, a, b, x, s, t); } function md5_ii(a, b, c, d, x, s, t) { return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); } /* * Calculate the HMAC-MD5, of a key and some data */ function core_hmac_md5(key, data) { var bkey = str2binl(key); if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); var ipad = Array(16), opad = Array(16); for(var i = 0; i < 16; i++) { ipad[i] = bkey[i] ^ 0x36363636; opad[i] = bkey[i] ^ 0x5C5C5C5C; } var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); return core_md5(opad.concat(hash), 512 + 128); } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ function safe_add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /* * Bitwise rotate a 32-bit number to the left. */ function bit_rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } /* * Convert a string to an array of little-endian words * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. */ function str2binl(str) { var bin = Array(); var mask = (1 << chrsz) - 1; for(var i = 0; i < str.length * chrsz; i += chrsz) bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32); return bin; } /* * Convert an array of little-endian words to a string */ function binl2str(bin) { var str = ""; var mask = (1 << chrsz) - 1; for(var i = 0; i < bin.length * 32; i += chrsz) str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); return str; } /* * Convert an array of little-endian words to a hex string. */ function binl2hex(binarray) { var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; var str = ""; for(var i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); } return str; } /* * Convert an array of little-endian words to a base-64 string */ function binl2b64(binarray) { var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var str = ""; for(var i = 0; i < binarray.length * 4; i += 3) { var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); for(var j = 0; j < 4; j++) { if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); } } return str; }