From dc9eda9364eec93983ba95ea3560c5c72296af0b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 12 Jul 2012 20:18:33 +0200 Subject: [PATCH] Use Tinycon to display chat mentions in favicon. --- src/static/js/chat.js | 12 +- src/static/js/tinycon.js | 237 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 7 deletions(-) create mode 100644 src/static/js/tinycon.js diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 18904352f..9dfc9dc1f 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -23,11 +23,12 @@ var padutils = require('./pad_utils').padutils; var padcookie = require('./pad_cookie').padcookie; +require('./tinycon'); + var chat = (function() { var isStuck = false; var chatMentions = 0; - var title = document.title; var self = { show: function () { @@ -35,7 +36,7 @@ var chat = (function() $("#chatbox").show(); self.scrollDown(); chatMentions = 0; - document.title = title; + Tinycon.setBubble(0); }, stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen { @@ -126,12 +127,9 @@ var chat = (function() // chat throb stuff -- Just make it throw for twice as long if(wasMentioned && !alreadyFocused) { // If the user was mentioned show for twice as long and flash the browser window - if (chatMentions == 0){ - title = document.title; - } $('#chatthrob').html(""+authorName+"" + ": " + text).show().delay(4000).hide(400); chatMentions++; - document.title = "("+chatMentions+") " + title; + Tinycon.setBubble(chatMentions); } else { @@ -141,7 +139,7 @@ var chat = (function() // Clear the chat mentions when the user clicks on the chat input box $('#chatinput').click(function(){ chatMentions = 0; - document.title = title; + Tinycon.setBubble(0); }); self.scrollDown(); diff --git a/src/static/js/tinycon.js b/src/static/js/tinycon.js new file mode 100644 index 000000000..0e6a10a10 --- /dev/null +++ b/src/static/js/tinycon.js @@ -0,0 +1,237 @@ +/*! + * Tinycon - A small library for manipulating the Favicon + * Tom Moor, http://tommoor.com + * Copyright (c) 2012 Tom Moor + * MIT Licensed + * @version 0.2.6 +*/ + +(function(){ + + var Tinycon = {}; + var currentFavicon = null; + var originalFavicon = null; + var originalTitle = document.title; + var faviconImage = null; + var canvas = null; + var options = {}; + var defaults = { + width: 7, + height: 9, + font: '10px arial', + colour: '#ffffff', + background: '#F03D25', + fallback: true + }; + + var ua = (function () { + var agent = navigator.userAgent.toLowerCase(); + // New function has access to 'agent' via closure + return function (browser) { + return agent.indexOf(browser) !== -1; + }; + }()); + + var browser = { + ie: ua('msie'), + chrome: ua('chrome'), + webkit: ua('chrome') || ua('safari'), + safari: ua('safari') && !ua('chrome'), + mozilla: ua('mozilla') && !ua('chrome') && !ua('safari') + }; + + // private methods + var getFaviconTag = function(){ + + var links = document.getElementsByTagName('link'); + + for(var i=0, len=links.length; i < len; i++) { + if ((links[i].getAttribute('rel') || '').match(/\bicon\b/)) { + return links[i]; + } + } + + return false; + }; + + var removeFaviconTag = function(){ + + var links = document.getElementsByTagName('link'); + var head = document.getElementsByTagName('head')[0]; + + for(var i=0, len=links.length; i < len; i++) { + var exists = (typeof(links[i]) !== 'undefined'); + if (exists && links[i].getAttribute('rel') === 'icon') { + head.removeChild(links[i]); + } + } + }; + + var getCurrentFavicon = function(){ + + if (!originalFavicon || !currentFavicon) { + var tag = getFaviconTag(); + originalFavicon = currentFavicon = tag ? tag.getAttribute('href') : '/favicon.ico'; + } + + return currentFavicon; + }; + + var getCanvas = function (){ + + if (!canvas) { + canvas = document.createElement("canvas"); + canvas.width = 16; + canvas.height = 16; + } + + return canvas; + }; + + var setFaviconTag = function(url){ + removeFaviconTag(); + + var link = document.createElement('link'); + link.type = 'image/x-icon'; + link.rel = 'icon'; + link.href = url; + document.getElementsByTagName('head')[0].appendChild(link); + }; + + var log = function(message){ + if (window.console) window.console.log(message); + }; + + var drawFavicon = function(num, colour) { + + // fallback to updating the browser title if unsupported + if (!getCanvas().getContext || browser.ie || browser.safari || options.fallback === 'force') { + return updateTitle(num); + } + + var context = getCanvas().getContext("2d"); + var colour = colour || '#000000'; + var num = num || 0; + var src = getCurrentFavicon(); + + faviconImage = new Image(); + faviconImage.onload = function() { + + // clear canvas + context.clearRect(0, 0, 16, 16); + + // draw original favicon + context.drawImage(faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, 16, 16); + + // draw bubble over the top + if (num > 0) drawBubble(context, num, colour); + + // refresh tag in page + refreshFavicon(); + }; + + // allow cross origin resource requests if the image is not a data:uri + // as detailed here: https://github.com/mrdoob/three.js/issues/1305 + if (!src.match(/^data/)) { + faviconImage.crossOrigin = 'anonymous'; + } + + faviconImage.src = src; + }; + + var updateTitle = function(num) { + + if (options.fallback) { + if (num > 0) { + document.title = '('+num+') ' + originalTitle; + } else { + document.title = originalTitle; + } + } + }; + + var drawBubble = function(context, num, colour) { + + // bubble needs to be larger for double digits + var len = (num+"").length-1; + var width = options.width + (6*len); + var w = 16-width; + var h = 16-options.height; + + // webkit seems to render fonts lighter than firefox + context.font = (browser.webkit ? 'bold ' : '') + options.font; + context.fillStyle = options.background; + context.strokeStyle = options.background; + context.lineWidth = 1; + + // bubble + context.fillRect(w,h,width-1,options.height); + + // rounded left + context.beginPath(); + context.moveTo(w-0.5,h+1); + context.lineTo(w-0.5,15); + context.stroke(); + + // rounded right + context.beginPath(); + context.moveTo(15.5,h+1); + context.lineTo(15.5,15); + context.stroke(); + + // bottom shadow + context.beginPath(); + context.strokeStyle = "rgba(0,0,0,0.3)"; + context.moveTo(w,16); + context.lineTo(15,16); + context.stroke(); + + // number + context.fillStyle = options.colour; + context.textAlign = "right"; + context.textBaseline = "top"; + + // unfortunately webkit/mozilla are a pixel different in text positioning + context.fillText(num, 15, browser.mozilla ? 7 : 6); + }; + + var refreshFavicon = function(){ + // check support + if (!getCanvas().getContext) return; + + setFaviconTag(getCanvas().toDataURL()); + }; + + + // public methods + Tinycon.setOptions = function(custom){ + options = {}; + + for(var key in defaults){ + options[key] = custom.hasOwnProperty(key) ? custom[key] : defaults[key]; + } + return this; + }; + + Tinycon.setImage = function(url){ + currentFavicon = url; + refreshFavicon(); + return this; + }; + + Tinycon.setBubble = function(num, colour){ + + // validate + if(isNaN(parseFloat(num)) || !isFinite(num)) return log('Bubble must be a number'); + + drawFavicon(num, colour); + return this; + }; + + Tinycon.reset = function(){ + Tinycon.setImage(originalFavicon); + }; + + Tinycon.setOptions(defaults); + window.Tinycon = Tinycon; +})();