diff --git a/actions/showstream.php b/actions/showstream.php index 4d3067eed3..89285b13c7 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -380,8 +380,13 @@ class ShowstreamAction extends ProfileAction $this->showEmptyListMessage(); } + $args = array('nickname' => $this->user->nickname); + if (!empty($this->tag)) + { + $args['tag'] = $this->tag; + } $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page, - 'showstream', array('nickname' => $this->user->nickname)); + 'showstream', $args); } function showAnonymousMessage() diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php index edee239a03..5e2867ea81 100644 --- a/actions/twitapistatuses.php +++ b/actions/twitapistatuses.php @@ -136,6 +136,11 @@ class TwitapistatusesAction extends TwitterapiAction } + function home_timeline($args, $apidata) + { + call_user_func(array($this, 'friends_timeline'), $args, $apidata); + } + function user_timeline($args, $apidata) { parent::handle($args); diff --git a/classes/User.php b/classes/User.php index 14d3cf54fa..8386f1e185 100644 --- a/classes/User.php +++ b/classes/User.php @@ -120,11 +120,15 @@ class User extends Memcached_DataObject function allowed_nickname($nickname) { // XXX: should already be validated for size, content, etc. - static $blacklist = array('rss', 'xrds', 'doc', 'main', - 'settings', 'notice', 'user', - 'search', 'avatar', 'tag', 'tags', - 'api', 'message', 'group', 'groups', - 'local'); + + $blacklist = array(); + + //all directory and file names should be blacklisted + $d = dir(INSTALLDIR); + while (false !== ($entry = $d->read())) { + $blacklist[]=$entry; + } + $d->close(); $merged = array_merge($blacklist, common_config('nickname', 'blacklist')); return !in_array($nickname, $merged); } diff --git a/doc-src/im b/doc-src/im index c722a4e2cb..631f6d9bb7 100644 --- a/doc-src/im +++ b/doc-src/im @@ -37,10 +37,10 @@ currently-implemented commands: * **help**: Show this help. List available Jabber/XMPP commands * **follow <nickname>**: Subscribe to <nickname> * **sub <nickname>**: Same as follow -* **leave <nickname>**: Subscribe to <nickname> +* **leave <nickname>**: Unsubscribe from <nickname> * **unsub <nickname>**: Same as leave * **d <nickname> <text>**: Send direct message to <nickname> with message body <text> * **get <nickname>**: Get last notice from <nickname> * **last <nickname>**: Same as 'get' * **whois <nickname>**: Get Profile info on <nickname> -* **fav <nickname>**: Add user's last notice as a favorite \ No newline at end of file +* **fav <nickname>**: Add user's last notice as a favorite diff --git a/lib/designsettings.php b/lib/designsettings.php index fdc05562e0..820d534f23 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -325,7 +325,6 @@ class DesignSettingsAction extends AccountSettingsAction parent::showScripts(); $this->script('js/farbtastic/farbtastic.js'); - $this->script('js/farbtastic/farbtastic.go.js'); $this->script('js/userdesign.go.js'); $this->autofocus('design_background-image_file'); diff --git a/lib/noticelist.php b/lib/noticelist.php index 6b2bccd976..d4cd3ff6e5 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -261,7 +261,7 @@ class NoticeListItem extends Widget $attrs = array('href' => $this->profile->profileurl, 'class' => 'url'); if (!empty($this->profile->fullname)) { - $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ') '; + $attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')'; } $this->out->elementStart('a', $attrs); $this->showAvatar(); @@ -418,9 +418,17 @@ class NoticeListItem extends Widget function showContext() { - // XXX: also show context if there are replies to this notice - if (!empty($this->notice->conversation) - && $this->notice->conversation != $this->notice->id) { + $hasConversation = false; + if( !empty($this->notice->conversation) + && $this->notice->conversation != $this->notice->id){ + $hasConversation = true; + }else{ + $conversation = Notice::conversationStream($this->notice->id, 1, 1); + if($conversation->N > 0){ + $hasConversation = true; + } + } + if ($hasConversation){ $convurl = common_local_url('conversation', array('id' => $this->notice->conversation)); $this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id, diff --git a/lib/router.php b/lib/router.php index 0505c99421..5529e60acb 100644 --- a/lib/router.php +++ b/lib/router.php @@ -272,12 +272,12 @@ class Router $m->connect('api/statuses/:method', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); + array('method' => '(public_timeline|home_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); $m->connect('api/statuses/:method/:argument', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); + array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); // users @@ -436,7 +436,7 @@ class Router $m->connect('api/statuses/:method/:argument', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); + array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); $m->connect('api/statusnet/groups/:method/:argument', array('action' => 'api', diff --git a/plugins/Autocomplete/readme.txt b/plugins/Autocomplete/readme.txt index 3272aa1eef..1db4c65658 100644 --- a/plugins/Autocomplete/readme.txt +++ b/plugins/Autocomplete/readme.txt @@ -1,5 +1,7 @@ Autocomplete allows users to autocomplete screen names in @ replies. When an "@" is typed into the notice text area, an autocomplete box is displayed populated with the user's friends' screen names. +Note: This plugin doesn't work if the site is in Private mode, i.e. when $config['site']['private'] is set to true. + Installation ============ Add "addPlugin('Autocomplete');" to the bottom of your config.php diff --git a/plugins/InfiniteScroll/InfiniteScrollPlugin.php b/plugins/InfiniteScroll/InfiniteScrollPlugin.php index c955298cb9..5928c007fe 100644 --- a/plugins/InfiniteScroll/InfiniteScrollPlugin.php +++ b/plugins/InfiniteScroll/InfiniteScrollPlugin.php @@ -40,7 +40,7 @@ class InfiniteScrollPlugin extends Plugin function onEndShowScripts($action) { - $action->script('plugins/InfiniteScroll/jquery.infinitescroll.min.js'); + $action->script('plugins/InfiniteScroll/jquery.infinitescroll.js'); $action->script('plugins/InfiniteScroll/infinitescroll.js'); } } diff --git a/plugins/InfiniteScroll/infinitescroll.js b/plugins/InfiniteScroll/infinitescroll.js index 6513072d06..ae4d53d095 100644 --- a/plugins/InfiniteScroll/infinitescroll.js +++ b/plugins/InfiniteScroll/infinitescroll.js @@ -1,6 +1,7 @@ jQuery(document).ready(function($){ $('notices_primary').infinitescroll({ debug: true, + infiniteScroll : false, nextSelector : "li.nav_next a", loadingImg : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif', text : "Loading the next set of posts...", @@ -12,4 +13,3 @@ jQuery(document).ready(function($){ NoticeAttachments(); }); }); - diff --git a/plugins/InfiniteScroll/jquery.infinitescroll.js b/plugins/InfiniteScroll/jquery.infinitescroll.js index 670686b0e6..ec31bb0863 100644 --- a/plugins/InfiniteScroll/jquery.infinitescroll.js +++ b/plugins/InfiniteScroll/jquery.infinitescroll.js @@ -92,14 +92,14 @@ if (props.isDuringAjax || props.isInvalidPage || props.isDone) return; - if ( !isNearBottom(opts,props) ) return; + if ( opts.infiniteScroll && !isNearBottom(opts,props) ) return; // we dont want to fire the ajax multiple times props.isDuringAjax = true; // show the loading message and hide the previous/next links props.loadingMsg.appendTo( opts.contentSelector ).show(); - $( opts.navSelector ).hide(); + if(opts.infiniteScroll) $( opts.navSelector ).hide(); // increment the URL bit. e.g. /page/3/ props.currPage++; @@ -205,10 +205,19 @@ } }); - // bind scroll handler to element (if its a local scroll) or window - $(opts.localMode ? this : window) - .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } ) - .trigger('scroll.infscr'); // trigger the event, in case it's a short page + if(opts.infiniteScroll){ + // bind scroll handler to element (if its a local scroll) or window + $(opts.localMode ? this : window) + .bind('scroll.infscr', function(){ infscrSetup(path,opts,props,callback); } ) + .trigger('scroll.infscr'); // trigger the event, in case it's a short page + }else{ + $(opts.nextSelector).click( + function(){ + infscrSetup(path,opts,props,callback); + return false; + } + ); + } return this; @@ -222,6 +231,7 @@ $.infinitescroll = { defaults : { debug : false, + infiniteScroll : true, preload : false, nextSelector : "div.navigation a:first", loadingImg : "http://www.infinite-scroll.com/loading.gif", diff --git a/plugins/Meteor/meteorupdater.js b/plugins/Meteor/meteorupdater.js index 2e688336f1..91d12cde9c 100644 --- a/plugins/Meteor/meteorupdater.js +++ b/plugins/Meteor/meteorupdater.js @@ -1,21 +1,37 @@ -// update the local timeline from a Meteor server -// +// Update the local timeline from a Meteor server +// XXX: If @a is subscribed to @b, @a should get @b's notices in @a's Personal timeline. +// Do Replies timeline. var MeteorUpdater = function() { - return { + return { - init: function(server, port, timeline) - { - Meteor.callbacks["process"] = function(data) { - RealtimeUpdate.receive(JSON.parse(data)); - }; + init: function(server, port, timeline) + { + Meteor.callbacks["process"] = function(data) { + var d = JSON.parse(data); - Meteor.host = server; - Meteor.port = port; - Meteor.joinChannel(timeline, 0); - Meteor.connect(); - } - } + var user_url = $('address .url')[0].href+d['user']['screen_name']; + + var wlh = window.location.href; + + if (wlh.indexOf('?') > 0) { + wlh = wlh.slice(0, wlh.indexOf('?')) + } + + if (timeline == 'public' || + user_url+'/all' == wlh || + user_url == wlh) { + + RealtimeUpdate.receive(d); + } + }; + + Meteor.host = server; + Meteor.port = port; + Meteor.joinChannel(timeline, 0); + Meteor.connect(); + } + } }(); diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 82eca3d08c..611b1d86c9 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -63,20 +63,22 @@ class RealtimePlugin extends Plugin { $path = null; - switch ($action->trimmed('action')) { - case 'public': - $path = array('public'); - break; - case 'tag': - $tag = $action->trimmed('tag'); - if (!empty($tag)) { - $path = array('tag', $tag); - } else { + $a = $action->trimmed('action'); + + switch ($a) { + case 'public': case 'all': case 'replies': case 'showstream': + $path = array($a); + break; + case 'tag': + $tag = $action->trimmed('tag'); + if (!empty($tag)) { + $path = array('tag', $tag); + } else { + return true; + } + break; + default: return true; - } - break; - default: - return true; } $timeline = $this->_pathToChannel($path); @@ -95,10 +97,16 @@ class RealtimePlugin extends Plugin $user_id = 0; } + $action->script('plugins/Realtime/jquery.getUrlParam.js'); + $action->elementStart('script', array('type' => 'text/javascript')); - $action->raw("$(document).ready(function() { "); - $action->raw($this->_updateInitialize($timeline, $user_id)); - $action->raw(" });"); + $action->raw(' + + '); $action->elementEnd('script'); return true; @@ -108,11 +116,13 @@ class RealtimePlugin extends Plugin { $paths = array(); - // XXX: Add other timelines; this is just for the public one + // TODO: Replies timeline if ($notice->is_local || ($notice->is_local == 0 && !common_config('public', 'localonly'))) { - $paths[] = array('public'); + foreach (array('public', 'all', 'replies', 'showstream') as $a) { + $paths[] = array($a); + } } $tags = $this->getNoticeTags($notice); diff --git a/plugins/Realtime/icon_external.gif b/plugins/Realtime/icon_external.gif new file mode 100644 index 0000000000..c4118d53bd Binary files /dev/null and b/plugins/Realtime/icon_external.gif differ diff --git a/plugins/Realtime/jquery.getUrlParam.js b/plugins/Realtime/jquery.getUrlParam.js new file mode 100644 index 0000000000..e8f73eb478 --- /dev/null +++ b/plugins/Realtime/jquery.getUrlParam.js @@ -0,0 +1,72 @@ +/* Copyright (c) 2006-2007 Mathias Bank (http://www.mathias-bank.de) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * Version 2.1 + * + * Thanks to + * Hinnerk Ruemenapf - http://hinnerk.ruemenapf.de/ for bug reporting and fixing. + * Tom Leonard for some improvements + * + */ +jQuery.fn.extend({ +/** +* Returns get parameters. +* +* If the desired param does not exist, null will be returned +* +* To get the document params: +* @example value = $(document).getUrlParam("paramName"); +* +* To get the params of a html-attribut (uses src attribute) +* @example value = $('#imgLink').getUrlParam("paramName"); +*/ + getUrlParam: function(strParamName){ + strParamName = escape(unescape(strParamName)); + + var returnVal = new Array(); + var qString = null; + + if ($(this).attr("nodeName")=="#document") { + //document-handler + + if (window.location.search.search(strParamName) > -1 ){ + + qString = window.location.search.substr(1,window.location.search.length).split("&"); + } + + } else if ($(this).attr("src")!="undefined") { + + var strHref = $(this).attr("src") + if ( strHref.indexOf("?") > -1 ){ + var strQueryString = strHref.substr(strHref.indexOf("?")+1); + qString = strQueryString.split("&"); + } + } else if ($(this).attr("href")!="undefined") { + + var strHref = $(this).attr("href") + if ( strHref.indexOf("?") > -1 ){ + var strQueryString = strHref.substr(strHref.indexOf("?")+1); + qString = strQueryString.split("&"); + } + } else { + return null; + } + + + if (qString==null) return null; + + + for (var i=0;i↗'); -RealtimeUpdate = { - _userid: 0, - _replyurl: '', - _favorurl: '', - _deleteurl: '', + $('#realtime_timeline').css({ + 'margin':'2px 0 0 11px', + 'background':'transparent url('+$('address .url')[0].href+'/plugins/Realtime/icon_external.gif) no-repeat 45% 45%', + 'text-indent':'-9999px', + 'width':'16px', + 'height':'16px', + 'padding':'0', + 'display':'block', + 'float':'right', + 'border':'none', + 'cursor':'pointer' + }); - init: function(userid, replyurl, favorurl, deleteurl) - { - RealtimeUpdate._userid = userid; - RealtimeUpdate._replyurl = replyurl; - RealtimeUpdate._favorurl = favorurl; - RealtimeUpdate._deleteurl = deleteurl; - }, + $('#realtime_timeline').click(function() { + window.open($(this).parent('a').attr('href')+'?realtime=1', + $(this).parent('a').attr('title'), + 'toolbar=no,resizable=yes,scrollbars=yes,status=yes'); - receive: function(data) - { - id = data.id; + return false; + }); + } + else { + window.resizeTo(575, 640); + address = $('address'); + content = $('#content'); + $('body').html(address); + $('address').hide(); + $('body').append(content); + $('#content').css({'width':'92%'}); + } - // Don't add it if it already exists - if ($("#notice-"+id).length > 0) { - return; - } + // add a notice encoded as JSON into the current timeline + // + // TODO: i18n - var noticeItem = RealtimeUpdate.makeNoticeItem(data); - $("#notices_primary .notices").prepend(noticeItem, true); - $("#notices_primary .notice:first").css({display:"none"}); - $("#notices_primary .notice:first").fadeIn(1000); - NoticeReply(); - }, + RealtimeUpdate = { + _userid: 0, + _replyurl: '', + _favorurl: '', + _deleteurl: '', - makeNoticeItem: function(data) - { - user = data['user']; - html = data['html'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); - source = data['source'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); + init: function(userid, replyurl, favorurl, deleteurl) + { + RealtimeUpdate._userid = userid; + RealtimeUpdate._replyurl = replyurl; + RealtimeUpdate._favorurl = favorurl; + RealtimeUpdate._deleteurl = deleteurl; + }, - ni = "
  • "+ - "
    "+ - ""+ - ""+ - "\""+user['screen_name']+"\"/"+ - ""+user['screen_name']+""+ - ""+ - ""+ - "

    "+html+"

    "+ - "
    "+ - "
    "+ - ""+ - "a few seconds ago"+ - " "+ - ""+ - "from "+ - "+source+"+ // may have a link - ""; - if (data['in_reply_to_status_id']) { - ni = ni+" in context"; - } + receive: function(data) + { + id = data.id; - ni = ni+"
    "+ - "
    "; + // Don't add it if it already exists - 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 == data['user']['id']) { + if ($("#notice-"+id).length > 0) { + return; + } + + var noticeItem = RealtimeUpdate.makeNoticeItem(data); + $("#notices_primary .notices").prepend(noticeItem, true); + $("#notices_primary .notice:first").css({display:"none"}); + $("#notices_primary .notice:first").fadeIn(1000); + NoticeReply(); + }, + + makeNoticeItem: function(data) + { + user = data['user']; + html = data['html'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); + source = data['source'].replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); + + ni = "
  • "+ + "
    "+ + ""+ + ""+ + "\""+user['screen_name']+"\"/"+ + ""+user['screen_name']+""+ + ""+ + ""+ + "

    "+html+"

    "+ + "
    "+ + "
    "+ + ""+ + "a few seconds ago"+ + " "+ + ""+ + "from "+ + ""+source+""+ // may have a link + ""; + if (data['in_reply_to_status_id']) { + ni = ni+" in context"; + } + + ni = ni+"
    "+ + "
    "; + + 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 == data['user']['id']) { ni = ni+RealtimeUpdate.makeDeleteLink(data['id']); - } - } + } + } - ni = ni+"
    "+ - "
  • "; - return ni; - }, + ni = ni+""+ + ""; + return ni; + }, - makeFavoriteForm: function(id, session_key) - { - var ff; + makeFavoriteForm: function(id, session_key) + { + var ff; - ff = "
    "+ - "
    "+ - "Favor this notice"+ - ""+ - ""+ - ""+ - "
    "+ - "
    "; - return ff; - }, + ff = "
    "+ + "
    "+ + "Favor this notice"+ + ""+ + ""+ + ""+ + "
    "+ + "
    "; + return ff; + }, - makeReplyLink: function(id, nickname) - { - var rl; - rl = "Reply "+id+""; - return rl; - }, + makeReplyLink: function(id, nickname) + { + var rl; + rl = "Reply "+id+""; + return rl; + }, - makeDeleteLink: function(id) - { - var dl, delurl; - delurl = RealtimeUpdate._deleteurl.replace("0000000000", id); + makeDeleteLink: function(id) + { + var dl, delurl; + delurl = RealtimeUpdate._deleteurl.replace("0000000000", id); - dl = "Delete"; + dl = "Delete"; + + return dl; + } + } + +}); - return dl; - }, -} diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README index ce23a26953..b996f96cc3 100644 --- a/plugins/recaptcha/README +++ b/plugins/recaptcha/README @@ -6,7 +6,7 @@ Use: 1. Get an API key from http://recaptcha.net 2. In config.php add: -include_once('plugins/recaptcha.php'); +include_once('plugins/recaptcha/recaptcha.php'); $captcha = new recaptcha(publickey, privatekey, showErrors); Changelog