From 2b0d0b2fe7a72db7c4462268bf1412a3773dc331 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Sat, 16 Feb 2013 19:08:10 +0100 Subject: [PATCH] Add Unread Tab Icons. --- 4chan_x.user.js | 58 ++++++++++++++++++++++++++++++++++++++++--- src/config.coffee | 2 +- src/features.coffee | 60 +++++++++++++++++++++++++++++++++++++++++++-- src/main.coffee | 1 + 4 files changed, 114 insertions(+), 7 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index a64f453af..d03600f20 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -43,7 +43,7 @@ */ (function() { - var $, $$, Anonymize, ArchiveLink, AutoGIF, Board, Build, Clone, Conf, Config, DeleteLink, DownloadLink, FileInfo, Filter, Get, Header, ImageExpand, ImageHover, Main, Menu, Notification, Polyfill, Post, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Recursive, Redirect, RelativeDates, ReplyHiding, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, Time, UI, Unread, d, doc, g, + var $, $$, Anonymize, ArchiveLink, AutoGIF, Board, Build, Clone, Conf, Config, DeleteLink, DownloadLink, Favicon, FileInfo, Filter, Get, Header, ImageExpand, ImageHover, Main, Menu, Notification, Polyfill, Post, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Recursive, Redirect, RelativeDates, ReplyHiding, ReportLink, RevealSpoilers, Sauce, Settings, Thread, ThreadExcerpt, ThreadHiding, ThreadStats, ThreadUpdater, Time, UI, Unread, d, doc, g, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -89,7 +89,7 @@ Monitoring: { 'Thread Updater': [true, 'Fetch and insert new replies. Has more options in its own dialog.'], 'Unread Count': [true, 'Show the unread posts count in the tab title.'], - 'Unread Favicon': [true, 'Show a different favicon when there are unread posts.'], + 'Unread Tab Icon': [true, 'Show a different favicon when there are unread posts.'], 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'], 'Thread Stats': [true, 'Display reply and image count.'], 'Thread Watcher': [true, 'Bookmark threads.'], @@ -3908,7 +3908,7 @@ Unread = { init: function() { - if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Favicon']) { + if (g.VIEW !== 'thread' || !Conf['Unread Count'] && !Conf['Unread Tab Icon']) { return; } $.on(d, 'ThreadUpdate', this.onUpdate); @@ -3976,10 +3976,59 @@ return Unread.update(); }, update: function() { - return d.title = "(" + Unread.posts.length + ") " + Unread.title; + var count; + count = Unread.posts.length; + if (Conf['Unread Count']) { + d.title = "(" + Unread.posts.length + ") " + Unread.title; + } + if (!Conf['Unread Tab Icon']) { + return; + } + Favicon.el.href = g.DEAD ? count ? Favicon.unreadDead : Favicon.dead : count ? Favicon.unread : Favicon["default"]; + return $.add(d.head, Favicon.el); } }; + Favicon = { + init: function() { + return $.ready(function() { + var href; + Favicon.el = $('link[rel="shortcut icon"]', d.head); + Favicon.el.type = 'image/x-icon'; + href = Favicon.el.href; + Favicon.SFW = /ws\.ico$/.test(href); + Favicon["default"] = href; + return Favicon["switch"](); + }); + }, + "switch": function() { + switch (Conf['favicon']) { + case 'ferongr': + Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; + Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; + Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; + break; + case 'xat-': + Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC'; + Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg=='; + Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII='; + break; + case 'Mayhem': + Favicon.unreadDead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg=='; + Favicon.unreadSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC'; + Favicon.unreadNSFW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII='; + break; + case 'Original': + Favicon.unreadDead = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; + Favicon.unreadSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; + Favicon.unreadNSFW = 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; + } + return Favicon.unread = Favicon.SFW ? Favicon.unreadSFW : Favicon.unreadNSFW; + }, + empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', + dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==' + }; + ThreadStats = { init: function() { if (g.VIEW !== 'thread' || !Conf['Thread Stats']) { @@ -5691,6 +5740,7 @@ initFeature('Auto-GIF', AutoGIF); initFeature('Image Hover', ImageHover); initFeature('Thread Excerpt', ThreadExcerpt); + initFeature('Favicon', Favicon); initFeature('Unread', Unread); initFeature('Thread Stats', ThreadStats); initFeature('Thread Updater', ThreadUpdater); diff --git a/src/config.coffee b/src/config.coffee index 97b67aae5..ce99e0512 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -35,7 +35,7 @@ Config = Monitoring: 'Thread Updater': [true, 'Fetch and insert new replies. Has more options in its own dialog.'] 'Unread Count': [true, 'Show the unread posts count in the tab title.'] - 'Unread Favicon': [true, 'Show a different favicon when there are unread posts.'] + 'Unread Tab Icon': [true, 'Show a different favicon when there are unread posts.'] 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'] 'Thread Stats': [true, 'Display reply and image count.'] 'Thread Watcher': [true, 'Bookmark threads.'] diff --git a/src/features.coffee b/src/features.coffee index 55e9904e9..83129a91a 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -2432,7 +2432,7 @@ ThreadExcerpt = Unread = init: -> - return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Favicon'] + return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Tab Icon'] $.on d, 'ThreadUpdate', @onUpdate $.on d, 'QRPostSuccessful', @post $.on d, 'scroll visibilitychange', @read @@ -2480,7 +2480,63 @@ Unread = Unread.update() update: -> - d.title = "(#{Unread.posts.length}) #{Unread.title}" + count = Unread.posts.length + + if Conf['Unread Count'] + d.title = "(#{Unread.posts.length}) #{Unread.title}" + + return unless Conf['Unread Tab Icon'] + + Favicon.el.href = + if g.DEAD + if count + Favicon.unreadDead + else + Favicon.dead + else + if count + Favicon.unread + else + Favicon.default + + # `favicon.href = href` doesn't work on Firefox. + # `favicon.href = href` isn't enough on Opera. + # Opera won't always update the favicon if the href didn't change. + $.add d.head, Favicon.el + +Favicon = + init: -> + $.ready -> + Favicon.el = $ 'link[rel="shortcut icon"]', d.head + Favicon.el.type = 'image/x-icon' + {href} = Favicon.el + Favicon.SFW = /ws\.ico$/.test href + Favicon.default = href + Favicon.switch() + + switch: -> + switch Conf['favicon'] + when 'ferongr' + Favicon.unreadDead = 'data:image/gif;base64,<%= grunt.file.read("img/favicons/ferongr/unreadDead.gif", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadSFW = 'data:image/gif;base64,<%= grunt.file.read("img/favicons/ferongr/unreadSFW.gif", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadNSFW = 'data:image/gif;base64,<%= grunt.file.read("img/favicons/ferongr/unreadNSFW.gif", {encoding: "base64"}).toString("base64") %>' + when 'xat-' + Favicon.unreadDead = 'data:image/png;base64,<%= grunt.file.read("img/favicons/xat-/unreadDead.png", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadSFW = 'data:image/png;base64,<%= grunt.file.read("img/favicons/xat-/unreadSFW.png", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadNSFW = 'data:image/png;base64,<%= grunt.file.read("img/favicons/xat-/unreadNSFW.png", {encoding: "base64"}).toString("base64") %>' + when 'Mayhem' + Favicon.unreadDead = 'data:image/png;base64,<%= grunt.file.read("img/favicons/Mayhem/unreadDead.png", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadSFW = 'data:image/png;base64,<%= grunt.file.read("img/favicons/Mayhem/unreadSFW.png", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadNSFW = 'data:image/png;base64,<%= grunt.file.read("img/favicons/Mayhem/unreadNSFW.png", {encoding: "base64"}).toString("base64") %>' + when 'Original' + Favicon.unreadDead = 'data:image/gif;base64,<%= grunt.file.read("img/favicons/Original/unreadDead.gif", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadSFW = 'data:image/gif;base64,<%= grunt.file.read("img/favicons/Original/unreadSFW.gif", {encoding: "base64"}).toString("base64") %>' + Favicon.unreadNSFW = 'data:image/gif;base64,<%= grunt.file.read("img/favicons/Original/unreadNSFW.gif", {encoding: "base64"}).toString("base64") %>' + Favicon.unread = if Favicon.SFW then Favicon.unreadSFW else Favicon.unreadNSFW + + empty: 'data:image/gif;base64,<%= grunt.file.read("img/favicons/empty.gif", {encoding: "base64"}).toString("base64") %>' + dead: 'data:image/gif;base64,<%= grunt.file.read("img/favicons/dead.gif", {encoding: "base64"}).toString("base64") %>' + ThreadStats = init: -> diff --git a/src/main.coffee b/src/main.coffee index dd39a1665..c9593a0a0 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -334,6 +334,7 @@ Main = initFeature 'Auto-GIF', AutoGIF initFeature 'Image Hover', ImageHover initFeature 'Thread Excerpt', ThreadExcerpt + initFeature 'Favicon', Favicon initFeature 'Unread', Unread initFeature 'Thread Stats', ThreadStats initFeature 'Thread Updater', ThreadUpdater