Merge branch 'realtime' into 1.0.x

This commit is contained in:
Brion Vibber 2011-03-14 13:46:15 -07:00
commit 7f4a9c4145
5 changed files with 104 additions and 149 deletions

View File

@ -78,6 +78,9 @@ class ShownoticeAction extends OwnerDesignAction
function prepare($args) function prepare($args)
{ {
parent::prepare($args); parent::prepare($args);
if ($this->boolean('ajax')) {
StatusNet::setApi(true);
}
$id = $this->arg('notice'); $id = $this->arg('notice');
@ -188,22 +191,26 @@ class ShownoticeAction extends OwnerDesignAction
{ {
parent::handle($args); parent::handle($args);
if ($this->notice->is_local == Notice::REMOTE_OMB) { if ($this->boolean('ajax')) {
if (!empty($this->notice->url)) { $this->showAjax();
$target = $this->notice->url; } else {
} else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) { if ($this->notice->is_local == Notice::REMOTE_OMB) {
// Old OMB posts saved the remote URL only into the URI field. if (!empty($this->notice->url)) {
$target = $this->notice->uri; $target = $this->notice->url;
} else { } else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) {
// Shouldn't happen. // Old OMB posts saved the remote URL only into the URI field.
$target = false; $target = $this->notice->uri;
} } else {
if ($target && $target != $this->selfUrl()) { // Shouldn't happen.
common_redirect($target, 301); $target = false;
return false; }
if ($target && $target != $this->selfUrl()) {
common_redirect($target, 301);
return false;
}
} }
$this->showPage();
} }
$this->showPage();
} }
/** /**
@ -232,6 +239,21 @@ class ShownoticeAction extends OwnerDesignAction
$this->elementEnd('ol'); $this->elementEnd('ol');
} }
function showAjax()
{
header('Content-Type: text/xml;charset=utf-8');
$this->xw->startDocument('1.0', 'UTF-8');
$this->elementStart('html');
$this->elementStart('head');
$this->element('title', null, _('Notice'));
$this->elementEnd('head');
$this->elementStart('body');
$nli = new NoticeListItem($this->notice, $this);
$nli->show();
$this->elementEnd('body');
$this->elementEnd('html');
}
/** /**
* Don't show page notice * Don't show page notice
* *

View File

@ -1,9 +1,12 @@
As of StatusNet 1.0.x, actual formatting of the notices is done server-side,
loaded by AJAX after the real-time notification comes in. This has the drawback
that we may make extra HTTP requests and delay incoming notices a little, but
means that formatting and internationalization is consistent.
== TODO == == TODO ==
* i18n
* Update mark behaviour (on notice send) * Update mark behaviour (on notice send)
* Pause, Send a notice ~ should not update counter * Pause, Send a notice ~ should not update counter
* Pause ~ retain up to 50-100 most recent notices * Pause ~ retain up to 50-100 most recent notices
* Add geo data
* Make it work for Conversation page (perhaps a little tricky) * Make it work for Conversation page (perhaps a little tricky)
* IE is updating the counter in document title all the time (Not sure if this * IE is updating the counter in document title all the time (Not sure if this
is still an issue) is still an issue)

View File

@ -45,9 +45,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
*/ */
class RealtimePlugin extends Plugin class RealtimePlugin extends Plugin
{ {
protected $replyurl = null; protected $showurl = null;
protected $favorurl = null;
protected $deleteurl = null;
/** /**
* When it's time to initialize the plugin, calculate and * When it's time to initialize the plugin, calculate and
@ -56,11 +54,8 @@ class RealtimePlugin extends Plugin
function onInitializePlugin() function onInitializePlugin()
{ {
$this->replyurl = common_local_url('newnotice');
$this->favorurl = common_local_url('favor');
$this->repeaturl = common_local_url('repeat');
// FIXME: need to find a better way to pass this pattern in // FIXME: need to find a better way to pass this pattern in
$this->deleteurl = common_local_url('deletenotice', $this->showurl = common_local_url('shownotice',
array('notice' => '0000000000')); array('notice' => '0000000000'));
return true; return true;
} }
@ -323,7 +318,12 @@ class RealtimePlugin extends Plugin
function _getScripts() function _getScripts()
{ {
return array(Plugin::staticPath('Realtime', 'realtimeupdate.min.js')); if (common_config('site', 'minify')) {
$js = 'realtimeupdate.min.js';
} else {
$js = 'realtimeupdate.js';
}
return array(Plugin::staticPath('Realtime', $js));
} }
/** /**
@ -354,7 +354,7 @@ class RealtimePlugin extends Plugin
function _updateInitialize($timeline, $user_id) function _updateInitialize($timeline, $user_id)
{ {
return "RealtimeUpdate.init($user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->repeaturl\", \"$this->deleteurl\"); "; return "RealtimeUpdate.init($user_id, \"$this->showurl\"); ";
} }
function _connect() function _connect()

View File

@ -44,10 +44,7 @@
*/ */
RealtimeUpdate = { RealtimeUpdate = {
_userid: 0, _userid: 0,
_replyurl: '', _showurl: '',
_favorurl: '',
_repeaturl: '',
_deleteurl: '',
_updatecounter: 0, _updatecounter: 0,
_maxnotices: 50, _maxnotices: 50,
_windowhasfocus: true, _windowhasfocus: true,
@ -66,21 +63,15 @@ RealtimeUpdate = {
* feed data into the RealtimeUpdate object! * feed data into the RealtimeUpdate object!
* *
* @param {int} userid: local profile ID of the currently logged-in user * @param {int} userid: local profile ID of the currently logged-in user
* @param {String} replyurl: URL for newnotice action, used when generating reply buttons * @param {String} showurl: URL for shownotice action, used when fetching formatting notices.
* @param {String} favorurl: URL for favor action, used when generating fave buttons
* @param {String} repeaturl: URL for repeat action, used when generating repeat buttons
* @param {String} deleteurl: URL template for deletenotice action, used when generating delete buttons.
* This URL contains a stub value of 0000000000 which will be replaced with the notice ID. * This URL contains a stub value of 0000000000 which will be replaced with the notice ID.
* *
* @access public * @access public
*/ */
init: function(userid, replyurl, favorurl, repeaturl, deleteurl) init: function(userid, showurl)
{ {
RealtimeUpdate._userid = userid; RealtimeUpdate._userid = userid;
RealtimeUpdate._replyurl = replyurl; RealtimeUpdate._showurl = showurl;
RealtimeUpdate._favorurl = favorurl;
RealtimeUpdate._repeaturl = repeaturl;
RealtimeUpdate._deleteurl = deleteurl;
RealtimeUpdate._documenttitle = document.title; RealtimeUpdate._documenttitle = document.title;
@ -163,50 +154,51 @@ RealtimeUpdate = {
return; return;
} }
var noticeItem = RealtimeUpdate.makeNoticeItem(data); RealtimeUpdate.makeNoticeItem(data, function(noticeItem) {
var noticeItemID = $(noticeItem).attr('id'); var noticeItemID = $(noticeItem).attr('id');
var list = $("#notices_primary .notices:first") var list = $("#notices_primary .notices:first")
var prepend = true; var prepend = true;
var threaded = list.hasClass('threaded-notices'); var threaded = list.hasClass('threaded-notices');
if (threaded && data.in_reply_to_status_id) { if (threaded && data.in_reply_to_status_id) {
// aho! // aho!
var parent = $('#notice-' + data.in_reply_to_status_id); var parent = $('#notice-' + data.in_reply_to_status_id);
if (parent.length == 0) { if (parent.length == 0) {
// @todo fetch the original, insert it, and finish the rest // @todo fetch the original, insert it, and finish the rest
} else { } else {
// Check the parent notice to make sure it's not a reply itself. // Check the parent notice to make sure it's not a reply itself.
// If so, use it's parent as the parent. // If so, use it's parent as the parent.
var parentList = parent.closest('.notices'); var parentList = parent.closest('.notices');
if (parentList.hasClass('threaded-replies')) { if (parentList.hasClass('threaded-replies')) {
parent = parentList.closest('.notice'); parent = parentList.closest('.notice');
}
list = parent.find('.threaded-replies');
if (list.length == 0) {
list = $('<ul class="notices threaded-replies xoxo"></ul>');
parent.append(list);
}
prepend = false;
} }
list = parent.find('.threaded-replies');
if (list.length == 0) {
list = $('<ul class="notices threaded-replies xoxo"></ul>');
parent.append(list);
}
prepend = false;
} }
}
var newNotice = $(noticeItem); var newNotice = $(noticeItem);
if (prepend) { if (prepend) {
list.prepend(newNotice); list.prepend(newNotice);
} else {
var placeholder = list.find('li.notice-reply-placeholder')
if (placeholder.length > 0) {
newNotice.insertBefore(placeholder)
} else { } else {
newNotice.appendTo(list); var placeholder = list.find('li.notice-reply-placeholder')
SN.U.NoticeInlineReplyPlaceholder(parent); if (placeholder.length > 0) {
newNotice.insertBefore(placeholder)
} else {
newNotice.appendTo(list);
SN.U.NoticeInlineReplyPlaceholder(parent);
}
} }
} newNotice.css({display:"none"}).fadeIn(1000);
newNotice.css({display:"none"}).fadeIn(1000);
SN.U.NoticeReplyTo($('#'+noticeItemID)); SN.U.NoticeReplyTo($('#'+noticeItemID));
SN.U.NoticeWithAttachment($('#'+noticeItemID)); SN.U.NoticeWithAttachment($('#'+noticeItemID));
});
}, },
/** /**
@ -263,86 +255,24 @@ RealtimeUpdate = {
}, },
/** /**
* Builds a notice HTML block from JSON API-style data. * Builds a notice HTML block from JSON API-style data;
* loads data from server, so runs async.
* *
* @param {Object} data: extended JSON API-formatted notice * @param {Object} data: extended JSON API-formatted notice
* @return {String} HTML fragment * @param {function} callback: function(DOMNode) to receive new code
*
* @fixme this replicates core StatusNet code, making maintenance harder
* @fixme sloppy HTML building (raw concat without escaping)
* @fixme no i18n support
* @fixme local variables pollute global namespace
* *
* @access private * @access private
*/ */
makeNoticeItem: function(data) makeNoticeItem: function(data, callback)
{ {
if (data.hasOwnProperty('retweeted_status')) { var url = RealtimeUpdate._showurl.replace('0000000000', data.id);
original = data['retweeted_status']; $.get(url, {ajax: 1}, function(data, textStatus, xhr) {
repeat = data; var notice = $('li.notice:first', data);
data = original; if (notice.length) {
unique = repeat['id']; var node = document._importNode(notice[0], true);
responsible = repeat['user']; callback(node);
} else { }
original = null; });
repeat = null;
unique = data['id'];
responsible = data['user'];
}
user = data['user'];
html = data['html'].replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&amp;/g,'&');
source = data['source'].replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/&amp;/g,'&');
ni = "<li class=\"hentry notice\" id=\"notice-"+unique+"\">"+
"<div class=\"entry-title\">"+
"<span class=\"vcard author\">"+
"<a href=\""+user['profile_url']+"\" class=\"url\" title=\""+user['name']+"\">"+
"<img src=\""+user['profile_image_url']+"\" class=\"avatar photo\" width=\"48\" height=\"48\" alt=\""+user['screen_name']+"\"/>"+
"<span class=\"nickname fn\">"+user['screen_name']+"</span>"+
"</a>"+
"</span>"+
"<p class=\"entry-content\">"+html+"</p>"+
"</div>"+
"<div class=\"entry-content\">"+
"<a class=\"timestamp\" rel=\"bookmark\" href=\""+data['url']+"\" >"+
"<abbr class=\"published\" title=\""+data['created_at']+"\">a few seconds ago</abbr>"+
"</a> "+
"<span class=\"source\">"+
"from "+
"<span class=\"device\">"+source+"</span>"+ // may have a link
"</span>";
if (data['conversation_url']) {
ni = ni+" <a class=\"response\" href=\""+data['conversation_url']+"\">in context</a>";
}
if (repeat) {
ru = repeat['user'];
ni = ni + "<span class=\"repeat vcard\">Repeated by " +
"<a href=\"" + ru['profile_url'] + "\" class=\"url\">" +
"<span class=\"nickname\">"+ ru['screen_name'] + "</span></a></span>";
}
ni = ni+"</div>";
ni = ni + "<div class=\"notice-options\">";
if (RealtimeUpdate._userid != 0) {
var input = $("form#form_notice fieldset input#token");
var session_key = input.val();
ni = ni+RealtimeUpdate.makeFavoriteForm(data['id'], session_key);
ni = ni+RealtimeUpdate.makeReplyLink(data['id'], data['user']['screen_name']);
if (RealtimeUpdate._userid == responsible['id']) {
ni = ni+RealtimeUpdate.makeDeleteLink(data['id']);
} else if (RealtimeUpdate._userid != user['id']) {
ni = ni+RealtimeUpdate.makeRepeatForm(data['id'], session_key);
}
}
ni = ni+"</div>";
ni = ni+"</li>";
return ni;
}, },
/** /**

File diff suppressed because one or more lines are too long