From 58ea00048599307b5235cab566057582651f2298 Mon Sep 17 00:00:00 2001
From: Evan Prodromou <evan@status.net>
Date: Wed, 13 Jul 2011 16:10:08 -0400
Subject: [PATCH] JavaScript and other changes to make realtime_channel
 management work

---
 plugins/Realtime/RealtimePlugin.php    | 45 ++++++++++++++++++--------
 plugins/Realtime/Realtime_channel.php  | 34 +++++++++++++++++--
 plugins/Realtime/closechannel.php      |  4 +--
 plugins/Realtime/keepalivechannel.php  |  2 +-
 plugins/Realtime/realtimeupdate.js     | 25 ++++++++++++--
 plugins/Realtime/realtimeupdate.min.js |  2 +-
 6 files changed, 88 insertions(+), 24 deletions(-)

diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php
index 5396d6d38e..eca3a8b029 100644
--- a/plugins/Realtime/RealtimePlugin.php
+++ b/plugins/Realtime/RealtimePlugin.php
@@ -93,16 +93,24 @@ class RealtimePlugin extends Plugin
      */
     function onRouterInitialized($m)
     {
-        // Discovery actions
-        $m->connect('main/channel/:channel_key/keepalive',
-                    array('action' => 'keepalivechannel'));
-        $m->connect('main/channel/:channel_key/close',
-                    array('action' => 'closechannel'));
+        $m->connect('main/channel/:channelkey/keepalive',
+                    array('action' => 'keepalivechannel'),
+                    array('channelkey' => '[a-z0-9]{32}'));
+        $m->connect('main/channel/:channelkey/close',
+                    array('action' => 'closechannel'),
+                    array('channelkey' => '[a-z0-9]{32}'));
+        return true;
     }
 
     function onEndShowScripts($action)
     {
-        $timeline = $this->_getTimeline($action);
+        $channel = $this->_getChannel($action);
+
+        if (empty($channel)) {
+            return true;
+        }
+
+        $timeline = $this->_pathToChannel(array($channel->channel_key));
 
         // If there's not a timeline on this page,
         // just return true
@@ -137,12 +145,14 @@ class RealtimePlugin extends Plugin
         }
         else {
             $pluginPath = common_path('plugins/Realtime/');
-            $realtimeUI = ' RealtimeUpdate.initActions("'.$url.'", "'.$timeline.'", "'. $pluginPath .'");';
+            $keepalive = common_local_url('keepalivechannel', array('channelkey' => $channel->channel_key));
+            $close = common_local_url('closechannel', array('channelkey' => $channel->channel_key));
+            $realtimeUI = ' RealtimeUpdate.initActions("'.$url.'", "'.$timeline.'", "'. $pluginPath .'", "'.$keepalive.'", "'.$close.'"); ';
         }
 
         $script = ' $(document).ready(function() { '.
           $realtimeUI.
-          $this->_updateInitialize($timeline, $user_id).
+            $this->_updateInitialize($timeline, $user_id).
           '}); ';
         $action->inlineScript($script);
 
@@ -431,7 +441,18 @@ class RealtimePlugin extends Plugin
         return '';
     }
 
+
     function _getTimeline($action)
+    {
+        $channel = $this->_getChannel($action);
+        if (empty($channel)) {
+            return null;
+        }
+
+        return $this->_pathToChannel(array($channel->channel_key));
+    }
+
+    function _getChannel($action)
     {
         $timeline = null;
 		$arg1     = null;
@@ -480,12 +501,8 @@ class RealtimePlugin extends Plugin
 												$action_name,
 												$arg1,
 												$arg2);
-		
-		if (!empty($channel)) {
-			$timeline = $this->_pathToChannel(array($channel->channel_key));
-		}
-				
-        return $timeline;
+
+        return $channel;
     }
     
     function onStartReadWriteTables(&$alwaysRW, &$rwdb)
diff --git a/plugins/Realtime/Realtime_channel.php b/plugins/Realtime/Realtime_channel.php
index 9b38ae9970..679ff1273a 100644
--- a/plugins/Realtime/Realtime_channel.php
+++ b/plugins/Realtime/Realtime_channel.php
@@ -58,6 +58,7 @@ class Realtime_channel extends Managed_DataObject
     public $arg1;          // argument
     public $arg2;          // argument, usually null
     public $channel_key;   // 128-bit shared secret key
+    public $audience;      // listener count
     public $created;	   // created date
     public $modified;      // modified date
 
