diff --git a/dev/App/Abstract.js b/dev/App/Abstract.js
index 6fe033604..f38dbe7b1 100644
--- a/dev/App/Abstract.js
+++ b/dev/App/Abstract.js
@@ -172,7 +172,7 @@
/**
* @param {string} sTitle
*/
- AbstractApp.prototype.setTitle = function (sTitle)
+ AbstractApp.prototype.setWindowTitle = function (sTitle)
{
sTitle = ((Utils.isNormal(sTitle) && 0 < sTitle.length) ? sTitle + ' - ' : '') +
Settings.settingsGet('Title') || '';
diff --git a/dev/App/User.js b/dev/App/User.js
index ccb02367c..0c3093beb 100644
--- a/dev/App/User.js
+++ b/dev/App/User.js
@@ -9,6 +9,7 @@
$ = require('$'),
moment = require('moment'),
SimplePace = require('SimplePace'),
+ Tinycon = require('Tinycon'),
Enums = require('Common/Enums'),
Globals = require('Common/Globals'),
@@ -1342,7 +1343,7 @@
{
Globals.$html.addClass('rl-user-auth');
- this.setTitle(Translator.i18n('TITLES/LOADING'));
+ this.setWindowTitle(Translator.i18n('TITLES/LOADING'));
//require.ensure([], function() { // require code splitting
@@ -1479,6 +1480,17 @@
_.defer(function () {
self.initVerticalLayoutResizer(Enums.ClientSideKeyName.FolderListSize);
});
+
+ if (Tinycon)
+ {
+ Tinycon.setOptions({
+ fallback: false
+ });
+
+ Events.sub('mailbox.inbox-unread-count', function (iCount) {
+ Tinycon.setBubble(0 < iCount ? (99 < iCount ? 99 : iCount) : 0);
+ });
+ }
}
}
else
diff --git a/dev/Screen/Admin/Login.js b/dev/Screen/Admin/Login.js
index 9dd723aef..5624218a8 100644
--- a/dev/Screen/Admin/Login.js
+++ b/dev/Screen/Admin/Login.js
@@ -24,7 +24,7 @@
LoginAdminScreen.prototype.onShow = function ()
{
- require('App/Admin').setTitle('');
+ require('App/Admin').setWindowTitle('');
};
module.exports = LoginAdminScreen;
diff --git a/dev/Screen/Admin/Settings.js b/dev/Screen/Admin/Settings.js
index 74ccb0851..edd717088 100644
--- a/dev/Screen/Admin/Settings.js
+++ b/dev/Screen/Admin/Settings.js
@@ -75,7 +75,7 @@
SettingsAdminScreen.prototype.onShow = function ()
{
- require('App/Admin').setTitle('');
+ require('App/Admin').setWindowTitle('');
};
module.exports = SettingsAdminScreen;
diff --git a/dev/Screen/User/About.js b/dev/Screen/User/About.js
index d63c8a2b2..424f0728c 100644
--- a/dev/Screen/User/About.js
+++ b/dev/Screen/User/About.js
@@ -24,7 +24,7 @@
AboutUserScreen.prototype.onShow = function ()
{
- require('App/User').setTitle('RainLoop');
+ require('App/User').setWindowTitle('RainLoop');
};
module.exports = AboutUserScreen;
diff --git a/dev/Screen/User/Login.js b/dev/Screen/User/Login.js
index bc999a788..744aa2686 100644
--- a/dev/Screen/User/Login.js
+++ b/dev/Screen/User/Login.js
@@ -24,7 +24,7 @@
LoginUserScreen.prototype.onShow = function ()
{
- require('App/User').setTitle('');
+ require('App/User').setWindowTitle('');
};
module.exports = LoginUserScreen;
diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js
index 50f20bcaf..e98426af3 100644
--- a/dev/Screen/User/MailBox.js
+++ b/dev/Screen/User/MailBox.js
@@ -45,21 +45,21 @@
*/
MailBoxUserScreen.prototype.oLastRoute = {};
- MailBoxUserScreen.prototype.setNewTitle = function ()
+ MailBoxUserScreen.prototype.updateWindowTitle = function ()
{
var
sEmail = AccountStore.email(),
nFoldersInboxUnreadCount = FolderStore.foldersInboxUnreadCount()
;
- require('App/User').setTitle(('' === sEmail ? '' :
+ require('App/User').setWindowTitle(('' === sEmail ? '' : '' +
(0 < nFoldersInboxUnreadCount ? '(' + nFoldersInboxUnreadCount + ') ' : ' ') +
sEmail + ' - ') + Translator.i18n('TITLES/MAILBOX'));
};
MailBoxUserScreen.prototype.onShow = function ()
{
- this.setNewTitle();
+ this.updateWindowTitle();
Globals.keyScope(Enums.KeyState.MessageList);
};
@@ -112,7 +112,7 @@
SettingsStore.layout.valueHasMutated();
}, 50);
- Events.sub('mailbox.inbox-unread-count', function (iCount) {
+ Events.sub('mailbox.inbox-unread-count', _.bind(function (iCount) {
FolderStore.foldersInboxUnreadCount(iCount);
@@ -124,12 +124,10 @@
oItem.count(iCount);
}
});
- });
- FolderStore.foldersInboxUnreadCount.subscribe(function () {
- this.setNewTitle();
- }, this);
+ this.updateWindowTitle();
+ }, this));
};
MailBoxUserScreen.prototype.onBuild = function ()
diff --git a/dev/Screen/User/Settings.js b/dev/Screen/User/Settings.js
index ecd5359fc..b9e05ff5f 100644
--- a/dev/Screen/User/Settings.js
+++ b/dev/Screen/User/Settings.js
@@ -122,7 +122,7 @@
SettingsUserScreen.prototype.setSettingsTitle = function ()
{
- require('App/User').setTitle(this.sSettingsTitle);
+ require('App/User').setWindowTitle(this.sSettingsTitle);
};
module.exports = SettingsUserScreen;
diff --git a/dev/bootstrap.js b/dev/bootstrap.js
index 2572f95fa..d88505169 100644
--- a/dev/bootstrap.js
+++ b/dev/bootstrap.js
@@ -54,7 +54,7 @@
window['__APP_BOOT'] = function (fCall) {
- $(function () {
+ $(_.delay(function () {
if (!$('#rl-content').is(':visible'))
{
@@ -82,7 +82,8 @@
}
window['__APP_BOOT'] = null;
- });
+
+ }, 10));
};
};
diff --git a/gulpfile.js b/gulpfile.js
index ff5587109..4976b8976 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -157,6 +157,7 @@ cfg.paths.js = {
'vendors/queue/queue.min.js',
'vendors/inputosaurus/inputosaurus.min.js',
'vendors/moment/min/moment.min.js ',
+ 'vendors/tinycon/tinycon.min.js ',
'vendors/routes/signals.min.js',
'vendors/routes/hasher.min.js',
'vendors/routes/crossroads.min.js',
diff --git a/vendors/tinycon/LICENSE b/vendors/tinycon/LICENSE
new file mode 100644
index 000000000..fdb4f08c8
--- /dev/null
+++ b/vendors/tinycon/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2013 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/vendors/tinycon/README.md b/vendors/tinycon/README.md
new file mode 100644
index 000000000..80dac2627
--- /dev/null
+++ b/vendors/tinycon/README.md
@@ -0,0 +1,90 @@
+# 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.
+
+
+
+See the Live Demo here.
+
+## Documentation
+
+Tinycon adds a single object to the global namespace and does not require initialisation.
+
+### Basic Usage
+
+```javascript
+Tinycon.setBubble(6);
+```
+
+### Options
+
+Tinycon can take a range of options to customise 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
+* 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
+
+```javascript
+Tinycon.setOptions({
+ width: 7,
+ height: 9,
+ font: '10px arial',
+ colour: '#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',
+ colour: '#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:
+
+* Chrome 15+
+* Firefox 9+
+* Opera 11+
+
+Currently the library degrades to title update:
+
+* Internet Explorer 9
+* Safari 5
+
+
+## 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.
+[More Information](http://en.wikipedia.org/wiki/MIT_License)
+
+Tinycon was inspired by [Notificon](https://github.com/makeable/Notificon)
+
+
+## Download
+
+Releases are available for download from
+[GitHub](http://github.com/tommoor/tinycon/downloads).
diff --git a/vendors/tinycon/tinycon.js b/vendors/tinycon/tinycon.js
new file mode 100644
index 000000000..3955fb3c7
--- /dev/null
+++ b/vendors/tinycon/tinycon.js
@@ -0,0 +1,279 @@
+/*!
+ * Tinycon - A small library for manipulating the Favicon
+ * Tom Moor, http://tommoor.com
+ * Copyright (c) 2012 Tom Moor
+ * @license MIT Licensed
+ * @version 0.6.3
+ */
+
+(function(){
+
+ var Tinycon = {};
+ var currentFavicon = null;
+ var originalFavicon = null;
+ var faviconImage = null;
+ var canvas = null;
+ var options = {};
+ var r = window.devicePixelRatio || 1;
+ var size = 16 * r;
+ var defaults = {
+ width: 7,
+ height: 9,
+ font: 10 * r + 'px arial',
+ colour: '#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('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 = size;
+ canvas.height = size;
+ }
+
+ 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 = 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, 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/) && 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, 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 * 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.colour;
+ 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 = {};
+
+ 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;
+
+ if(typeof define === 'function' && define.amd) {
+ define(Tinycon);
+ }
+
+})();
diff --git a/vendors/tinycon/tinycon.min.js b/vendors/tinycon/tinycon.min.js
new file mode 100644
index 000000000..283f511ca
--- /dev/null
+++ b/vendors/tinycon/tinycon.min.js
@@ -0,0 +1,8 @@
+/*!
+ Tinycon - A small library for manipulating the Favicon
+ Tom Moor, http://tommoor.com
+ Copyright (c) 2012 Tom Moor
+ @license MIT Licensed
+ @version 0.6.3
+*/
+(function(){var Tinycon={};var currentFavicon=null;var originalFavicon=null;var faviconImage=null;var canvas=null;var options={};var r=window.devicePixelRatio||1;var size=16*r;var defaults={width:7,height:9,font:10*r+'px arial',colour:'#ffffff',background:'#F03D25',fallback:true,crossOrigin: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/)&&options.crossOrigin){faviconImage.crossOrigin='anonymous'}faviconImage.src=src};var updateTitle=function(label){if(options.fallback){var originalTitle=document.title;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,colour){if(typeof label=='number'&&label>99&&options.abbreviate){label=abbreviateNumber(label)}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;context.font=(browser.webkit?'bold ':'')+options.font;context.fillStyle=options.background;context.strokeStyle=options.background;context.lineWidth=r;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();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();context.fillStyle=options.colour;context.textAlign="right";context.textBaseline="top";context.fillText(label,r===2?29:15,browser.mozilla?7*r:6*r)};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;if(typeof define==='function'&&define.amd){define(Tinycon)}})();
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index e6300ec27..d1ce5de8e 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -38,6 +38,7 @@ module.exports = {
'hasher': 'window.hasher',
'Jua': 'window.Jua',
'Autolinker': 'window.Autolinker',
+ 'Tinycon': 'window.Tinycon',
'buzz': 'window.buzz',
'ssm': 'window.ssm',
'key': 'window.key',