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