@@ -117,6 +118,10 @@ class Realtime_channel extends Managed_DataObject
 			    			   'length' => 32,
 			    			   'not null' => true,
 			    			   'description' => 'shared secret key for this channel'),
+			    'audience' => array('type' => 'integer',
+                                    'not null' => true,
+                                    'default' => 0,
+                                    'description' => 'reference count'),
                 'created' => array('type' => 'datetime',
 				   				   'not null' => true,
 				   				   'description' => 'date this record was created'),
@@ -144,6 +149,7 @@ class Realtime_channel extends Managed_DataObject
     	$channel->action  = $action;
     	$channel->arg1    = $arg1;
     	$channel->arg2    = $arg2;
+    	$channel->audience  = 1;
     	
     	$channel->channel_key = common_good_rand(16); // 128-bit key, 32 hex chars
     	
@@ -230,18 +236,40 @@ class Realtime_channel extends Managed_DataObject
     	}
     	
     	if ($channel->find(true)) {
-            $channel->touch();
+            $channel->increment();
     		return $channel;
     	} else {
     		return null;
     	}
     }
 
+    function increment()
+    {
+        // XXX: race
+        $orig = clone($this);
+        $this->audience++;
+        $this->modified = common_sql_now();
+        $this->update($orig);
+    }
+
     function touch()
     {
-   		// Touch it!
+        // XXX: race
         $orig = clone($this);
         $this->modified = common_sql_now();
         $this->update($orig);
     }
-}
\ No newline at end of file
+
+    function decrement()
+    {
+        // XXX: race
+        if ($this->audience == 1) {
+            $this->delete();
+        } else {
+            $orig = clone($this);
+            $this->audience--;
+            $this->modified = common_sql_now();
+            $this->update($orig);
+        }
+    }
+}
diff --git a/plugins/Realtime/closechannel.php b/plugins/Realtime/closechannel.php
index f07e24b297..63c616e5db 100644
--- a/plugins/Realtime/closechannel.php
+++ b/plugins/Realtime/closechannel.php
@@ -66,7 +66,7 @@ class ClosechannelAction extends Action
             throw new ClientException(_m('You have to POST it.'));
         }
 
