From af1c14939e443cf37ca981756bdbbc804d118c40 Mon Sep 17 00:00:00 2001
From: Chris Hunt <c@chrishunt.co>
Date: Fri, 7 Jun 2013 17:15:49 -0700
Subject: [PATCH] Add 'dynamic favicon' setting

---
 app/assets/images/default-favicon.ico         | Bin 0 -> 1342 bytes
 app/assets/javascripts/discourse.js           |   8 +
 .../external/jquery.faviconNotify.js          | 224 ++++++++++++++++++
 app/models/site_setting.rb                    |   3 +-
 config/locales/server.en.yml                  |   1 +
 5 files changed, 235 insertions(+), 1 deletion(-)
 create mode 100644 app/assets/images/default-favicon.ico
 create mode 100644 app/assets/javascripts/external/jquery.faviconNotify.js

diff --git a/app/assets/images/default-favicon.ico b/app/assets/images/default-favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..d20ae8eeded413f3de968472303a4dbc2a63409c
GIT binary patch
literal 1342
zcmd^8ZBJ5R7=98VA)LGk2*<-opa2sX2uMIXK_&%QCI}&FTf`hq#7eUjAtPGNm+C_s
zt@_lL-1=5Ou<JJ4D)kq1vy1!vd7k^_Fm?(*m5SkCU=Ar`dd8RofC-jgb}+`^`<L8q
z_omTkY^b%`4V_+J)@rqzdcA&4r_;?TmCCAGt$qZ3n&bHS(b3VW%jM$#Ji}x%b)gr5
z34pHk0a+jdSOBtL1)@@^v>*sV)9?3-&<_atZy+oH357zj(AU?u1lbE*1MWkg!~LcH
z{(iED2zX<Ae0)3rnel?~9PB2Lbvm7Ue!q7=5D5Gb#Y^9d#o|X%6hFt~@!d!yvN}9G
zyok6MkVIZXqtTdoo^L>(qI|2>S_U`at`h9!TrMY1CX<{>rAmiFp$g7;2s_nAHLiI)
zo_W->0hwyGjg5^x4hDlB$VUiy>P#lH5sSqd0|Nt8Ukp9YTP&6*kmG<c8ojt}Hk<Dq
zNj@~Judkm^Bof;WhhqjNuD7>0igT&{1Y?p!DwX;Q`SM}-v8+%i)M%z~o&$WGAnbw9
zU^aR5bU&R=(+p3fdcD4czHKAm24Q5xz6-u6ld)N^*V~0Zb7C?awOVZz>;Z5cciwhg
zaJ}g1>7g0;(3f|mQt4T<*`(DyLY+>>5Do`Aa8SRq*v~Bb7v?zb8+5BgBFRlm1U?|Y
z-fp+gVt$g}hqPL)P^D6-LB9o1k7}6R!K@}HCtGs4{3UEk^t6io>8`A-v~gcyXXkYu
zd~13-^B(>JU<K}A2dVx8G8hbJ@Ev`@duSF$K*SEwemwR0e6O&-P1tC6Y4?^9M{oG+
zYaALHQXu{<zK2in%>sTP4N&YApbFd_92}go*=(mFA4|b-IBU1tx6qemnM_7+q}`@|
Ng)w*j*rfm1_!9uNhV}pe

literal 0
HcmV?d00001

diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js
index 954d8ad1b..33e1627ce 100644
--- a/app/assets/javascripts/discourse.js
+++ b/app/assets/javascripts/discourse.js
@@ -85,6 +85,14 @@ Discourse = Ember.Application.createWithMixins({
     }, 200);
   }.observes('title', 'hasFocus', 'notifyCount'),
 
+  faviconChanged: function() {
+    if(Discourse.SiteSettings.dynamic_favicon) {
+      $.faviconNotify(
+        Discourse.SiteSettings.favicon_url, this.get('notifyCount')
+      );
+    }
+  }.observes('notifyCount'),
+
   // The classes of buttons to show on a post
   postButtons: function() {
     return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
diff --git a/app/assets/javascripts/external/jquery.faviconNotify.js b/app/assets/javascripts/external/jquery.faviconNotify.js
new file mode 100644
index 000000000..2b071e30e
--- /dev/null
+++ b/app/assets/javascripts/external/jquery.faviconNotify.js
@@ -0,0 +1,224 @@
+/**
+ * jQuery Favicon Notify
+ *
+ * Updates the favicon to notify the user of changes. In the original tests I
+ * had an embedded font collection to allow any charachers - I decided that the
+ * ~130Kb and added complexity was overkill. As such it now uses a manual glyph
+ * set meaning that only numerical notifications are possible.
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *
+ *		http://www.opensource.org/licenses/mit-license.php
+ *		http://www.gnu.org/licenses/gpl.html
+ *
+ * @author		David King
+ * @copyright	Copyright (c) 2011 +
+ * @url			oodavid.com
+ */
+(function($){
+	var canvas;
+	var bg		= '#000000';
+	var fg		= '#FFFFFF';
+	var pos		= 'br';
+	$.faviconNotify = function(icon, num, myPos, myBg, myFg){
+		// Default the positions
+		myPos	= myPos	|| pos;
+		myFg	= myFg	|| fg;
+		myBg	= myBg	|| bg;
+		// Create a canvas if we need one
+		canvas = canvas || $('<canvas />')[0];
+		if(canvas.getContext){
+			// Load the icon
+			$('<img />').load(function(e){
+				// Load the icon into the canvas
+				canvas.height = canvas.width = 16;
+				var ctx = canvas.getContext('2d');
+				ctx.clearRect(0, 0, canvas.width, canvas.height);
+				ctx.drawImage(this, 0, 0);
+				// We gots num?
+				if(num !== undefined){
+					num = parseFloat(num, 10);
+					// Convert the num into a glyphs array
+					var myGlyphs = [];
+					if(num > 99){
+						myGlyphs.push(glyphs['LOTS']);
+					} else {
+						num = num.toString().split('');
+						$.each(num, function(k,v){
+							myGlyphs.push(glyphs[v]);
+						});
+					}
+					// Merge the glyphs together
+					var combined = [];
+					var glyphHeight = myGlyphs[0].length;
+					$.each(myGlyphs, function(k,v){
+						for(y=0; y<glyphHeight; y++){
+							// First pass?
+							if(combined[y] === undefined) {
+								combined[y] = v[y];
+							} else {
+								// Merge the glyph parts, careful of the boundaries
+								var l = combined[y].length;
+								if(combined[y][(l-1)] == ' '){
+									combined[y] = combined[y].substring(0, (l-1)) + v[y];
+								} else {
+									combined[y] += v[y].substring(1);
+								}
+							}
+						}
+					});
+					// Figure out our starting position
+					var glyphWidth = combined[0].length;
+					var x = (myPos.indexOf('l') != -1) ? 0 : (16 - glyphWidth);
+					var y = (myPos.indexOf('t') != -1) ? 0 : (16 - glyphHeight);
+					// Draw them pixels!
+					for(dX=0; dX<glyphWidth; dX++){
+						for(dY=0; dY<glyphHeight; dY++){
+							var pixel = combined[dY][dX];
+							if(pixel != ' '){
+								ctx.fillStyle = (pixel == '@') ? myFg : myBg;
+								ctx.fillRect((x+dX), (y+dY), 1, 1);
+							}
+						}
+					}
+				}
+				// Update the favicon
+				$('link[rel$=icon]').remove();
+				$('head').append($('<link rel="shortcut icon" type="image/x-icon"/>').attr('href', canvas.toDataURL('image/png')));
+			}).attr('src', icon)
+		}
+	};
+	var glyphs	= {
+		'0': [
+			'  ---  ',
+			' -@@@- ',
+			'-@---@-',
+			'-@- -@-',
+			'-@- -@-',
+			'-@- -@-',
+			'-@---@-',
+			' -@@@- ',
+			'  ---  ' ],
+		'1': [
+			'  -  ',
+			' -@- ',
+			'-@@- ',
+			' -@- ',
+			' -@- ',
+			' -@- ',
+			' -@- ',
+			'-@@@-',
+			' --- ' ],
+		'2': [
+			'  ---  ',
+			' -@@@- ',
+			'-@---@-',
+			' - --@-',
+			'  -@@- ',
+			' -@--  ',
+			'-@---- ',
+			'-@@@@@-',
+			' ----- ' ],
+		'3': [
+			'  ---  ',
+			' -@@@- ',
+			'-@---@-',
+			' - --@-',
+			'  -@@- ',
+			' - --@-',
+			'-@---@-',
+			' -@@@- ',
+			'  ---  ' ],
+		'4': [
+			'    -- ',
+			'   -@@-',
+			'  -@-@-',
+			' -@--@-',
+			'-@---@-',
+			'-@@@@@-',
+			' ----@-',
+			'    -@-',
+			'     - ' ],
+		'5': [
+			' ----- ',
+			'-@@@@@-',
+			'-@---- ',
+			'-@---  ',
+			'-@@@@- ',
+			' ----@-',
+			'-@---@-',
+			' -@@@- ',
+			'  ---  ' ],
+		'6': [
+			'  ---  ',
+			' -@@@- ',
+			'-@---@-',
+			'-@---- ',
+			'-@@@@- ',
+			'-@---@-',
+			'-@---@-',
+			' -@@@- ',
+			'  ---  ' ],
+		'7': [
+			' ----- ',
+			'-@@@@@-',
+			' ----@-',
+			'   -@- ',
+			'   -@- ',
+			'  -@-  ',
+			'  -@-  ',
+			'  -@-  ',
+			'   -   ' ],
+		'8': [
+			'  ---  ',
+			' -@@@- ',
+			'-@---@-',
+			'-@---@-',
+			' -@@@- ',
+			'-@---@-',
+			'-@---@-',
+			' -@@@- ',
+			'  ---  ' ],
+		'9': [
+			'  ---  ',
+			' -@@@- ',
+			'-@---@-',
+			'-@---@-',
+			' -@@@@-',
+			' ----@-',
+			'-@---@-',
+			' -@@@- ',
+			'  ---  ' ],
+		'!': [
+			' - ',
+			'-@-',
+			'-@-',
+			'-@-',
+			'-@-',
+			'-@-',
+			' - ',
+			'-@-',
+			' - ' ],
+		'.': [
+			'   ',
+			'   ',
+			'   ',
+			'   ',
+			'   ',
+			'   ',
+			' - ',
+			'-@-',
+			' - ' ],
+		'LOTS': [
+			' -   -- ---  -- ',
+			'-@- -@@-@@@--@@-',
+			'-@--@--@-@--@-  ',
+			'-@--@--@-@--@-  ',
+			'-@--@--@-@- -@- ',
+			'-@--@--@-@-  -@-',
+			'-@--@--@-@----@-',
+			'-@@@-@@--@-@@@- ',
+			' --- --  - ---  '
+		]
+	};
+})(jQuery);
diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb
index e5b99a922..93fee5b34 100644
--- a/app/models/site_setting.rb
+++ b/app/models/site_setting.rb
@@ -79,7 +79,8 @@ class SiteSetting < ActiveRecord::Base
   setting(:invite_expiry_days, 14)
   setting(:active_user_rate_limit_secs, 60)
   setting(:previous_visit_timeout_hours, 1)
-  setting(:favicon_url, '/assets/default-favicon.png')
+  client_setting(:favicon_url, '/assets/default-favicon.ico')
+  client_setting(:dynamic_favicon, false)
   setting(:apple_touch_icon_url, '/assets/default-apple-touch-icon.png')
 
   setting(:ninja_edit_window, 5.minutes.to_i)
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 47f225f6e..9e689872a 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -460,6 +460,7 @@ en:
     logo_url: "The logo for your site eg: http://example.com/logo.png"
     logo_small_url: "The small logo for your site used when scrolling down on topics eg: http://example.com/logo-small.png"
     favicon_url: "A favicon for your site, see http://en.wikipedia.org/wiki/Favicon"
+    dynamic_favicon: "Show incoming message notifications on favicon"
     apple_touch_icon_url: "Icon used for Apple touch devices. Recommended size is 144px by 144px."
 
     notification_email: "The return email address used when sending system emails such as notifying users of lost passwords, new accounts etc"