diff --git a/.gitignore b/.gitignore index 496ee2c..6e984a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.DS_Store \ No newline at end of file +.DS_Store +.log +node_modules/ diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..33bd7e4 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,16 @@ +module.exports = function(grunt) { + grunt.loadNpmTasks('grunt-contrib-uglify'); + + grunt.initConfig({ + uglify: { + all: { + options: { + preserveComments: 'some' + }, + files: { + 'tinycon.min.js': ['tinycon.js'] + } + }, + }, + }); +}; \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e8a657b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Tom Moor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 4066577..44da454 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Tinycon -A small library for manipulating the favicon, in particular adding alert bubbles and changing images. Tinycon gracefully falls back to a number in title approach for browers that don't support canvas or dynamic favicons. +A small library for manipulating the favicon, in particular adding alert bubbles and changing images. Tinycon gracefully falls back to a number in title approach for browsers that don't support canvas or dynamic favicons. @@ -8,7 +8,19 @@ A small library for manipulating the favicon, in particular adding alert bubbles ## Documentation -Tinycon adds a single object to the global namespace and does not require initialisation. +Tinycon adds a single object to the global namespace and does not require initialization. + + +### Installation + +Install with your favorite package manager. + +``` +npm install tinycon --save +``` +``` +yarn add tinycon +``` ### Basic Usage @@ -18,13 +30,13 @@ Tinycon.setBubble(6); ### Options -Tinycon can take a range of options to customise the look +Tinycon can take a range of options to customize the look * width: the width of the alert bubble * height: the height of the alert bubble * font: a css string to use for the fontface (recommended to leave this) -* colour: the foreground font colour -* background: the alert bubble background colour +* color: the foreground font color +* background: the alert bubble background color * fallback: should we fallback to a number in brackets for browsers that don't support canvas/dynamic favicons? Boolean, or use the string 'force' to ensure a title update even in supported browsers. * abbreviate: should tinycon shrink large numbers such as 1000 to an abbreviated version (1k). Boolean, defaults to true @@ -33,12 +45,35 @@ Tinycon.setOptions({ width: 7, height: 9, font: '10px arial', - colour: '#ffffff', + color: '#ffffff', background: '#549A2F', fallback: true }); ``` +### AMD support + +Tinycon can also be used as an asynchronous module. + +```javascript +require([ + 'tinycon.js' +], function (T) { + + T.setOptions({ + width: 7, + height: 9, + font: '10px arial', + color: '#ffffff', + background: '#549A2F', + fallback: true + }); + + T.setBubble(7); + +}); +``` + ## Browser Support Tinycon has been tested to work completely in the following browsers. Older versions may be supported, but haven't been tested: @@ -47,12 +82,17 @@ Tinycon has been tested to work completely in the following browsers. Older vers * Firefox 9+ * Opera 11+ -Currently the library degrades to title update: +Currently the library degrades to title update in the following browsers: * Internet Explorer 9 * Safari 5 +## Development + +To produce the minified file run `grunt uglify` + + ## License / Credits Tinycon is released under the MIT license. It is simple and easy to understand and places almost no restrictions on what you can do with Tinycon. diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..23e091a --- /dev/null +++ b/bower.json @@ -0,0 +1,30 @@ +{ + "name": "tinycon", + "description": "A small library for manipulating the favicon, in particular adding alert bubbles and changing images.", + "homepage": "http://blog.tommoor.com/tinycon/", + "repository": { + "type": "git", + "url": "https://github.com/tommoor/tinycon.git" + }, + "moduleType": [ + "globals", + "amd" + ], + "keywords": [ + "favicon", + "notification" + ], + "main": [ + "tinycon.js" + ], + "ignore": [ + "examples", + "Gruntfile.js", + ".gitignore", + "*.md", + "LICENSE", + "package.json" + ], + "private": false, + "license": "MIT" +} diff --git a/examples/index-amd.html b/examples/index-amd.html new file mode 100644 index 0000000..34eef1f --- /dev/null +++ b/examples/index-amd.html @@ -0,0 +1,17 @@ + + + + Tinycon + + + + + + + \ No newline at end of file diff --git a/examples/require.js b/examples/require.js new file mode 100644 index 0000000..d08afe4 --- /dev/null +++ b/examples/require.js @@ -0,0 +1,35 @@ +/* + RequireJS 2.1.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + Available via the MIT or new BSD license. + see: http://github.com/jrburke/requirejs for details +*/ +var requirejs,require,define; +(function(Y){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function x(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(H(n)){if(this.events.error)try{e=j.execCb(c,n,b,e)}catch(d){a=d}else e=j.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",C(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&& +!this.ignore&&(p[c]=e,l.onResourceLoad))l.onResourceLoad(j,this.map,this.depMaps);delete k[c];this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=h(a.prefix);this.depMaps.push(d);s(d,"defined",t(this,function(e){var n,d;d=this.map.name;var v=this.map.parentMap?this.map.parentMap.name:null,f=j.makeRequire(a.parentMap,{enableBuildCallback:!0, +skipMap:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,v,!0)})||""),e=h(a.prefix+"!"+d,this.map.parentMap),s(e,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=i(k,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",t(this,function(a){this.emit("error",a)}));d.enable()}}else n=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=t(this,function(a){this.inited=!0;this.error= +a;a.requireModules=[b];E(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&delete k[a.map.id]});C(a)}),n.fromText=t(this,function(e,c){var d=a.name,u=h(d),v=O;c&&(e=c);v&&(O=!1);q(u);r(m.config,b)&&(m.config[d]=m.config[b]);try{l.exec(e)}catch(k){throw Error("fromText eval for "+d+" failed: "+k);}v&&(O=!0);this.depMaps.push(u);j.completeLoad(d);f([d],n)}),e.load(a.name,f,n,m)}));j.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){this.enabling=this.enabled=!0;x(this.depMaps,t(this,function(a, +b){var c,e;if("string"===typeof a){a=h(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=i(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&s(a,"error",this.errback)}c=a.id;e=k[c];!r(N,c)&&(e&&!e.enabled)&&j.enable(a,this)}));E(this.pluginMaps,t(this,function(a){var b=i(k,a.id);b&&!b.enabled&&j.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c= +this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};j={config:m,contextName:b,registry:k,defined:p,urlFetched:S,defQueue:F,Module:W,makeModuleMap:h,nextTick:l.nextTick,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.pkgs,c=m.shim,e={paths:!0,config:!0,map:!0};E(a,function(a,b){e[b]?"map"===b?Q(m[b],a,!0,!0):Q(m[b],a,!0):m[b]=a});a.shim&&(E(a.shim,function(a, +b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=j.makeShimExports(a);c[b]=a}),m.shim=c);a.packages&&(x(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ga,"").replace(aa,"")}}),m.pkgs=b);E(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=h(b))});if(a.deps||a.callback)j.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Y,arguments)); +return b||a.exports&&Z(a.exports)}},makeRequire:function(a,d){function f(e,c,u){var i,m;d.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return C(J("requireargs","Invalid require call"),u);if(a&&r(N,e))return N[e](k[a.id]);if(l.get)return l.get(j,e,a);i=h(e,a,!1,!0);i=i.id;return!r(p,i)?C(J("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[i]}K();j.nextTick(function(){K();m=q(h(null,a));m.skipMap=d.skipMap; +m.init(e,c,u,{enabled:!0});B()});return f}d=d||{};Q(f,{isBrowser:z,toUrl:function(b){var d=b.lastIndexOf("."),g=null;-1!==d&&(g=b.substring(d,b.length),b=b.substring(0,d));return j.nameToUrl(c(b,a&&a.id,!0),g)},defined:function(b){return r(p,h(b,a,!1,!0).id)},specified:function(b){b=h(b,a,!1,!0).id;return r(p,b)||r(k,b)}});a||(f.undef=function(b){w();var c=h(b,a,!0),d=i(k,b);delete p[b];delete S[c.url];delete X[b];d&&(d.events.defined&&(X[b]=d.events),delete k[b])});return f},enable:function(a){i(k, +a.id)&&q(a).enable()},completeLoad:function(a){var b,c,d=i(m.shim,a)||{},h=d.exports;for(w();F.length;){c=F.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);D(c)}c=i(k,a);if(!b&&!r(p,a)&&c&&!c.inited){if(m.enforceDefine&&(!h||!Z(h)))return y(a)?void 0:C(J("nodefine","No define call for "+a,null,[a]));D([a,d.deps||[],d.exportsFn])}B()},nameToUrl:function(a,b){var c,d,h,f,j,k;if(l.jsExtRegExp.test(a))f=a+(b||"");else{c=m.paths;d=m.pkgs;f=a.split("/");for(j=f.length;0f.attachEvent.toString().indexOf("[native code"))&&!V?(O=!0,f.attachEvent("onreadystatechange", +b.onScriptLoad)):(f.addEventListener("load",b.onScriptLoad,!1),f.addEventListener("error",b.onScriptError,!1)),f.src=d,K=f,D?A.insertBefore(f,D):A.appendChild(f),K=null,f;$&&(importScripts(d),b.completeLoad(c))};z&&M(document.getElementsByTagName("script"),function(b){A||(A=b.parentNode);if(s=b.getAttribute("data-main"))return q.baseUrl||(G=s.split("/"),ba=G.pop(),ca=G.length?G.join("/")+"/":"./",q.baseUrl=ca,s=ba),s=s.replace(aa,""),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var i, +f;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=[]);!c.length&&H(d)&&d.length&&(d.toString().replace(ia,"").replace(ja,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(i=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),i=P;i&&(b||(b=i.getAttribute("data-requiremodule")),f=B[i.getAttribute("data-requirecontext")])}(f?f.defQueue:R).push([b,c,d])};define.amd= +{jQuery:!0};l.exec=function(b){return eval(b)};l(q)}})(this); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7797642 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "tinycon", + "version": "0.6.8", + "description": "Add notification bubbles in the favicon", + "author": "Tom Moor (http://tommoor.com)", + "license": "MIT", + "main": "./tinycon.js", + "scripts": { + "build": "grunt uglify", + "prepublish": "grunt uglify" + }, + "repository": { + "type": "git", + "url": "git://github.com/tommoor/tinycon.git" + }, + "devDependencies": { + "grunt": "~0.4.5", + "grunt-contrib-uglify": "^2.2.1" + } +} diff --git a/tinycon.js b/tinycon.js index 9b8d75a..0b77a73 100644 --- a/tinycon.js +++ b/tinycon.js @@ -1,260 +1,292 @@ /*! * Tinycon - A small library for manipulating the Favicon * Tom Moor, http://tommoor.com - * Copyright (c) 2012 Tom Moor - * MIT Licensed - * @version 0.5 -*/ + * Copyright (c) 2015 Tom Moor + * @license MIT Licensed + */ (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, - abbreviate: 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') || '').match(/\bicon\b/)) { - 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(label, colour) { - - // fallback to updating the browser title if unsupported - if (!getCanvas().getContext || browser.ie || browser.safari || options.fallback === 'force') { - return updateTitle(label); - } - - var context = getCanvas().getContext("2d"); - var colour = colour || '#000000'; - 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 ((label + '').length > 0) drawBubble(context, label, 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(label) { - - if (options.fallback) { - if ((label + '').length > 0) { - document.title = '(' + label + ') ' + originalTitle; - } else { - document.title = originalTitle; - } - } - }; - - var drawBubble = function(context, label, colour) { - - // automatic abbreviation for long (>2 digits) numbers - if (typeof label == 'number' && label > 99 && options.abbreviate) { - label = abbreviateNumber(label); - } - - // bubble needs to be larger for double digits - var len = (label + '').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(); - - // label - context.fillStyle = options.colour; - context.textAlign = "right"; - context.textBaseline = "top"; - - // unfortunately webkit/mozilla are a pixel different in text positioning - context.fillText(label, 15, browser.mozilla ? 7 : 6); - }; - - var refreshFavicon = function(){ - // check support - if (!getCanvas().getContext) return; - - setFaviconTag(getCanvas().toDataURL()); - }; - - var abbreviateNumber = function(label) { - var metricPrefixes = [ - ['G', 1000000000], - ['M', 1000000], - ['k', 1000] - ]; - - for(var i = 0; i < metricPrefixes.length; ++i) { - if (label >= metricPrefixes[i][1]) { - label = round(label / metricPrefixes[i][1]) + metricPrefixes[i][0]; - break; - } - } - - return label; - }; - - var round = function (value, precision) { - var number = new Number(value); - return number.toFixed(precision); - }; - - // 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(label, colour) { - label = label || ''; - drawFavicon(label, colour); - return this; - }; - - Tinycon.reset = function(){ - setFaviconTag(originalFavicon); - }; - - Tinycon.setOptions(defaults); - window.Tinycon = Tinycon; + var Tinycon = {}; + var currentFavicon = null; + var originalFavicon = null; + var faviconImage = null; + var canvas = null; + var options = {}; + // Chrome browsers with nonstandard zoom report fractional devicePixelRatio. + var r = Math.ceil(window.devicePixelRatio) || 1; + var size = 16 * r; + var defaults = { + width: 7, + height: 9, + font: 10 * r + 'px arial', + color: '#ffffff', + background: '#F03D25', + fallback: true, + crossOrigin: true, + abbreviate: 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('trident'), + 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/i)) { + return links[i]; + } + } + + return false; + }; + + var removeFaviconTag = function(){ + + var links = document.getElementsByTagName('link'); + + for(var i=0, len=links.length; i < len; i++) { + var exists = (typeof(links[i]) !== 'undefined'); + if (exists && (links[i].getAttribute('rel') || '').match(/\bicon\b/i)) { + links[i].parentNode.removeChild(links[i]); + } + } + }; + + var getCurrentFavicon = function(){ + + if (!originalFavicon || !currentFavicon) { + var tag = getFaviconTag(); + currentFavicon = tag ? tag.getAttribute('href') : '/favicon.ico'; + if (!originalFavicon) { + originalFavicon = currentFavicon; + } + } + + return currentFavicon; + }; + + var getCanvas = function (){ + + if (!canvas) { + canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + } + + return canvas; + }; + + var setFaviconTag = function(url){ + if(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(label, color) { + + // fallback to updating the browser title if unsupported + if (!getCanvas().getContext || browser.ie || browser.safari || options.fallback === 'force') { + return updateTitle(label); + } + + var context = getCanvas().getContext("2d"); + var color = color || '#000000'; + var src = getCurrentFavicon(); + + faviconImage = document.createElement('img'); + faviconImage.onload = function() { + + // clear canvas + context.clearRect(0, 0, size, size); + + // draw the favicon + context.drawImage(faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, size, size); + + // draw bubble over the top + if ((label + '').length > 0) drawBubble(context, label, color); + + // 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/) && options.crossOrigin) { + faviconImage.crossOrigin = 'anonymous'; + } + + faviconImage.src = src; + }; + + var updateTitle = function(label) { + + if (options.fallback) { + // Grab the current title that we can prefix with the label + var originalTitle = document.title; + + // Strip out the old label if there is one + if (originalTitle[0] === '(') { + originalTitle = originalTitle.slice(originalTitle.indexOf(' ')); + } + + if ((label + '').length > 0) { + document.title = '(' + label + ') ' + originalTitle; + } else { + document.title = originalTitle; + } + } + }; + + var drawBubble = function(context, label, color) { + + // automatic abbreviation for long (>2 digits) numbers + if (typeof label == 'number' && label > 99 && options.abbreviate) { + label = abbreviateNumber(label); + } + + // bubble needs to be larger for double digits + var len = (label + '').length-1; + + var width = options.width * r + (6 * r * len), + height = options.height * r; + + var top = size - height, + left = size - width - r, + bottom = 16 * r, + right = 16 * r, + radius = 2 * r; + + // 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 = r; + + // bubble + context.beginPath(); + context.moveTo(left + radius, top); + context.quadraticCurveTo(left, top, left, top + radius); + context.lineTo(left, bottom - radius); + context.quadraticCurveTo(left, bottom, left + radius, bottom); + context.lineTo(right - radius, bottom); + context.quadraticCurveTo(right, bottom, right, bottom - radius); + context.lineTo(right, top + radius); + context.quadraticCurveTo(right, top, right - radius, top); + context.closePath(); + context.fill(); + + // bottom shadow + context.beginPath(); + context.strokeStyle = "rgba(0,0,0,0.3)"; + context.moveTo(left + radius / 2.0, bottom); + context.lineTo(right - radius / 2.0, bottom); + context.stroke(); + + // label + context.fillStyle = options.color; + context.textAlign = "right"; + context.textBaseline = "top"; + + // unfortunately webkit/mozilla are a pixel different in text positioning + context.fillText(label, r === 2 ? 29 : 15, browser.mozilla ? 7*r : 6*r); + }; + + var refreshFavicon = function(){ + // check support + if (!getCanvas().getContext) return; + + setFaviconTag(getCanvas().toDataURL()); + }; + + var abbreviateNumber = function(label) { + var metricPrefixes = [ + ['G', 1000000000], + ['M', 1000000], + ['k', 1000] + ]; + + for(var i = 0; i < metricPrefixes.length; ++i) { + if (label >= metricPrefixes[i][1]) { + label = round(label / metricPrefixes[i][1]) + metricPrefixes[i][0]; + break; + } + } + + return label; + }; + + var round = function (value, precision) { + var number = new Number(value); + return number.toFixed(precision); + }; + + // public methods + Tinycon.setOptions = function(custom){ + options = {}; + + // account for deprecated UK English spelling + if (custom.colour) { + custom.color = custom.colour; + } + + 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(label, color) { + label = label || ''; + drawFavicon(label, color); + return this; + }; + + Tinycon.reset = function(){ + currentFavicon = originalFavicon; + setFaviconTag(originalFavicon); + }; + + Tinycon.setOptions(defaults); + + if(typeof define === 'function' && define.amd) { + define(Tinycon); + } else if (typeof module !== 'undefined') { + module.exports = Tinycon; + } else { + window.Tinycon = Tinycon; + } + })(); diff --git a/tinycon.min.js b/tinycon.min.js index 1a9403c..659c99a 100644 --- a/tinycon.min.js +++ b/tinycon.min.js @@ -1,8 +1,7 @@ /*! - Tinycon - A small library for manipulating the Favicon - Tom Moor, http://tommoor.com - Copyright (c) 2012 Tom Moor - MIT Licensed - @version 0.5 -*/ -(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,abbreviate:true};var ua=(function(){var agent=navigator.userAgent.toLowerCase();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')};var getFaviconTag=function(){var links=document.getElementsByTagName('link');for(var i=0,len=links.length;i0)drawBubble(context,label,colour);refreshFavicon()};if(!src.match(/^data/)){faviconImage.crossOrigin='anonymous'}faviconImage.src=src};var updateTitle=function(label){if(options.fallback){if((label+'').length>0){document.title='('+label+') '+originalTitle}else{document.title=originalTitle}}};var drawBubble=function(context,label,colour){if(typeof label=='number'&&label>99&&options.abbreviate){label=abbreviateNumber(label)}var len=(label+'').length-1;var width=options.width+(6*len);var w=16-width;var h=16-options.height;context.font=(browser.webkit?'bold ':'')+options.font;context.fillStyle=options.background;context.strokeStyle=options.background;context.lineWidth=1;context.fillRect(w,h,width-1,options.height);context.beginPath();context.moveTo(w-0.5,h+1);context.lineTo(w-0.5,15);context.stroke();context.beginPath();context.moveTo(15.5,h+1);context.lineTo(15.5,15);context.stroke();context.beginPath();context.strokeStyle="rgba(0,0,0,0.3)";context.moveTo(w,16);context.lineTo(15,16);context.stroke();context.fillStyle=options.colour;context.textAlign="right";context.textBaseline="top";context.fillText(label,15,browser.mozilla?7:6)};var refreshFavicon=function(){if(!getCanvas().getContext)return;setFaviconTag(getCanvas().toDataURL())};var abbreviateNumber=function(label){var metricPrefixes=[['G',1000000000],['M',1000000],['k',1000]];for(var i=0;i=metricPrefixes[i][1]){label=round(label/metricPrefixes[i][1])+metricPrefixes[i][0];break}}return label};var round=function(value,precision){var number=new Number(value);return number.toFixed(precision)};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(label,colour){label=label||'';drawFavicon(label,colour);return this};Tinycon.reset=function(){setFaviconTag(originalFavicon)};Tinycon.setOptions(defaults);window.Tinycon=Tinycon})(); \ No newline at end of file + * Tinycon - A small library for manipulating the Favicon + * Tom Moor, http://tommoor.com + * Copyright (c) 2015 Tom Moor + * @license MIT Licensed + */ +!function(){var a={},b=null,c=null,d=null,e=null,f={},g=Math.ceil(window.devicePixelRatio)||1,h=16*g,i={width:7,height:9,font:10*g+"px arial",color:"#ffffff",background:"#F03D25",fallback:!0,crossOrigin:!0,abbreviate:!0},j=function(){var a=navigator.userAgent.toLowerCase();return function(b){return a.indexOf(b)!==-1}}(),k={ie:j("trident"),chrome:j("chrome"),webkit:j("chrome")||j("safari"),safari:j("safari")&&!j("chrome"),mozilla:j("mozilla")&&!j("chrome")&&!j("safari")},l=function(){for(var a=document.getElementsByTagName("link"),b=0,c=a.length;b0&&s(c,a,b),t()},!e.match(/^data/)&&f.crossOrigin&&(d.crossOrigin="anonymous"),d.src=e},r=function(a){if(f.fallback){var b=document.title;"("===b[0]&&(b=b.slice(b.indexOf(" "))),(a+"").length>0?document.title="("+a+") "+b:document.title=b}},s=function(a,b,c){"number"==typeof b&&b>99&&f.abbreviate&&(b=u(b));var d=(b+"").length-1,e=f.width*g+6*g*d,i=f.height*g,j=h-i,l=h-e-g,m=16*g,n=16*g,o=2*g;a.font=(k.webkit?"bold ":"")+f.font,a.fillStyle=f.background,a.strokeStyle=f.background,a.lineWidth=g,a.beginPath(),a.moveTo(l+o,j),a.quadraticCurveTo(l,j,l,j+o),a.lineTo(l,m-o),a.quadraticCurveTo(l,m,l+o,m),a.lineTo(n-o,m),a.quadraticCurveTo(n,m,n,m-o),a.lineTo(n,j+o),a.quadraticCurveTo(n,j,n-o,j),a.closePath(),a.fill(),a.beginPath(),a.strokeStyle="rgba(0,0,0,0.3)",a.moveTo(l+o/2,m),a.lineTo(n-o/2,m),a.stroke(),a.fillStyle=f.color,a.textAlign="right",a.textBaseline="top",a.fillText(b,2===g?29:15,k.mozilla?7*g:6*g)},t=function(){o().getContext&&p(o().toDataURL())},u=function(a){for(var b=[["G",1e9],["M",1e6],["k",1e3]],c=0;c=b[c][1]){a=v(a/b[c][1])+b[c][0];break}return a},v=function(a,b){return new Number(a).toFixed(b)};a.setOptions=function(a){f={},a.colour&&(a.color=a.colour);for(var b in i)f[b]=a.hasOwnProperty(b)?a[b]:i[b];return this},a.setImage=function(a){return b=a,t(),this},a.setBubble=function(a,b){return a=a||"",q(a,b),this},a.reset=function(){b=c,p(c)},a.setOptions(i),"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module?module.exports=a:window.Tinycon=a}(); \ No newline at end of file