-        $this->channelKey = $this->trimmed('channel_key');
+        $this->channelKey = $this->trimmed('channelkey');
 
         if (empty($this->channelKey)) {
             throw new ClientException(_m('No channel key argument.'));
@@ -91,7 +91,7 @@ class ClosechannelAction extends Action
 
     function handle($argarray=null)
     {
-        $this->channel->delete();
+        $this->channel->decrement();
 
         header('HTTP/1.1 204 No Content');
 
diff --git a/plugins/Realtime/keepalivechannel.php b/plugins/Realtime/keepalivechannel.php
index dc1bfb6d24..152595d76c 100644
--- a/plugins/Realtime/keepalivechannel.php
+++ b/plugins/Realtime/keepalivechannel.php
@@ -66,7 +66,7 @@ class KeepalivechannelAction extends Action
             throw new ClientException(_m('You have to POST it.'));
         }
 
-        $this->channelKey = $this->trimmed('channel_key');
+        $this->channelKey = $this->trimmed('channelkey');
 
         if (empty($this->channelKey)) {
             throw new ClientException(_m('No channel key argument.'));
diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js
index d9f5e7604f..ab7d529f65 100644
--- a/plugins/Realtime/realtimeupdate.js
+++ b/plugins/Realtime/realtimeupdate.js
@@ -1,6 +1,6 @@
 /*
  * StatusNet - a distributed open-source microblogging tool
- * Copyright (C) 2008, StatusNet, Inc.
+ * Copyright (C) 2009-2011, StatusNet, Inc.
  *
  * Add a notice encoded as JSON into the current timeline
  *
@@ -21,7 +21,7 @@
  * @package   StatusNet
  * @author    Evan Prodromou <evan@status.net>
  * @author    Sarven Capadisli <csarven@status.net>
- * @copyright 2009 StatusNet, Inc.
+ * @copyright 2009-2011 StatusNet, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link      http://status.net/
  */
@@ -45,6 +45,8 @@
 RealtimeUpdate = {
      _userid: 0,
      _showurl: '',
+     _keepaliveurl: '',
+     _closeurl: '',
      _updatecounter: 0,
      _maxnotices: 50,
      _windowhasfocus: true,
@@ -390,11 +392,28 @@ RealtimeUpdate = {
       *
       * @access private
       */
-     initActions: function(url, timeline, path)
+    initActions: function(url, timeline, path, keepaliveurl, closeurl)
      {
         $('#notices_primary').prepend('<ul id="realtime_actions"><li id="realtime_playpause"></li><li id="realtime_timeline"></li></ul>');
 
         RealtimeUpdate._pluginPath = path;
+        RealtimeUpdate._keepaliveurl = keepaliveurl;
+        RealtimeUpdate._closeurl = closeurl;
+
+
+	 // On unload, let the server know we're no longer listening
+         $(window).unload(function() {
+            $.ajax({
+                type: 'POST',
+                url: RealtimeUpdate._closeurl});
+	 });
+
+	setInterval(function() {
+            $.ajax({
+                type: 'POST',
+                url: RealtimeUpdate._keepaliveurl});
+	    
+	}, 15 * 60 * 1000 ); // every 15 min; timeout in 30 min
 
         RealtimeUpdate.initPlayPause();
         RealtimeUpdate.initAddPopup(url, timeline, RealtimeUpdate._pluginPath);
diff --git a/plugins/Realtime/realtimeupdate.min.js b/plugins/Realtime/realtimeupdate.min.js
index a7453f3a16..ad3fb97a76 100644
--- a/plugins/Realtime/realtimeupdate.min.js
+++ b/plugins/Realtime/realtimeupdate.min.js
@@ -1 +1 @@
-RealtimeUpdate={_userid:0,_showurl:"",_updatecounter:0,_maxnotices:50,_windowhasfocus:true,_documenttitle:"",_paused:false,_queuedNotices:[],init:function(a,b){RealtimeUpdate._userid=a;RealtimeUpdate._showurl=b;RealtimeUpdate._documenttitle=document.title;$(window).bind("focus",function(){RealtimeUpdate._windowhasfocus=true;RealtimeUpdate._updatecounter=0;RealtimeUpdate.removeWindowCounter()});$(window).bind("blur",function(){$("#notices_primary .notice").removeClass("mark-top");$("#notices_primary .notice:first").addClass("mark-top");RealtimeUpdate._windowhasfocus=false;return false})},receive:function(a){if(RealtimeUpdate.isNoticeVisible(a.id)){return}if(RealtimeUpdate._paused===false){RealtimeUpdate.purgeLastNoticeItem();RealtimeUpdate.insertNoticeItem(a)}else{RealtimeUpdate._queuedNotices.push(a);RealtimeUpdate.updateQueuedCounter()}RealtimeUpdate.updateWindowCounter()},insertNoticeItem:function(a){if(RealtimeUpdate.isNoticeVisible(a.id)){return}RealtimeUpdate.makeNoticeItem(a,function(b){if(RealtimeUpdate.isNoticeVisible(a.id)){return}var c=$(b).attr("id");var d=$("#notices_primary .notices:first");var j=true;var e=d.hasClass("threaded-notices");if(e&&a.in_reply_to_status_id){var g=$("#notice-"+a.in_reply_to_status_id);if(g.length==0){}else{var h=g.closest(".notices");if(h.hasClass("threaded-replies")){g=h.closest(".notice")}d=g.find(".threaded-replies");if(d.length==0){d=$('<ul class="notices threaded-replies xoxo"></ul>');g.append(d);SN.U.NoticeInlineReplyPlaceholder(g)}j=false}}var i=$(b);if(j){d.prepend(i)}else{var f=d.find("li.notice-reply-placeholder");if(f.length>0){i.insertBefore(f)}else{i.appendTo(d)}}i.css({display:"none"}).fadeIn(1000);SN.U.NoticeReplyTo($("#"+c));SN.U.NoticeWithAttachment($("#"+c))})},isNoticeVisible:function(a){return($("#notice-"+a).length>0)},purgeLastNoticeItem:function(){if($("#notices_primary .notice").length>RealtimeUpdate._maxnotices){$("#notices_primary .notice:last").remove()}},updateWindowCounter:function(){if(RealtimeUpdate._windowhasfocus===false){RealtimeUpdate._updatecounter+=1;document.title="("+RealtimeUpdate._updatecounter+") "+RealtimeUpdate._documenttitle}},removeWindowCounter:function(){document.title=RealtimeUpdate._documenttitle},makeNoticeItem:function(b,c){var a=RealtimeUpdate._showurl.replace("0000000000",b.id);$.get(a,{ajax:1},function(f,h,g){var e=$("li.notice:first",f);if(e.length){var d=document._importNode(e[0],true);c(d)}})},makeFavoriteForm:function(c,b){var a;a='<form id="favor-'+c+'" class="form_favor" method="post" action="'+RealtimeUpdate._favorurl+'"><fieldset><legend>Favor this notice</legend><input name="token-'+c+'" type="hidden" id="token-'+c+'" value="'+b+'"/><input name="notice" type="hidden" id="notice-n'+c+'" value="'+c+'"/><input type="submit" id="favor-submit-'+c+'" name="favor-submit-'+c+'" class="submit" value="Favor" title="Favor this notice"/></fieldset></form>';return a},makeReplyLink:function(c,a){var b;b='<a class="notice_reply" href="'+RealtimeUpdate._replyurl+"?replyto="+a+'" title="Reply to this notice">Reply <span class="notice_id">'+c+"</span></a>";return b},makeRepeatForm:function(c,b){var a;a='<form id="repeat-'+c+'" class="form_repeat" method="post" action="'+RealtimeUpdate._repeaturl+'"><fieldset><legend>Repeat this notice?</legend><input name="token-'+c+'" type="hidden" id="token-'+c+'" value="'+b+'"/><input name="notice" type="hidden" id="notice-'+c+'" value="'+c+'"/><input type="submit" id="repeat-submit-'+c+'" name="repeat-submit-'+c+'" class="submit" value="Yes" title="Repeat this notice"/></fieldset></form>';return a},makeDeleteLink:function(c){var b,a;a=RealtimeUpdate._deleteurl.replace("0000000000",c);b='<a class="notice_delete" href="'+a+'" title="Delete this notice">Delete</a>';return b},initActions:function(a,b,c){$("#notices_primary").prepend('<ul id="realtime_actions"><li id="realtime_playpause"></li><li id="realtime_timeline"></li></ul>');RealtimeUpdate._pluginPath=c;RealtimeUpdate.initPlayPause();RealtimeUpdate.initAddPopup(a,b,RealtimeUpdate._pluginPath)},initPlayPause:function(){if(typeof(localStorage)=="undefined"){RealtimeUpdate.showPause()}else{if(localStorage.getItem("RealtimeUpdate_paused")==="true"){RealtimeUpdate.showPlay()}else{RealtimeUpdate.showPause()}}},showPause:function(){RealtimeUpdate.setPause(false);RealtimeUpdate.showQueuedNotices();RealtimeUpdate.addNoticesHover();$("#realtime_playpause").remove();$("#realtime_actions").prepend('<li id="realtime_playpause"><button id="realtime_pause" class="pause"></button></li>');$("#realtime_pause").text(SN.msg("realtime_pause")).attr("title",SN.msg("realtime_pause_tooltip")).bind("click",function(){RealtimeUpdate.removeNoticesHover();RealtimeUpdate.showPlay();return false})},showPlay:function(){RealtimeUpdate.setPause(true);$("#realtime_playpause").remove();$("#realtime_actions").prepend('<li id="realtime_playpause"><span id="queued_counter"></span> <button id="realtime_play" class="play"></button></li>');$("#realtime_play").text(SN.msg("realtime_play")).attr("title",SN.msg("realtime_play_tooltip")).bind("click",function(){RealtimeUpdate.showPause();return false})},setPause:function(a){RealtimeUpdate._paused=a;if(typeof(localStorage)!="undefined"){localStorage.setItem("RealtimeUpdate_paused",RealtimeUpdate._paused)}},showQueuedNotices:function(){$.each(RealtimeUpdate._queuedNotices,function(a,b){RealtimeUpdate.insertNoticeItem(b)});RealtimeUpdate._queuedNotices=[];RealtimeUpdate.removeQueuedCounter()},updateQueuedCounter:function(){$("#realtime_playpause #queued_counter").html("("+RealtimeUpdate._queuedNotices.length+")")},removeQueuedCounter:function(){$("#realtime_playpause #queued_counter").empty()},addNoticesHover:function(){$("#notices_primary .notices").hover(function(){if(RealtimeUpdate._paused===false){RealtimeUpdate.showPlay()}},function(){if(RealtimeUpdate._paused===true){RealtimeUpdate.showPause()}})},removeNoticesHover:function(){$("#notices_primary .notices").unbind()},initAddPopup:function(a,b,c){$("#realtime_timeline").append('<button id="realtime_popup"></button>');$("#realtime_popup").text(SN.msg("realtime_popup")).attr("title",SN.msg("realtime_popup_tooltip")).bind("click",function(){window.open(a,"","toolbar=no,resizable=yes,scrollbars=yes,status=no,menubar=no,personalbar=no,location=no,width=500,height=550");return false})},initPopupWindow:function(){$(".notices .entry-title a, .notices .entry-content a").bind("click",function(){window.open(this.href,"");return false});$("#showstream .entity_profile").css({width:"69%"})}};
\ No newline at end of file
+RealtimeUpdate={_userid:0,_showurl:"",_keepaliveurl:"",_closeurl:"",_updatecounter:0,_maxnotices:50,_windowhasfocus:true,_documenttitle:"",_paused:false,_queuedNotices:[],init:function(a,b){RealtimeUpdate._userid=a;RealtimeUpdate._showurl=b;RealtimeUpdate._documenttitle=document.title;$(window).bind("focus",function(){RealtimeUpdate._windowhasfocus=true;RealtimeUpdate._updatecounter=0;RealtimeUpdate.removeWindowCounter()});$(window).bind("blur",function(){$("#notices_primary .notice").removeClass("mark-top");$("#notices_primary .notice:first").addClass("mark-top");RealtimeUpdate._windowhasfocus=false;return false})},receive:function(a){if(RealtimeUpdate.isNoticeVisible(a.id)){return}if(RealtimeUpdate._paused===false){RealtimeUpdate.purgeLastNoticeItem();RealtimeUpdate.insertNoticeItem(a)}else{RealtimeUpdate._queuedNotices.push(a);RealtimeUpdate.updateQueuedCounter()}RealtimeUpdate.updateWindowCounter()},insertNoticeItem:function(a){if(RealtimeUpdate.isNoticeVisible(a.id)){return}RealtimeUpdate.makeNoticeItem(a,function(b){if(RealtimeUpdate.isNoticeVisible(a.id)){return}var c=$(b).attr("id");var d=$("#notices_primary .notices:first");var j=true;var e=d.hasClass("threaded-notices");if(e&&a.in_reply_to_status_id){var g=$("#notice-"+a.in_reply_to_status_id);if(g.length==0){}else{var h=g.closest(".notices");if(h.hasClass("threaded-replies")){g=h.closest(".notice")}d=g.find(".threaded-replies");if(d.length==0){d=$('<ul class="notices threaded-replies xoxo"></ul>');g.append(d);SN.U.NoticeInlineReplyPlaceholder(g)}j=false}}var i=$(b);if(j){d.prepend(i)}else{var f=d.find("li.notice-reply-placeholder");if(f.length>0){i.insertBefore(f)}else{i.appendTo(d)}}i.css({display:"none"}).fadeIn(1000);SN.U.NoticeReplyTo($("#"+c));SN.U.NoticeWithAttachment($("#"+c))})},isNoticeVisible:function(a){return($("#notice-"+a).length>0)},purgeLastNoticeItem:function(){if($("#notices_primary .notice").length>RealtimeUpdate._maxnotices){$("#notices_primary .notice:last").remove()}},updateWindowCounter:function(){if(RealtimeUpdate._windowhasfocus===false){RealtimeUpdate._updatecounter+=1;document.title="("+RealtimeUpdate._updatecounter+") "+RealtimeUpdate._documenttitle}},removeWindowCounter:function(){document.title=RealtimeUpdate._documenttitle},makeNoticeItem:function(b,c){var a=RealtimeUpdate._showurl.replace("0000000000",b.id);$.get(a,{ajax:1},function(f,h,g){var e=$("li.notice:first",f);if(e.length){var d=document._importNode(e[0],true);c(d)}})},makeFavoriteForm:function(c,b){var a;a='<form id="favor-'+c+'" class="form_favor" method="post" action="'+RealtimeUpdate._favorurl+'"><fieldset><legend>Favor this notice</legend><input name="token-'+c+'" type="hidden" id="token-'+c+'" value="'+b+'"/><input name="notice" type="hidden" id="notice-n'+c+'" value="'+c+'"/><input type="submit" id="favor-submit-'+c+'" name="favor-submit-'+c+'" class="submit" value="Favor" title="Favor this notice"/></fieldset></form>';return a},makeReplyLink:function(c,a){var b;b='<a class="notice_reply" href="'+RealtimeUpdate._replyurl+"?replyto="+a+'" title="Reply to this notice">Reply <span class="notice_id">'+c+"</span></a>";return b},makeRepeatForm:function(c,b){var a;a='<form id="repeat-'+c+'" class="form_repeat" method="post" action="'+RealtimeUpdate._repeaturl+'"><fieldset><legend>Repeat this notice?</legend><input name="token-'+c+'" type="hidden" id="token-'+c+'" value="'+b+'"/><input name="notice" type="hidden" id="notice-'+c+'" value="'+c+'"/><input type="submit" id="repeat-submit-'+c+'" name="repeat-submit-'+c+'" class="submit" value="Yes" title="Repeat this notice"/></fieldset></form>';return a},makeDeleteLink:function(c){var b,a;a=RealtimeUpdate._deleteurl.replace("0000000000",c);b='<a class="notice_delete" href="'+a+'" title="Delete this notice">Delete</a>';return b},initActions:function(a,c,d,b,e){$("#notices_primary").prepend('<ul id="realtime_actions"><li id="realtime_playpause"></li><li id="realtime_timeline"></li></ul>');RealtimeUpdate._pluginPath=d;RealtimeUpdate._keepaliveurl=b;RealtimeUpdate._closeurl=e;$(window).unload(function(){$.ajax({type:"POST",url:RealtimeUpdate._closeurl})});setInterval(function(){$.ajax({type:"POST",url:RealtimeUpdate._keepaliveurl})},15*60*1000);RealtimeUpdate.initPlayPause();RealtimeUpdate.initAddPopup(a,c,RealtimeUpdate._pluginPath)},initPlayPause:function(){if(typeof(localStorage)=="undefined"){RealtimeUpdate.showPause()}else{if(localStorage.getItem("RealtimeUpdate_paused")==="true"){RealtimeUpdate.showPlay()}else{RealtimeUpdate.showPause()}}},showPause:function(){RealtimeUpdate.setPause(false);RealtimeUpdate.showQueuedNotices();RealtimeUpdate.addNoticesHover();$("#realtime_playpause").remove();$("#realtime_actions").prepend('<li id="realtime_playpause"><button id="realtime_pause" class="pause"></button></li>');$("#realtime_pause").text(SN.msg("realtime_pause")).attr("title",SN.msg("realtime_pause_tooltip")).bind("click",function(){RealtimeUpdate.removeNoticesHover();RealtimeUpdate.showPlay();return false})},showPlay:function(){RealtimeUpdate.setPause(true);$("#realtime_playpause").remove();$("#realtime_actions").prepend('<li id="realtime_playpause"><span id="queued_counter"></span> <button id="realtime_play" class="play"></button></li>');$("#realtime_play").text(SN.msg("realtime_play")).attr("title",SN.msg("realtime_play_tooltip")).bind("click",function(){RealtimeUpdate.showPause();return false})},setPause:function(a){RealtimeUpdate._paused=a;if(typeof(localStorage)!="undefined"){localStorage.setItem("RealtimeUpdate_paused",RealtimeUpdate._paused)}},showQueuedNotices:function(){$.each(RealtimeUpdate._queuedNotices,function(a,b){RealtimeUpdate.insertNoticeItem(b)});RealtimeUpdate._queuedNotices=[];RealtimeUpdate.removeQueuedCounter()},updateQueuedCounter:function(){$("#realtime_playpause #queued_counter").html("("+RealtimeUpdate._queuedNotices.length+")")},removeQueuedCounter:function(){$("#realtime_playpause #queued_counter").empty()},addNoticesHover:function(){$("#notices_primary .notices").hover(function(){if(RealtimeUpdate._paused===false){RealtimeUpdate.showPlay()}},function(){if(RealtimeUpdate._paused===true){RealtimeUpdate.showPause()}})},removeNoticesHover:function(){$("#notices_primary .notices").unbind()},initAddPopup:function(a,b,c){$("#realtime_timeline").append('<button id="realtime_popup"></button>');$("#realtime_popup").text(SN.msg("realtime_popup")).attr("title",SN.msg("realtime_popup_tooltip")).bind("click",function(){window.open(a,"","toolbar=no,resizable=yes,scrollbars=yes,status=no,menubar=no,personalbar=no,location=no,width=500,height=550");return false})},initPopupWindow:function(){$(".notices .entry-title a, .notices .entry-content a").bind("click",function(){window.open(this.href,"");return false});$("#showstream .entity_profile").css({width:"69%"})}};
\ No newline at end of file
-- 
2.39.5