]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '1.0.x' into people_tags_rebase
authorShashi Gowda <connect2shashi@gmail.com>
Tue, 22 Mar 2011 02:26:25 +0000 (07:56 +0530)
committerShashi Gowda <connect2shashi@gmail.com>
Tue, 22 Mar 2011 02:26:25 +0000 (07:56 +0530)
Conflicts:
EVENTS.txt
actions/peopletag.php
actions/tagother.php
classes/Notice.php
js/util.js
js/util.min.js
lib/accountprofileblock.php
lib/action.php
lib/activityobject.php
lib/command.php
lib/personalgroupnav.php
plugins/OStatus/OStatusPlugin.php

23 files changed:
1  2 
EVENTS.txt
actions/public.php
actions/selftag.php
classes/Notice.php
classes/statusnet.ini
db/core.php
js/util.js
js/util.min.js
lib/accountprofileblock.php
lib/action.php
lib/activityobject.php
lib/command.php
lib/commandinterpreter.php
lib/default.php
lib/framework.php
lib/personalgroupnav.php
lib/publicgroupnav.php
lib/router.php
lib/subgroupnav.php
lib/util.php
plugins/OStatus/OStatusPlugin.php
theme/base/css/display.css
theme/default/css/display.css

diff --combined EVENTS.txt
index 7b154ec3530b34a5d7eb66583d82b1f38fb57e92,54d06655eecdcbb592ac307579bcd25e547e6e34..5fdf7b8b9d2e715d876176e6288f842d1dc3ec5a
@@@ -1116,152 -1116,26 +1116,176 @@@ EndGroupProfileElements: Start showing 
  - $action: action being executed (for output and params)
  - $group: group for the page
  
 +StartShowProfileTagContent: When showing a people tag page
 +- $action: action being executed (for output and params)
 +
 +EndShowProfileTagContent: After showing the contents of a people tag page
 +- $action: action being executed (for output and params)
 +
 +StartShowTaggedProfilesMiniList: at the start of mini list of tagged profiles
 +- $action: action being executed (for output and params)
 +
 +EndShowTaggedProfilesMiniList: at the end of mini list of tagged profiles
 +- $action: action being executed (for output and params)
 +
 +StartShowProfileTagSubscribersMiniList: at the start of mini list of people tag subscribers
 +- $action: action being executed (for output and params)
 +
 +EndShowProfileTagSubscribersMiniList: at the end of mini list of people tag subscribers
 +- $action: action being executed (for output and params)
 +
 +StartTagProfileAction: When starting to show profile tagging page
 +- $action: action being executed (for output and params)
 +- $profile: profile being tagged
 +
 +EndTagProfileAction: After showing profile tagging page
 +- $action: action being executed (for output and params)
 +- $profile: profile being tagged
 +
 +StartProfileCompletionSearch: When starting a profile search for autocompletion
 +- $action: action being executed (for output and params)
 +- &$profile: result Profile objects
 +- $search_engine: the search engine
 +
 +EndProfileCompletionSearch: After search results for profile autocompletion have been found
 +- $action: profilec completion action
 +- &$profile: current result Profile objects
 +- $search_engine: The search engine object
 +
 +StartShowTagProfileForm: When showing people tagging form
 +- $action: action being executed (for output and params)
 +- $profile: profile being tagged
 +
 +EndShowTagProfileForm: After showing people tagging form
 +- $action: action being executed (for output and params)
 +- $profile: profile being tagged
 +
 +StartSavePeopletags: When starting to save people tags
 +- $action: action being executed (for output and params)
 +- $tagstring: string input, a list of tags
 +
 +EndSavePeopletags: After saving people tags
 +- $action: action being executed (for output and params)
 +- $tagstring: string input, a list of tags
 +
 +StartProfiletagGetUri: when generating the Uri for a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$uri: the URI
 +
 +EndProfiletagGetUri: after generating the uri for a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$uri: the URI
 +
 +StartUserPeopletagHomeUrl: when generating the homepage url for a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$url: the URL
 +
 +EndUserPeopletagHomeUrl: after generating the homepage url for a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$url: the URL
 +
 +StartProfiletagPermalink: when generating the permalink url for a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$url: the URL
 +
 +EndProfiletagPermalink: after generating the permalink url for a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$url: the URL
 +
 +StartTagProfile: when tagging a profile
 +- $tagger: profile tagging
 +- $tagged: profile being tagged
 +- $tag: the tag
 +
 +EndTagProfile: after tagging a profile
 +- $newtag: the newly created Profile_tag object
 +
 +StartUntagProfile: when deleting a people tag
 +- $ptag: the Profile_tag object being deleted
 +
 +EndUntagProfile: after deleting a people tag
 +- $orig: a copy of the deleted Profile_tag object
 +
 +StartSubscribePeopletag: when subscribing to a people tag
 +- $peopletag: Profile_list object being subscribed to
 +- $profile: subscriber's profile
 +
 +EndSubscribePeopletag: after subscribing to a people tag
 +- $profile_list: the people tag, a Profile_list object: Profile_list object being subscribed to
 +- $profile: subscriber's profile
 +
 +StartUnsubscribePeopletag: when unsubscribing to a people tag
 +- $profile_list: the people tag, a Profile_list object: Profile_list object being subscribed to
 +- $profile: subscriber's profile
 +
 +EndUnsubscribePeopletag: after unsubscribing to a people tag
 +- $peopletag: Profile_list object being subscribed to
 +- $profile: subscriber's profile
 +
 +StartActivityObjectFromPeopletag: while starting to create an ActivityObject from a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$object: activity object
 +
 +EndActivityObjectFromPeopletag: after making an ActivityObject from a people tag
 +- $profile_list: the people tag, a Profile_list object
 +- &$object: activity object
 +
 +StartPeopletagGroupNav: Showing the people tag nav menu
 +- $menu: the menu widget; use $menu->action for output
 +
 +EndPeopletagGroupNav: after showing the people tag nav menu
 +- $menu: the menu widget; use $menu->action for output
 +
 +StartShowPeopletagItem: when showing a people tag
 +- $widget: PeopletagListItem widget
 +
 +EndShowPeopletagItem: after showing a people tag
 +- $widget: PeopletagListItem widget
 +
 +StartSubscribePeopletagForm: when showing people tag subscription form
 +- $action: action being executed (for output and params)
 +- $peopletag: people tag being subscribed to
 +
 +EndSubscribePeopletagForm: after showing the people tag subscription form
 +- $action: action being executed (for output and params)
 +- $peopletag: people tag being subscribed to
 +
 +StartShowPeopletags: when showing a textual list of people tags
 +- $widget: PeopletagsWidget; use $widget->out for output
 +- $tagger: profile of the tagger
 +- $tagged: profile tagged
 +
 +EndShowPeopletags: after showing a textual list of people tags
 +- $widget: PeopletagsWidget; use $widget->out for output
 +- $tagger: profile of the tagger
 +- $tagged: profile tagged
 +
 +StartProfileListItemTags: when showing people tags in a profile list item widget
 +- $widget: ProfileListItem widget
 +
 +EndProfileListItemTags: after showing people tags in a profile list item widget
 +- $widget: ProfileListItem widget
++
+ StartActivityObjectOutputAtom: Called at start of Atom XML output generation for ActivityObject chunks, just inside the <activity:object>. Cancel the event to take over its output completely (you're responsible for calling the matching End event if so)
+ - $obj: ActivityObject
+ - $out: XMLOutputter to append custom output
+ EndActivityObjectOutputAtom: Called at end of Atom XML output generation for ActivityObject chunks, just inside the </activity:object>
+ - $obj: ActivityObject
+ - $out: XMLOutputter to append custom output
+ StartActivityObjectOutputJson: Called at start of JSON output generation for ActivityObject chunks: the array has not yet been filled out. Cancel the event to take over its output completely (you're responsible for calling the matching End event if so)
+ - $obj ActivityObject
+ - &$out: array to be serialized; you're free to modify it
+ EndActivityObjectOutputJson: Called at end of JSON output generation for ActivityObject chunks: the array has not yet been filled out.
+ - $obj ActivityObject
+ - &$out: array to be serialized; you're free to modify it
+ StartNoticeWhoGets: Called at start of inbox delivery prep; plugins can schedule notices to go to particular profiles that would otherwise not have reached them. Canceling will take over the entire addressing operation. Be aware that output can be cached or used several times, so should remain idempotent.
+ - $notice Notice
+ - &$ni: in/out array mapping profile IDs to constants: NOTICE_INBOX_SOURCE_SUB etc
+ EndNoticeWhoGets: Called at end of inbox delivery prep; plugins can filter out profiles from receiving inbox delivery here.  Be aware that output can be cached or used several times, so should remain idempotent.
+ - $notice Notice
+ - &$ni: in/out array mapping profile IDs to constants: NOTICE_INBOX_SOURCE_SUB etc
diff --combined actions/public.php
index ca9af4601ef4d72fe76e8fa25e67308a35efd811,b0294072504d9c5cca88c16cce6e6b89fca99bf1..d2b0373dc69382aad8951b88cc9f664117e90164
@@@ -51,7 -51,6 +51,6 @@@ define('MAX_PUBLIC_PAGE', 100)
   * @see      PublicrssAction
   * @see      PublicxrdsAction
   */
  class PublicAction extends Action
  {
      /**
       *
       * @return boolean success value
       */
      function prepare($args)
      {
          parent::prepare($args);
          $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
  
          if ($this->page > MAX_PUBLIC_PAGE) {
-             $this->clientError(sprintf(_("Beyond the page limit (%s)."), MAX_PUBLIC_PAGE));
+             // TRANS: Client error displayed when requesting a public timeline page beyond the page limit.
+             // TRANS: %s is the page limit.
+             $this->clientError(sprintf(_('Beyond the page limit (%s).'), MAX_PUBLIC_PAGE));
          }
  
          common_set_returnto($this->selfUrl());
                                         NOTICES_PER_PAGE + 1);
  
          if (!$this->notice) {
+             // TRANS: Server error displayed when a public timeline cannot be retrieved.
              $this->serverError(_('Could not retrieve public stream.'));
              return;
          }
  
          if($this->page > 1 && $this->notice->N == 0){
-             // TRANS: Server error when page not found (404)
+             // TRANS: Server error when page not found (404).
              $this->serverError(_('No such page.'),$code=404);
          }
  
       *
       * @return void
       */
      function handle($args)
      {
          parent::handle($args);
       *
       * @return page title, including page number if over 1
       */
      function title()
      {
          if ($this->page > 1) {
+             // TRANS: Title for all public timeline pages but the first.
+             // TRANS: %d is the page number.
              return sprintf(_('Public timeline, page %d'), $this->page);
          } else {
+             // TRANS: Title for the first public timeline page.
              return _('Public timeline');
          }
      }
       *
       * @return void
       */
      function getFeeds()
      {
          return array(new Feed(Feed::RSS1, common_local_url('publicrss'),
+                               // TRANS: Link description for public timeline feed.
                                _('Public Stream Feed (RSS 1.0)')),
                       new Feed(Feed::RSS2,
                                common_local_url('ApiTimelinePublic',
                                                 array('format' => 'rss')),
+                               // TRANS: Link description for public timeline feed.
                                _('Public Stream Feed (RSS 2.0)')),
                       new Feed(Feed::ATOM,
                                common_local_url('ApiTimelinePublic',
                                                 array('format' => 'atom')),
+                               // TRANS: Link description for public timeline feed.
                                _('Public Stream Feed (Atom)')));
      }
  
-     /**
-      * Show tabset for this page
-      *
-      * Uses the PublicGroupNav widget
-      *
-      * @return void
-      * @see PublicGroupNav
-      */
-     function showLocalNav()
-     {
-         $nav = new PublicGroupNav($this);
-         $nav->show();
-     }
      function showEmptyList()
      {
+         // TRANS: Text displayed for public feed when there are no public notices.
          $message = _('This is the public timeline for %%site.name%% but no one has posted anything yet.') . ' ';
  
          if (common_logged_in()) {
+             // TRANS: Additional text displayed for public feed when there are no public notices for a logged in user.
              $message .= _('Be the first to post!');
          }
          else {
              if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
+                 // TRANS: Additional text displayed for public feed when there are no public notices for a not logged in user.
                  $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
              }
-       }
+         }
  
          $this->elementStart('div', 'guide');
          $this->raw(common_markup_to_html($message));
       *
       * @return void
       */
      function showContent()
      {
          $nl = new ThreadedNoticeList($this->notice, $this);
          $pop->show();
          $gbp = new GroupsByMembersSection($this);
          $gbp->show();
 +        $ptp = new PeopletagsBySubsSection($this);
 +        $ptp->show();
          $feat = new FeaturedUsersSection($this);
          $feat->show();
      }
      function showAnonymousMessage()
      {
          if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
+             // TRANS: Message for not logged in users at an invite-only site trying to view the public feed of notices.
+             // TRANS: This message contains Markdown links. Please mind the formatting.
              $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
                     'based on the Free Software [StatusNet](http://status.net/) tool. ' .
                     '[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ' .
                     '([Read more](%%doc.help%%))');
          } else {
+             // TRANS: Message for not logged in users at a closed site trying to view the public feed of notices.
+             // TRANS: This message contains Markdown links. Please mind the formatting.
              $m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
                     'based on the Free Software [StatusNet](http://status.net/) tool.');
          }
diff --combined actions/selftag.php
index 0efb896f6b5675db24fb0a9cf1c99f4f63680c45,0000000000000000000000000000000000000000..b0b4981f10c237887127f16d63e1c8b813ebb041
mode 100644,000000..100644
--- /dev/null
@@@ -1,204 -1,0 +1,205 @@@
 +<?php
 +/**
 + * StatusNet, the distributed open-source microblogging tool
 + *
 + * Action for showing profiles self-tagged with a given tag
 + *
 + * PHP version 5
 + *
 + * LICENCE: This program is free software: you can redistribute it and/or modify
 + * it under the terms of the GNU Affero General Public License as published by
 + * the Free Software Foundation, either version 3 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU Affero General Public License for more details.
 + *
 + * You should have received a copy of the GNU Affero General Public License
 + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + * @category  Action
 + * @package   StatusNet
 + * @author    Evan Prodromou <evan@status.net>
 + * @author    Zach Copley <zach@status.net>
 + * @copyright 2009 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/
 + */
 +
 +if (!defined('STATUSNET') && !defined('LACONICA')) {
 +    exit(1);
 +}
 +
 +/**
 + * This class outputs a paginated list of profiles self-tagged with a given tag
 + *
 + * @category Output
 + * @package  StatusNet
 + * @author   Evan Prodromou <evan@status.net>
 + * @author   Zach Copley <zach@status.net>
 + * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 + * @link     http://status.net/
 + *
 + * @see      Action
 + */
 +class SelftagAction extends Action
 +{
 +
 +    var $tag  = null;
 +    var $page = null;
 +
 +    /**
 +     * For initializing members of the class.
 +     *
 +     * @param array $argarray misc. arguments
 +     *
 +     * @return boolean true
 +     */
 +    function prepare($argarray)
 +    {
 +        parent::prepare($argarray);
 +
 +        $this->tag = $this->trimmed('tag');
 +
 +        if (!common_valid_profile_tag($this->tag)) {
++            // TRANS: Client error displayed when trying to tag a profile with an invalid tag.
++            // TRANS: %s is the invalid tag.
 +            $this->clientError(sprintf(_('Not a valid people tag: %s.'),
 +                $this->tag));
 +            return;
 +        }
 +
 +        $this->page = ($this->arg('page')) ? $this->arg('page') : 1;
 +
 +        common_set_returnto($this->selfUrl());
 +
 +        return true;
 +    }
 +
 +    /**
 +     * Handler method
 +     *
 +     * @param array $argarray is ignored since it's now passed in in prepare()
 +     *
 +     * @return boolean is read only action?
 +     */
 +    function handle($argarray)
 +    {
 +        parent::handle($argarray);
 +        $this->showPage();
 +    }
 +
 +    /**
 +     * Whips up a query to get a list of profiles based on the provided
 +     * people tag and page, initalizes a ProfileList widget, and displays
 +     * it to the user.
 +     *
 +     * @return nothing
 +     */
 +    function showContent()
 +    {
 +
 +        $profile = new Profile();
 +
 +        $offset = ($this->page - 1) * PROFILES_PER_PAGE;
 +        $limit  = PROFILES_PER_PAGE + 1;
 +
 +        if (common_config('db', 'type') == 'pgsql') {
 +            $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset;
 +        } else {
 +            $lim = ' LIMIT ' . $offset . ', ' . $limit;
 +        }
 +
 +        // XXX: memcached this
 +
 +        $qry =  'SELECT profile.* ' .
 +                'FROM profile JOIN ( profile_tag, profile_list ) ' .
 +                'ON profile.id = profile_tag.tagger ' .
 +                'AND profile_tag.tagger = profile_list.tagger ' .
 +                'AND profile_list.tag = profile_tag.tag ' .
 +                'WHERE profile_tag.tagger = profile_tag.tagged ' .
 +                "AND profile_tag.tag = '%s' ";
 +
 +        $user = common_current_user();
 +        if (empty($user)) {
 +            $qry .= 'AND profile_list.private = false ';
 +        } else {
 +            $qry .= 'AND (profile_list.tagger = ' . $user->id .
 +                    ' OR profile_list.private = false) ';
 +        }
 +
 +        $qry .= 'ORDER BY profile_tag.modified DESC%s';
 +
 +        $profile->query(sprintf($qry, $this->tag, $lim));
 +
 +        $ptl = new SelfTagProfileList($profile, $this); // pass the ammunition
 +        $cnt = $ptl->show();
 +
 +        $this->pagination($this->page > 1,
 +                          $cnt > PROFILES_PER_PAGE,
 +                          $this->page,
 +                          'selftag',
 +                          array('tag' => $this->tag));
 +    }
 +
 +    /**
 +     * Returns the page title
 +     *
 +     * @return string page title
 +     */
 +    function title()
 +    {
 +        return sprintf(_('Users self-tagged with %1$s - page %2$d'),
 +            $this->tag, $this->page);
 +    }
 +
 +}
 +
 +class SelfTagProfileList extends ProfileList
 +{
 +    function newListItem($profile)
 +    {
 +        return new SelfTagProfileListItem($profile, $this->action);
 +    }
 +}
 +
 +class SelfTagProfileListItem extends ProfileListItem
 +{
 +    function linkAttributes()
 +    {
 +        $aAttrs = parent::linkAttributes();
 +
 +        if (common_config('nofollow', 'selftag')) {
 +            $aAttrs['rel'] .= ' nofollow';
 +        }
 +
 +        return $aAttrs;
 +    }
 +
 +    function homepageAttributes()
 +    {
 +        $aAttrs = parent::linkAttributes();
 +
 +        if (common_config('nofollow', 'selftag')) {
 +            $aAttrs['rel'] = 'nofollow';
 +        }
 +
 +        return $aAttrs;
 +    }
 +
 +    function showTags()
 +    {
 +        $selftags = new SelfTagsWidget($this->out, $this->profile, $this->profile);
 +        $selftags->show();
 +
 +        $user = common_current_user();
 +
 +        if (!empty($user) && $user->id != $this->profile->id &&
 +                $user->getProfile()->canTag($this->profile)) {
 +            $yourtags = new PeopleTagsWidget($this->out, $user, $this->profile);
 +            $yourtags->show();
 +        }
 +    }
 +}
diff --combined classes/Notice.php
index 21795ae21b5e27fe261711d91f7e34fde6e7c82f,a158a4930df6f4eb4aa3c1d1401f3933f1845d3b..79b034e76f9bb9055b5365f630b241c29758d03d
@@@ -72,6 -72,7 +72,7 @@@ class Notice extends Memcached_DataObje
      public $location_id;                     // int(4)
      public $location_ns;                     // int(4)
      public $repeat_of;                       // int(4)
+     public $object_type;                     // varchar(255)
  
      /* Static get */
      function staticGet($k,$v=NULL)
       *              array 'urls' list of attached/referred URLs to save with the
       *                           notice in place of extracting links from content
       *              boolean 'distribute' whether to distribute the notice, default true
+      *              string 'object_type' URL of the associated object type (default ActivityObject::NOTE)
       *
       * @fixme tag override
       *
              $notice->rendered = common_render_content($final, $notice);
          }
  
+         if (empty($object_type)) {
+             $notice->object_type = (empty($notice->reply_to)) ? ActivityObject::NOTE : ActivityObject::COMMENT;
+         } else {
+             $notice->object_type = $object_type;
+         }
          if (Event::handle('StartNoticeSave', array(&$notice))) {
  
              // XXX: some of these functions write to the DB
              $notice->saveGroups();
          }
  
 +        if (isset($peopletags)) {
 +            $notice->saveProfileTags($peopletags);
 +        } else {
 +            $notice->saveProfileTags();
 +        }
 +
          if (isset($urls)) {
              $notice->saveKnownUrls($urls);
          } else {
          if ($this->isPublic()) {
              self::blow('public;last');
          }
+         self::blow('fave:by_notice', $this->id);
+         if ($this->conversation) {
+             // In case we're the first, will need to calc a new root.
+             self::blow('notice:conversation_root:%d', $this->conversation);
+         }
      }
  
      /** save all urls in the notice to the db
          return false;
      }
  
+     /**
+      * Grab the earliest notice from this conversation.
+      *
+      * @return Notice or null
+      */
+     function conversationRoot()
+     {
+         if (!empty($this->conversation)) {
+             $c = self::memcache();
+             $key = Cache::key('notice:conversation_root:' . $this->conversation);
+             $notice = $c->get($key);
+             if ($notice) {
+                 return $notice;
+             }
+             $notice = new Notice();
+             $notice->conversation = $this->conversation;
+             $notice->orderBy('CREATED');
+             $notice->limit(1);
+             $notice->find(true);
+             if ($notice->N) {
+                 $c->set($key, $notice);
+                 return $notice;
+             }
+         }
+         return null;
+     }
      /**
       * Pull up a full list of local recipients who will be getting
       * this notice in their inbox. Results will be cached, so don't
          }
  
          $users = $this->getSubscribedUsers();
 +        $ptags = $this->getProfileTags();
  
          // FIXME: kind of ignoring 'transitional'...
          // we'll probably stop supporting inboxless mode
  
          $ni = array();
  
-         foreach ($users as $id) {
-             $ni[$id] = NOTICE_INBOX_SOURCE_SUB;
-         }
+         // Give plugins a chance to add folks in at start...
+         if (Event::handle('StartNoticeWhoGets', array($this, &$ni))) {
  
-         foreach ($groups as $group) {
-             $users = $group->getUserMembers();
              foreach ($users as $id) {
-                 if (!array_key_exists($id, $ni)) {
-                     $ni[$id] = NOTICE_INBOX_SOURCE_GROUP;
+                 $ni[$id] = NOTICE_INBOX_SOURCE_SUB;
+             }
+             foreach ($groups as $group) {
+                 $users = $group->getUserMembers();
+                 foreach ($users as $id) {
+                     if (!array_key_exists($id, $ni)) {
+                         $ni[$id] = NOTICE_INBOX_SOURCE_GROUP;
+                     }
                  }
              }
-         }
  
 -            foreach ($recipients as $recipient) {
 -                if (!array_key_exists($recipient, $ni)) {
 -                    $ni[$recipient] = NOTICE_INBOX_SOURCE_REPLY;
 +        foreach ($ptags as $ptag) {
 +            $users = $ptag->getUserSubscribers();
 +            foreach ($users as $id) {
 +                if (!array_key_exists($id, $ni)) {
 +                    $user = User::staticGet('id', $id);
 +                    if (!$user->hasBlocked($profile)) {
 +                        $ni[$id] = NOTICE_INBOX_SOURCE_PROFILE_TAG;
 +                    }
                  }
              }
-         }
 +        }
 +
 +        foreach ($recipients as $recipient) {
 +            if (!array_key_exists($recipient, $ni)) {
 +                $ni[$recipient] = NOTICE_INBOX_SOURCE_REPLY;
 +            }
  
-         // Exclude any deleted, non-local, or blocking recipients.
-         $profile = $this->getProfile();
-         $originalProfile = null;
-         if ($this->repeat_of) {
-             // Check blocks against the original notice's poster as well.
-             $original = Notice::staticGet('id', $this->repeat_of);
-             if ($original) {
-                 $originalProfile = $original->getProfile();
+             // Exclude any deleted, non-local, or blocking recipients.
+             $profile = $this->getProfile();
+             $originalProfile = null;
+             if ($this->repeat_of) {
+                 // Check blocks against the original notice's poster as well.
+                 $original = Notice::staticGet('id', $this->repeat_of);
+                 if ($original) {
+                     $originalProfile = $original->getProfile();
+                 }
              }
-         }
-         foreach ($ni as $id => $source) {
-             $user = User::staticGet('id', $id);
-             if (empty($user) || $user->hasBlocked($profile) ||
-                 ($originalProfile && $user->hasBlocked($originalProfile))) {
-                 unset($ni[$id]);
+             foreach ($ni as $id => $source) {
+                 $user = User::staticGet('id', $id);
+                 if (empty($user) || $user->hasBlocked($profile) ||
+                     ($originalProfile && $user->hasBlocked($originalProfile))) {
+                     unset($ni[$id]);
+                 }
              }
+             // Give plugins a chance to filter out...
+             Event::handle('EndNoticeWhoGets', array($this, &$ni));
          }
  
          if (!empty($c)) {
          return $ids;
      }
  
 +    function getProfileTags()
 +    {
 +        // Don't save ptags for repeats, for now.
 +
 +        if (!empty($this->repeat_of)) {
 +            return array();
 +        }
 +
 +        // XXX: cache me
 +
 +        $ptags = array();
 +
 +        $ptagi = new Profile_tag_inbox();
 +
 +        $ptagi->selectAdd();
 +        $ptagi->selectAdd('profile_tag_id');
 +
 +        $ptagi->notice_id = $this->id;
 +
 +        if ($ptagi->find()) {
 +            while ($ptagi->fetch()) {
 +                $profile_list = Profile_list::staticGet('id', $ptagi->profile_tag_id);
 +                if ($profile_list) {
 +                    $ptags[] = $profile_list;
 +                }
 +            }
 +        }
 +
 +        $ptagi->free();
 +
 +        return $ptags;
 +    }
 +
      /**
       * Record this notice to the given group inboxes for delivery.
       * Overrides the regular parsing of !group markup.
          return true;
      }
  
 +    /**
 +     * record targets into profile_tag_inbox.
 +     * @return array of Profile_list objects
 +     */
 +    function saveProfileTags($known=array())
 +    {
 +        // Don't save ptags for repeats, for now
 +
 +        if (!empty($this->repeat_of)) {
 +            return array();
 +        }
 +
 +        if (is_array($known)) {
 +            $ptags = $known;
 +        } else {
 +            $ptags = array();
 +        }
 +
 +        $ptag = new Profile_tag();
 +        $ptag->tagged = $this->profile_id;
 +
 +        if($ptag->find()) {
 +            while($ptag->fetch()) {
 +                $plist = Profile_list::getByTaggerAndTag($ptag->tagger, $ptag->tag);
 +                $ptags[] = clone($plist);
 +            }
 +        }
 +
 +        foreach ($ptags as $target) {
 +            $this->addToProfileTagInbox($target);
 +        }
 +
 +        return $ptags;
 +    }
 +
 +    function addToProfileTagInbox($plist)
 +    {
 +        $ptagi = Profile_tag_inbox::pkeyGet(array('profile_tag_id' => $plist->id,
 +                                         'notice_id' => $this->id));
 +
 +        if (empty($ptagi)) {
 +
 +            $ptagi = new Profile_tag_inbox();
 +
 +            $ptagi->query('BEGIN');
 +            $ptagi->profile_tag_id  = $plist->id;
 +            $ptagi->notice_id = $this->id;
 +            $ptagi->created   = $this->created;
 +
 +            $result = $ptagi->insert();
 +            if (!$result) {
 +                common_log_db_error($ptagi, 'INSERT', __FILE__);
 +                throw new ServerException(_('Problem saving profile_tag inbox.'));
 +            }
 +
 +            $ptagi->query('COMMIT');
 +
 +            self::blow('profile_tag:notice_ids:%d', $ptagi->profile_tag_id);
 +        }
 +
 +        return true;
 +    }
 +
      /**
       * Save reply records indicating that this notice needs to be
       * delivered to the local users with the given URIs.
                  $this->is_local == Notice::LOCAL_NONPUBLIC);
      }
  
+     /**
+      * Get the list of hash tags saved with this notice.
+      *
+      * @return array of strings
+      */
      public function getTags()
      {
          $tags = array();
diff --combined classes/statusnet.ini
index 6b68dfe713e9eee1d8157cbbac99273b9f77214f,338e5c5aeaace0486b78f87b1047e049df583e8e..da37dcaffd1dbb6ddf3033fc069c247f7f97762c
@@@ -336,6 -336,7 +336,7 @@@ lon = 
  location_id = 1
  location_ns = 1
  repeat_of = 1
+ object_type = 2
  
  [notice__keys]
  id = N
@@@ -460,43 -461,6 +461,43 @@@ tagger = 
  tagged = K
  tag = K
  
 +[profile_list]
 +id = 129
 +tagger = 129
 +tag = 130
 +description = 34
 +private = 17
 +created = 142
 +modified = 384
 +uri = 130
 +mainpage = 130
 +tagged_count = 129
 +subscriber_count = 129
 +
 +[profile_list__keys]
 +id = U
 +tagger = K
 +tag = K
 +
 +[profile_tag_inbox]
 +profile_tag_id = 129
 +notice_id = 129
 +created = 142
 +
 +[profile_tag_inbox__keys]
 +profile_tag_id = K
 +notice_id = K
 +
 +[profile_tag_subscription]
 +profile_tag_id = 129
 +profile_id = 129
 +created = 142
 +modified = 384 
 +
 +[profile_tag_subscription__keys]
 +profile_tag_id = K
 +profile_id = K
 +
  [queue_item]
  id = 129
  frame = 194
diff --combined db/core.php
index 9f9c7a24bb118a6e7f59e04aa5ea61e0d63f8f6c,16a59462d491bd2f884f3c454858486185e86331..cfed063a59ba49019a3e86e5008ae8cc5450d8c6
@@@ -201,6 -201,7 +201,7 @@@ $schema['notice'] = array
          'location_id' => array('type' => 'int', 'description' => 'location id if possible'),
          'location_ns' => array('type' => 'int', 'description' => 'namespace for location'),
          'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'),
+         'object_type' => array('type' => 'varchar', 'length' => 255, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'),
      ),
      'primary key' => array('id'),
      'unique keys' => array(
@@@ -604,9 -605,8 +605,9 @@@ $schema['profile_tag'] = array
      ),
      'primary key' => array('tagger', 'tagged', 'tag'),
      'foreign keys' => array(
 -        'profile_tag_tagger_fkey' => array('user', array('tagger' => 'id')),
 +        'profile_tag_tagger_fkey' => array('profile', array('tagger' => 'id')),
          'profile_tag_tagged_fkey' => array('profile', array('tagged' => 'id')),
 +        'profile_tag_tag_fkey' => array('profile_list', array('tag' => 'tag')),
      ),
      'indexes' => array(
          'profile_tag_modified_idx' => array('modified'),
      ),
  );
  
 +$schema['profile_list'] = array(
 +    'fields' => array(
 +        'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'),
 +        'tagger' => array('type' => 'int', 'not null' => true, 'description' => 'user making the tag'),
 +        'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'people tag'),
 +        'description' => array('type' => 'text', 'description' => 'description of the people tag'),
 +        'private' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'is this tag private'),
 +
 +        'created' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date the tag was added'),
 +        'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date the tag was modified'),
 +
 +        'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universal identifier'),
 +        'mainpage' => array('type' => 'varchar', 'length' => 255, 'description' => 'page to link to'),
 +        'tagged_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of people tagged with this tag by this user'),
 +        'subscriber_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of subscribers to this tag'),
 +    ),
 +    'primary key' => array('tagger', 'tag'),
 +    'unique keys' => array(
 +      'profile_list_id_key' => array('id')
 +    ),
 +    'foreign keys' => array(
 +        'profile_list_tagger_fkey' => array('profile', array('tagger' => 'id')),
 +    ),
 +    'indexes' => array(
 +        'profile_list_modified_idx' => array('modified'),
 +        'profile_list_tag_idx' => array('tag'),
 +        'profile_list_tagger_tag_idx' => array('tagger', 'tag'),
 +        'profile_list_tagged_count_idx' => array('tagged_count'),
 +        'profile_list_subscriber_count_idx' => array('subscriber_count'),
 +    ),
 +);
 +
 +$schema['profile_tag_inbox'] = array(
 +    'description' => 'Many-many table listing notices associated with people tags.',
 +    'fields' => array(
 +        'profile_tag_id' => array('type' => 'int', 'not null' => true, 'description' => 'people tag receiving the message'),
 +        'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice received'),
 +        'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice was created'),
 +    ),
 +    'primary key' => array('profile_tag_id', 'notice_id'),
 +    'foreign keys' => array(
 +        'profile_tag_inbox_profile_list_id_fkey' => array('profile_list', array('profile_tag_id' => 'id')),
 +        'profile_tag_inbox_notice_id_fkey' => array('notice', array('notice_id' => 'id')),
 +    ),
 +    'indexes' => array(
 +        'profile_tag_inbox_created_idx' => array('created'),
 +        'profile_tag_inbox_profile_tag_id_idx' => array('profile_tag_id'),
 +    ),
 +);
 +
 +$schema['profile_tag_subscription'] = array(
 +    'fields' => array(
 +        'profile_tag_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile_tag'),
 +        'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'),
 +
 +        'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
 +        'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
 +    ),
 +    'primary key' => array('profile_tag_id', 'profile_id'),
 +    'foreign keys' => array(
 +        'profile_tag_subscription_profile_list_id_fkey' => array('profile_list', array('profile_tag_id' => 'id')),
 +        'profile_tag_subscription_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
 +    ),
 +    'indexes' => array(
 +        // @fixme probably we want a (profile_id, created) index here?
 +        'profile_tag_subscription_profile_id_idx' => array('profile_id'),
 +        'profile_tag_subscription_created_idx' => array('created'),
 +    ),
 +);
 +
  $schema['profile_block'] = array(
      'fields' => array(
          'blocker' => array('type' => 'int', 'not null' => true, 'description' => 'user making the block'),
diff --combined js/util.js
index 84943e8b2416947c39c0eabf22423b993a75f3df,61e6719dcc9955bc00457d6bb786f1a94ada12f3..98fb6c985026114808d615d5f244be2ed14fdbee
@@@ -31,7 -31,8 +31,8 @@@ var SN = { // StatusNe
              CounterBlackout: false,
              MaxLength: 140,
              PatternUsername: /^[0-9a-zA-Z\-_.]*$/,
-             HTTP20x30x: [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307]
+             HTTP20x30x: [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307],
+             NoticeFormMaster: null // to be cloned from the one at top
          },
  
          /**
  
                  SN.U.Counter(form);
  
-                 NDT = form.find('[name=status_textarea]');
+                 NDT = form.find('.notice_data-text:first');
  
                  NDT.bind('keyup', function(e) {
                      SN.U.Counter(form);
           * @return number of chars
           */
          CharacterCount: function(form) {
-             return form.find('[name=status_textarea]').val().length;
+             return form.find('.notice_data-text:first').val().length;
          },
  
          /**
           * will be extracted and copied in, replacing the original form.
           * If there's no form, the first paragraph will be used.
           *
+          * This will automatically be applied on the 'submit' event for
+          * any form with the 'ajax' class.
+          *
           * @fixme can sometimes explode confusingly if returnd data is bogus
           * @fixme error handling is pretty vague
           * @fixme can't submit file uploads
                              .attr(SN.C.S.Disabled, SN.C.S.Disabled);
                  },
                  error: function (xhr, textStatus, errorThrown) {
-                     alert(errorThrown || textStatus);
+                     // If the server end reported an error from StatusNet,
+                     // find it -- otherwise we'll see what was reported
+                     // from the browser.
+                     var errorReported = null;
+                     if (xhr.responseXML) {
+                         errorReported = $('#error', xhr.responseXML).text();
+                     }
+                     alert(errorReported || errorThrown || textStatus);
+                     
+                     // Restore the form to original state.
+                     // Hopefully. :D
+                     form
+                         .removeClass(SN.C.S.Processing)
+                         .find('.submit')
+                             .removeClass(SN.C.S.Disabled)
+                             .removeAttr(SN.C.S.Disabled);
                  },
                  success: function(data, textStatus) {
                      if (typeof($('form', data)[0]) != 'undefined') {
                          form_new = document._importNode($('form', data)[0], true);
                          form.replaceWith(form_new);
                      }
-                     else {
+                     else if (typeof($('p', data)[0]) != 'undefined') {
                          form.replaceWith(document._importNode($('p', data)[0], true));
                      }
+                     else {
+                         alert('Unknown error.');
+                     }
                  }
              });
          },
                  dataType: 'xml',
                  timeout: '60000',
                  beforeSend: function(formData) {
-                     if (form.find('[name=status_textarea]').val() == '') {
+                     if (form.find('.notice_data-text:first').val() == '') {
                          form.addClass(SN.C.S.Warning);
                          return false;
                      }
                              var replyItem = form.closest('li.notice-reply');
  
                              if (replyItem.length > 0) {
-                                 // If this is an inline reply, insert it in place.
+                                 // If this is an inline reply, remove the form...
+                                 var list = form.closest('.threaded-replies');
+                                 var placeholder = list.find('.notice-reply-placeholder');
+                                 replyItem.remove();
                                  var id = $(notice).attr('id');
                                  if ($("#"+id).length == 0) {
-                                     var parentNotice = replyItem.closest('li.notice');
-                                     replyItem.replaceWith(notice);
-                                     SN.U.NoticeInlineReplyPlaceholder(parentNotice);
+                                     $(notice).insertBefore(placeholder);
                                  } else {
                                      // Realtime came through before us...
-                                     replyItem.remove();
                                  }
+                                 // ...and show the placeholder form.
+                                 placeholder.show();
                              } else if (notices.length > 0 && SN.U.belongsOnTimeline(notice)) {
                                  // Not a reply. If on our timeline, show it at the top!
  
                                          .css({display:'none'})
                                          .fadeIn(2500);
                                      SN.U.NoticeWithAttachment($('#'+notice.id));
-                                     SN.U.NoticeReplyTo($('#'+notice.id));
+                                     SN.U.switchInputFormTab("placeholder");
                                  }
                              } else {
                                  // Not on a timeline that this belongs on?
              });
          },
  
 +        FormProfileSearchXHR: function(form) {
 +            $.ajax({
 +                type: 'POST',
 +                dataType: 'xml',
 +                url: form.attr('action'),
 +                data: form.serialize() + '&ajax=1',
 +                beforeSend: function(xhr) {
 +                    form
 +                        .addClass(SN.C.S.Processing)
 +                        .find('.submit')
 +                            .addClass(SN.C.S.Disabled)
 +                            .attr(SN.C.S.Disabled, SN.C.S.Disabled);
 +                },
 +                error: function (xhr, textStatus, errorThrown) {
 +                    alert(errorThrown || textStatus);
 +                },
 +                success: function(data, textStatus) {
 +                    var results_placeholder = $('#profile_search_results');
 +                    if (typeof($('ul', data)[0]) != 'undefined') {
 +                        var list = document._importNode($('ul', data)[0], true);
 +                        results_placeholder.replaceWith(list);
 +                    }
 +                    else {
 +                        var _error = $('<li/>').append(document._importNode($('p', data)[0], true)); 
 +                        results_placeholder.html(_error);
 +                    }
 +                    form
 +                        .removeClass(SN.C.S.Processing)
 +                        .find('.submit')
 +                            .removeClass(SN.C.S.Disabled)
 +                            .attr(SN.C.S.Disabled, false);
 +                }
 +            });
 +        },
 +
 +        FormPeopletagsXHR: function(form) {
 +            $.ajax({
 +                type: 'POST',
 +                dataType: 'xml',
 +                url: form.attr('action'),
 +                data: form.serialize() + '&ajax=1',
 +                beforeSend: function(xhr) {
 +                    form.addClass(SN.C.S.Processing)
 +                        .find('.submit')
 +                            .addClass(SN.C.S.Disabled)
 +                            .attr(SN.C.S.Disabled, SN.C.S.Disabled);
 +                },
 +                error: function (xhr, textStatus, errorThrown) {
 +                    alert(errorThrown || textStatus);
 +                },
 +                success: function(data, textStatus) {
 +                    var results_placeholder = form.parents('.entity_tags');
 +                    if (typeof($('.entity_tags', data)[0]) != 'undefined') {
 +                        var tags = document._importNode($('.entity_tags', data)[0], true);
 +                        $(tags).find('.editable').append($('<button class="peopletags_edit_button"/>'));
 +                        results_placeholder.replaceWith(tags);
 +                    } else {
 +                        results_placeholder.find('p').remove();
 +                        results_placeholder.append(document._importNode($('p', data)[0], true));
 +                        form.removeClass(SN.C.S.Processing)
 +                            .find('.submit')
 +                                .removeClass(SN.C.S.Disabled)
 +                                .attr(SN.C.S.Disabled, false);
 +                    }
 +                }
 +            });
 +        },
 +
          normalizeGeoData: function(form) {
              SN.C.I.NoticeDataGeo.NLat = form.find('[name=lat]').val();
              SN.C.I.NoticeDataGeo.NLon = form.find('[name=lon]').val();
              }
  
          },
 +
          /**
           * Fetch an XML DOM from an XHR's response data.
           *
           * @access private
           */
          NoticeReply: function() {
-             if ($('#content .notice_reply').length > 0) {
-                 $('#content .notice').each(function() { SN.U.NoticeReplyTo($(this)); });
-             }
+             $('#content .notice_reply').live('click', function(e) {
+                 e.preventDefault();
+                 var notice = $(this).closest('li.notice');
+                 var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
+                 SN.U.NoticeInlineReplyTrigger(notice, '@' + nickname.text());
+                 return false;
+             });
          },
  
          /**
-          * Setup function -- DOES NOT trigger actions immediately.
-          *
-          * Sets up event handlers on the given notice's reply button to
-          * tweak the new-notice form with needed variables and focus it
-          * when pushed.
-          *
-          * (This replaces the default reply button behavior to submit
-          * directly to a form which comes back with a specialized page
-          * with the form data prefilled.)
-          *
-          * @param {jQuery} notice: jQuery object containing one or more notices
+          * Stub -- kept for compat with plugins for now.
           * @access private
           */
          NoticeReplyTo: function(notice) {
-             notice.find('.notice_reply').live('click', function(e) {
-                 e.preventDefault();
-                 var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
-                 SN.U.NoticeInlineReplyTrigger(notice, '@' + nickname.text());
-                 return false;
-             });
          },
  
          /**
                  // Update the existing form...
                  nextStep();
              } else {
-                 // Remove placeholder if any
-                 $('li.notice-reply-placeholder').remove();
+                 // Hide the placeholder...
+                 var placeholder = list.find('li.notice-reply-placeholder').hide();
  
                  // Create the reply form entry at the end
                  var replyItem = $('li.notice-reply', list);
                  if (replyItem.length == 0) {
-                     var url = $('#form_notice').attr('action');
                      replyItem = $('<li class="notice-reply"></li>');
-                     $.get(url, {ajax: 1}, function(data, textStatus, xhr) {
-                         var formEl = document._importNode($('form', data)[0], true);
+                     var intermediateStep = function(formMaster) {
+                         var formEl = document._importNode(formMaster, true);
                          replyItem.append(formEl);
-                         list.append(replyItem);
+                         list.append(replyItem); // *after* the placeholder
  
                          var form = replyForm = $(formEl);
-                         SN.U.NoticeLocationAttach(form);
-                         SN.U.FormNoticeXHR(form);
-                         SN.U.FormNoticeEnhancements(form);
-                         SN.U.NoticeDataAttach(form);
+                         SN.Init.NoticeFormSetup(form);
  
                          nextStep();
-                     });
+                     };
+                     if (SN.C.I.NoticeFormMaster) {
+                         // We've already saved a master copy of the form.
+                         // Clone it in!
+                         intermediateStep(SN.C.I.NoticeFormMaster);
+                     } else {
+                         // Fetch a fresh copy of the notice form over AJAX.
+                         // Warning: this can have a delay, which looks bad.
+                         // @fixme this fallback may or may not work
+                         var url = $('#form_notice').attr('action');
+                         $.get(url, {ajax: 1}, function(data, textStatus, xhr) {
+                             intermediateStep($('form', data)[0]);
+                         });
+                     }
                  }
              }
          },
  
-         /**
-          * Setup function -- DOES NOT apply immediately.
-          *
-          * Sets up event handlers for favor/disfavor forms to submit via XHR.
-          * Uses 'live' rather than 'bind', so applies to future as well as present items.
-          */
-         NoticeFavor: function() {
-             $('.form_favor').live('click', function() { SN.U.FormXHR($(this)); return false; });
-             $('.form_disfavor').live('click', function() { SN.U.FormXHR($(this)); return false; });
-         },
          NoticeInlineReplyPlaceholder: function(notice) {
              var list = notice.find('ul.threaded-replies');
              var placeholder = $('<li class="notice-reply-placeholder">' +
                                      '<input class="placeholder">' +
                                  '</li>');
-             placeholder.click(function() {
-                 SN.U.NoticeInlineReplyTrigger(notice);
-             });
-             placeholder.find('input').val(SN.msg('reply_placeholder'));
+             placeholder.find('input')
+                 .val(SN.msg('reply_placeholder'));
              list.append(placeholder);
          },
  
          /**
           * Setup function -- DOES NOT apply immediately.
           *
-          * Sets up event handlers for favor/disfavor forms to submit via XHR.
+          * Sets up event handlers for inline reply mini-form placeholders.
           * Uses 'live' rather than 'bind', so applies to future as well as present items.
           */
          NoticeInlineReplySetup: function() {
-             $('.threaded-replies').each(function() {
-                 var list = $(this);
-                 var notice = list.closest('.notice');
-                 SN.U.NoticeInlineReplyPlaceholder(notice);
-             });
+             $('li.notice-reply-placeholder input')
+                 .live('focus', function() {
+                     var notice = $(this).closest('li.notice');
+                     SN.U.NoticeInlineReplyTrigger(notice);
+                     return false;
+                 });
+             $('li.notice-reply-comments a')
+                 .live('click', function() {
+                     var url = $(this).attr('href');
+                     var area = $(this).closest('.threaded-replies');
+                     $.get(url, {ajax: 1}, function(data, textStatus, xhr) {
+                         var replies = $('.threaded-replies', data);
+                         if (replies.length) {
+                             area.replaceWith(document._importNode(replies[0], true));
+                         }
+                     });
+                     return false;
+                 });
          },
  
          /**
                  }
  
                  var NGW = form.find('.notice_data-geo_wrap');
-                 var geocodeURL = NGW.attr('title');
-                 NGW.removeAttr('title');
+                 var geocodeURL = NGW.attr('data-api');
  
                  label
                      .attr('title', label.text());
                  wrapper = $('<div class="'+SN.C.S.Success+' geo_status_wrapper"><button class="close" style="float:right">&#215;</button><div class="geo_status"></div></div>');
                  wrapper.find('button.close').click(function() {
                      form.find('[name=notice_data-geo]').removeAttr('checked').change();
+                     return false;
                  });
                  form.append(wrapper);
              }
  
              var profileLink = $('#nav_profile a').attr('href');
              if (profileLink) {
-                 var authorUrl = $(notice).find('.entry-title .author a.url').attr('href');
+                 var authorUrl = $(notice).find('.vcard.author a.url').attr('href');
                  if (authorUrl == profileLink) {
                      if (action == 'all' || action == 'showstream') {
                          // Posts always show on your own friends and profile streams.
              // UI links currently on the page use malleable names.
  
              return false;
-         }
+         },
+         /**
+          * Switch to another active input sub-form.
+          * This will hide the current form (if any), show the new one, and
+          * update the input type tab selection state.
+          *
+          * @param {String} tag
+          */
+       switchInputFormTab: function(tag) {
+           // The one that's current isn't current anymore
+           $('.input_form_nav_tab.current').removeClass('current');
+             if (tag == 'placeholder') {
+                 // Hack: when showing the placeholder, mark the tab
+                 // as current for 'Status'.
+                 $('#input_form_nav_status').addClass('current');
+             } else {
+                 $('#input_form_nav_'+tag).addClass('current');
+             }
+           $('.input_form.current').removeClass('current');
+           $('#input_form_'+tag)
+                 .addClass('current')
+                 .find('.ajax-notice').each(function() {
+                     var form = $(this);
+                     SN.Init.NoticeFormSetup(form);
+                 })
+                 .find('textarea:first').focus();
+       }
      },
  
      Init: {
           */
          NoticeForm: function() {
              if ($('body.user_in').length > 0) {
-                 $('.'+SN.C.S.FormNotice).each(function() {
-                     var form = $(this);
-                     SN.U.NoticeLocationAttach(form);
-                     SN.U.FormNoticeXHR(form);
-                     SN.U.FormNoticeEnhancements(form);
-                     SN.U.NoticeDataAttach(form);
+                 // SN.Init.NoticeFormSetup() will get run
+                 // when forms get displayed for the first time...
+                 // Hack to initialize the placeholder at top
+                 $('#input_form_placeholder input.placeholder').focus(function() {
+                     SN.U.switchInputFormTab("status");
+                 });
+                 // Make inline reply forms self-close when clicking out.
+                 $('body').bind('click', function(e) {
+                     var currentForm = $('#content .input_forms div.current');
+                     if (currentForm.length > 0) {
+                         if ($('#content .input_forms').has(e.target).length == 0) {
+                             // If all fields are empty, switch back to the placeholder.
+                             var fields = currentForm.find('textarea, input[type=text], input[type=""]');
+                             var anything = false;
+                             fields.each(function() {
+                                 anything = anything || $(this).val();
+                             });
+                             if (!anything) {
+                                 SN.U.switchInputFormTab("placeholder");
+                             }
+                         }
+                     }
+                     var openReplies = $('li.notice-reply');
+                     if (openReplies.length > 0) {
+                         var target = $(e.target);
+                         openReplies.each(function() {
+                             // Did we click outside this one?
+                             var replyItem = $(this);
+                             if (replyItem.has(e.target).length == 0) {
+                                 var textarea = replyItem.find('.notice_data-text:first');
+                                 var cur = $.trim(textarea.val());
+                                 // Only close if there's been no edit.
+                                 if (cur == '' || cur == textarea.data('initialText')) {
+                                     var parentNotice = replyItem.closest('li.notice');
+                                     replyItem.remove();
+                                     parentNotice.find('li.notice-reply-placeholder').show();
+                                 }
+                             }
+                         });
+                     }
                  });
              }
          },
  
+         /**
+          * Encapsulate notice form setup for a single form.
+          * Plugins can add extra setup by monkeypatching this
+          * function.
+          *
+          * @param {jQuery} form
+          */
+         NoticeFormSetup: function(form) {
+             if (!form.data('NoticeFormSetup')) {
+                 SN.U.NoticeLocationAttach(form);
+                 SN.U.FormNoticeXHR(form);
+                 SN.U.FormNoticeEnhancements(form);
+                 SN.U.NoticeDataAttach(form);
+                 form.data('NoticeFormSetup', true);
+             }
+         },
          /**
           * Run setup code for notice timeline views items:
           *
           */
          Notices: function() {
              if ($('body.user_in').length > 0) {
-                 SN.U.NoticeFavor();
+                 var masterForm = $('.form_notice:first');
+                 if (masterForm.length > 0) {
+                     SN.C.I.NoticeFormMaster = document._importNode(masterForm[0], true);
+                 }
                  SN.U.NoticeRepeat();
                  SN.U.NoticeReply();
                  SN.U.NoticeInlineReplySetup();
           */
          EntityActions: function() {
              if ($('body.user_in').length > 0) {
 +                $('.form_user_subscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_user_unsubscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_group_join').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_group_leave').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_user_nudge').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_peopletag_subscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_peopletag_unsubscribe').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_user_add_peopletag').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +                $('.form_user_remove_peopletag').live('click', function() { SN.U.FormXHR($(this)); return false; });
 +
                  SN.U.NewDirectMessage();
              }
          },
  
 +        ProfileSearch: function() {
 +            if ($('body.user_in').length > 0) {
 +                $('.form_peopletag_edit_user_search input.submit').live('click', function() {
 +                    SN.U.FormProfileSearchXHR($(this).parents('form')); return false;
 +                });
 +            }
 +        },
 +
          /**
           * Run setup code for login form:
           *
              });
          },
  
 +        PeopletagAutocomplete: function() {
 +            $('.form_tag_user #tags').tagInput({
 +                tags: SN.C.PtagACData,
 +                tagSeparator: " ",
 +                animate: false,
 +                formatLine: function (i, e, search, matches) {
 +                  var tag = "<b>" + e.tag.substring(0, search.length) + "</b>" + e.tag.substring(search.length);
 +
 +                  var line = $("<div/>").addClass('mode-' + e.mode);
 +                    line.append($("<div class='tagInputLineTag'>" + tag
 +                        + " <em class='privacy_mode'>" + e.mode + "</em></div>"));
 +                  if (e.freq)
 +                    line.append("<div class='tagInputLineFreq'>" + e.freq + "</div>");
 +                  return line;
 +                }
 +            });
 +        },
 +
 +        PeopleTags: function() {
 +            $('.user_profile_tags .editable').append($('<button class="peopletags_edit_button"/>'));
 +
 +            $('.peopletags_edit_button').live('click', function() {
 +                var form = $(this).parents('dd').eq(0).find('form');
 +                // We can buy time from the above animation
 +                if (typeof SN.C.PtagACData === 'undefined') {
 +                    $.getJSON(_peopletagAC + '?token=' + $('#token').val(), function(data) {
 +                        SN.C.PtagACData = data;
 +                        _loadTagInput(SN.Init.PeopletagAutocomplete);
 +                    });
 +                } else { _loadTagInput(SN.Init.PeopletagAutocomplete); }
 +
 +                $(this).parents('ul').eq(0).fadeOut(200, function() {form.fadeIn(200).find('input#tags')});
 +            })
 +
 +            $('.user_profile_tags form .submit').live('click', function() {
 +                SN.U.FormPeopletagsXHR($(this).parents('form')); return false;
 +            });
 +        },
 +
+         /**
+          * Set up any generic 'ajax' form so it submits via AJAX with auto-replacement.
+          */
+         AjaxForms: function() {
+             $('form.ajax').live('submit', function() {
+                 SN.U.FormXHR($(this));
+                 return false;
+             });
+         },
          /**
           * Add logic to any file upload forms to handle file size limits,
           * on browsers that support basic FileAPI.
   * don't start them loading until after DOM-ready time!
   */
  $(document).ready(function(){
+     SN.Init.AjaxForms();
      SN.Init.UploadForms();
      if ($('.'+SN.C.S.FormNotice).length > 0) {
          SN.Init.NoticeForm();
      if ($('#form_login').length > 0) {
          SN.Init.Login();
      }
 +    if ($('#profile_search_results').length > 0) {
 +        SN.Init.ProfileSearch();
 +    }
 +    if ($('.user_profile_tags .editable').length > 0) {
 +        SN.Init.PeopleTags();
 +    }
  });
diff --combined js/util.min.js
index a31d366d7bdaac46d0ae82a9365ea8ee016165d6,0f81741830d759766030c2e45dd27fc6bf890951..cfc06cda5ac6f9df21f3a3fdf1ddb32019173172
@@@ -1,1 -1,1 +1,1 @@@
- var SN={C:{I:{CounterBlackout:false,MaxLength:140,PatternUsername:/^[0-9a-zA-Z\-_.]*$/,HTTP20x30x:[200,201,202,203,204,205,206,300,301,302,303,304,305,306,307]},S:{Disabled:"disabled",Warning:"warning",Error:"error",Success:"success",Processing:"processing",CommandResult:"command_result",FormNotice:"form_notice",NoticeDataGeo:"notice_data-geo",NoticeDataGeoCookie:"NoticeDataGeo",NoticeDataGeoSelected:"notice_data-geo_selected",StatusNetInstance:"StatusNetInstance"}},messages:{},msg:function(a){if(typeof SN.messages[a]=="undefined"){return"["+a+"]"}else{return SN.messages[a]}},U:{FormNoticeEnhancements:function(b){if(jQuery.data(b[0],"ElementData")===undefined){MaxLength=b.find(".count").text();if(typeof(MaxLength)=="undefined"){MaxLength=SN.C.I.MaxLength}jQuery.data(b[0],"ElementData",{MaxLength:MaxLength});SN.U.Counter(b);NDT=b.find("[name=status_textarea]");NDT.bind("keyup",function(c){SN.U.Counter(b)});var a=function(c){window.setTimeout(function(){SN.U.Counter(b)},50)};NDT.bind("cut",a).bind("paste",a)}else{b.find(".count").text(jQuery.data(b[0],"ElementData").MaxLength)}},Counter:function(d){SN.C.I.FormNoticeCurrent=d;var b=jQuery.data(d[0],"ElementData").MaxLength;if(b<=0){return}var c=b-SN.U.CharacterCount(d);var a=d.find(".count");if(c.toString()!=a.text()){if(!SN.C.I.CounterBlackout||c===0){if(a.text()!=String(c)){a.text(c)}if(c<0){d.addClass(SN.C.S.Warning)}else{d.removeClass(SN.C.S.Warning)}if(!SN.C.I.CounterBlackout){SN.C.I.CounterBlackout=true;SN.C.I.FormNoticeCurrent=d;window.setTimeout("SN.U.ClearCounterBlackout(SN.C.I.FormNoticeCurrent);",500)}}}},CharacterCount:function(a){return a.find("[name=status_textarea]").val().length},ClearCounterBlackout:function(a){SN.C.I.CounterBlackout=false;SN.U.Counter(a)},RewriteAjaxAction:function(a){if(document.location.protocol=="https:"&&a.substr(0,5)=="http:"){return a.replace(/^http:\/\/[^:\/]+/,"https://"+document.location.host)}else{return a}},FormXHR:function(a){$.ajax({type:"POST",dataType:"xml",url:SN.U.RewriteAjaxAction(a.attr("action")),data:a.serialize()+"&ajax=1",beforeSend:function(b){a.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled)},error:function(c,d,b){alert(b||d)},success:function(b,c){if(typeof($("form",b)[0])!="undefined"){form_new=document._importNode($("form",b)[0],true);a.replaceWith(form_new)}else{a.replaceWith(document._importNode($("p",b)[0],true))}}})},FormNoticeXHR:function(b){SN.C.I.NoticeDataGeo={};b.append('<input type="hidden" name="ajax" value="1"/>');b.attr("action",SN.U.RewriteAjaxAction(b.attr("action")));var c=function(d,e){b.append($('<p class="form_response"></p>').addClass(d).text(e))};var a=function(){b.find(".form_response").remove()};b.ajaxForm({dataType:"xml",timeout:"60000",beforeSend:function(d){if(b.find("[name=status_textarea]").val()==""){b.addClass(SN.C.S.Warning);return false}b.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled);SN.U.normalizeGeoData(b);return true},error:function(f,g,e){b.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).removeAttr(SN.C.S.Disabled,SN.C.S.Disabled);a();if(g=="timeout"){c("error","Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.")}else{var d=SN.U.GetResponseXML(f);if($("."+SN.C.S.Error,d).length>0){b.append(document._importNode($("."+SN.C.S.Error,d)[0],true))}else{if(parseInt(f.status)===0||jQuery.inArray(parseInt(f.status),SN.C.I.HTTP20x30x)>=0){b.resetForm().find(".attach-status").remove();SN.U.FormNoticeEnhancements(b)}else{c("error","(Sorry! We had trouble sending your notice ("+f.status+" "+f.statusText+"). Please report the problem to the site administrator if this happens again.")}}}},success:function(i,f){a();var n=$("#"+SN.C.S.Error,i);if(n.length>0){c("error",n.text())}else{if($("body")[0].id=="bookmarklet"){self.close()}var d=$("#"+SN.C.S.CommandResult,i);if(d.length>0){c("success",d.text())}else{var m=document._importNode($("li",i)[0],true);var k=$("#notices_primary .notices:first");var l=b.closest("li.notice-reply");if(l.length>0){var e=$(m).attr("id");if($("#"+e).length==0){var j=l.closest("li.notice");l.replaceWith(m);SN.U.NoticeInlineReplyPlaceholder(j)}else{l.remove()}}else{if(k.length>0&&SN.U.belongsOnTimeline(m)){if($("#"+m.id).length===0){var h=b.find("[name=inreplyto]").val();var g="#notices_primary #notice-"+h;if($("body")[0].id=="conversation"){if(h.length>0&&$(g+" .notices").length<1){$(g).append('<ul class="notices"></ul>')}$($(g+" .notices")[0]).append(m)}else{k.prepend(m)}$("#"+m.id).css({display:"none"}).fadeIn(2500);SN.U.NoticeWithAttachment($("#"+m.id));SN.U.NoticeReplyTo($("#"+m.id))}}else{c("success",$("title",i).text())}}}b.resetForm();b.find("[name=inreplyto]").val("");b.find(".attach-status").remove();SN.U.FormNoticeEnhancements(b)}},complete:function(d,e){b.removeClass(SN.C.S.Processing).find(".submit").removeAttr(SN.C.S.Disabled).removeClass(SN.C.S.Disabled);b.find("[name=lat]").val(SN.C.I.NoticeDataGeo.NLat);b.find("[name=lon]").val(SN.C.I.NoticeDataGeo.NLon);b.find("[name=location_ns]").val(SN.C.I.NoticeDataGeo.NLNS);b.find("[name=location_id]").val(SN.C.I.NoticeDataGeo.NLID);b.find("[name=notice_data-geo]").attr("checked",SN.C.I.NoticeDataGeo.NDG)}})},FormProfileSearchXHR:function(a){$.ajax({type:"POST",dataType:"xml",url:a.attr("action"),data:a.serialize()+"&ajax=1",beforeSend:function(b){a.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled)},error:function(c,d,b){alert(b||d)},success:function(d,f){var b=$("#profile_search_results");if(typeof($("ul",d)[0])!="undefined"){var c=document._importNode($("ul",d)[0],true);b.replaceWith(c)}else{var e=$("<li/>").append(document._importNode($("p",d)[0],true));b.html(e)}a.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,false)}})},FormPeopletagsXHR:function(a){$.ajax({type:"POST",dataType:"xml",url:a.attr("action"),data:a.serialize()+"&ajax=1",beforeSend:function(b){a.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled)},error:function(c,d,b){alert(b||d)},success:function(d,e){var c=a.parents(".entity_tags");if(typeof($(".entity_tags",d)[0])!="undefined"){var b=document._importNode($(".entity_tags",d)[0],true);$(b).find(".editable").append($('<button class="peopletags_edit_button"/>'));c.replaceWith(b)}else{c.find("p").remove();c.append(document._importNode($("p",d)[0],true));a.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,false)}}})},normalizeGeoData:function(a){SN.C.I.NoticeDataGeo.NLat=a.find("[name=lat]").val();SN.C.I.NoticeDataGeo.NLon=a.find("[name=lon]").val();SN.C.I.NoticeDataGeo.NLNS=a.find("[name=location_ns]").val();SN.C.I.NoticeDataGeo.NLID=a.find("[name=location_id]").val();SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked");var b=$.cookie(SN.C.S.NoticeDataGeoCookie);if(b!==null&&b!="disabled"){b=JSON.parse(b);SN.C.I.NoticeDataGeo.NLat=a.find("[name=lat]").val(b.NLat).val();SN.C.I.NoticeDataGeo.NLon=a.find("[name=lon]").val(b.NLon).val();if(b.NLNS){SN.C.I.NoticeDataGeo.NLNS=a.find("[name=location_ns]").val(b.NLNS).val();SN.C.I.NoticeDataGeo.NLID=a.find("[name=location_id]").val(b.NLID).val()}else{a.find("[name=location_ns]").val("");a.find("[name=location_id]").val("")}}if(b=="disabled"){SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked",false).attr("checked")}else{SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked",true).attr("checked")}},GetResponseXML:function(b){try{return b.responseXML}catch(a){return(new DOMParser()).parseFromString(b.responseText,"text/xml")}},NoticeReply:function(){if($("#content .notice_reply").length>0){$("#content .notice").each(function(){SN.U.NoticeReplyTo($(this))})}},NoticeReplyTo:function(a){a.find(".notice_reply").live("click",function(c){c.preventDefault();var b=($(".author .nickname",a).length>0)?$($(".author .nickname",a)[0]):$(".author .nickname.uid");SN.U.NoticeInlineReplyTrigger(a,"@"+b.text());return false})},NoticeInlineReplyTrigger:function(g,h){var b=$($(".notice_id",g)[0]).text();var d=g;var e=g.closest(".notices");if(e.hasClass("threaded-replies")){d=e.closest(".notice")}else{e=$("ul.threaded-replies",g);if(e.length==0){e=$('<ul class="notices threaded-replies xoxo"></ul>');g.append(e)}}var i=$(".notice-reply-form",e);var c=function(){i.find("input[name=inreplyto]").val(b);var l=i.find("textarea");if(l.length==0){throw"No textarea"}var k="";if(h){k=h+" "}l.val(k+l.val().replace(RegExp(k,"i"),""));l.data("initialText",$.trim(h+""));l.focus();if(l[0].setSelectionRange){var j=l.val().length;l[0].setSelectionRange(j,j)}};if(i.length>0){c()}else{$("li.notice-reply-placeholder").remove();var f=$("li.notice-reply",e);if(f.length==0){var a=$("#form_notice").attr("action");f=$('<li class="notice-reply"></li>');$.get(a,{ajax:1},function(l,n,m){var j=document._importNode($("form",l)[0],true);f.append(j);e.append(f);var k=i=$(j);SN.U.NoticeLocationAttach(k);SN.U.FormNoticeXHR(k);SN.U.FormNoticeEnhancements(k);SN.U.NoticeDataAttach(k);c()})}}},NoticeFavor:function(){$(".form_favor").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_disfavor").live("click",function(){SN.U.FormXHR($(this));return false})},NoticeInlineReplyPlaceholder:function(b){var a=b.find("ul.threaded-replies");var c=$('<li class="notice-reply-placeholder"><input class="placeholder"></li>');c.click(function(){SN.U.NoticeInlineReplyTrigger(b)});c.find("input").val(SN.msg("reply_placeholder"));a.append(c)},NoticeInlineReplySetup:function(){$(".threaded-replies").each(function(){var b=$(this);var a=b.closest(".notice");SN.U.NoticeInlineReplyPlaceholder(a)})},NoticeRepeat:function(){$(".form_repeat").live("click",function(a){a.preventDefault();SN.U.NoticeRepeatConfirmation($(this));return false})},NoticeRepeatConfirmation:function(a){var c=a.find(".submit");var b=c.clone();b.addClass("submit_dialogbox").removeClass("submit");a.append(b);b.bind("click",function(){SN.U.FormXHR(a);return false});c.hide();a.addClass("dialogbox").append('<button class="close">&#215;</button>').closest(".notice-options").addClass("opaque");a.find("button.close").click(function(){$(this).remove();a.removeClass("dialogbox").closest(".notice-options").removeClass("opaque");a.find(".submit_dialogbox").remove();a.find(".submit").show();return false})},NoticeAttachments:function(){$(".notice a.attachment").each(function(){SN.U.NoticeWithAttachment($(this).closest(".notice"))})},NoticeWithAttachment:function(b){if(b.find(".attachment").length===0){return}var a=b.find(".attachment.more");if(a.length>0){$(a[0]).click(function(){var c=$(this);c.addClass(SN.C.S.Processing);$.get(c.attr("href")+"/ajax",null,function(d){c.parent(".entry-content").html($(d).find("#attachment_view .entry-content").html())});return false}).attr("title",SN.msg("showmore_tooltip"))}},NoticeDataAttach:function(b){var a=b.find("input[type=file]");a.change(function(f){b.find(".attach-status").remove();var d=$(this).val();if(!d){return false}var c=$('<div class="attach-status '+SN.C.S.Success+'"><code></code> <button class="close">&#215;</button></div>');c.find("code").text(d);c.find("button").click(function(){c.remove();a.val("");return false});b.append(c);if(typeof this.files=="object"){for(var e=0;e<this.files.length;e++){SN.U.PreviewAttach(b,this.files[e])}}})},maxFileSize:function(b){var a=$(b).find("input[name=MAX_FILE_SIZE]").attr("value");if(a){return parseInt(a)}else{return 0}},PreviewAttach:function(d,c){var e=c.type+" "+Math.round(c.size/1024)+"KB";var f=true;var h;if(typeof window.createObjectURL!="undefined"){h=function(i,j){j(window.createObjectURL(i))}}else{if(typeof window.FileReader!="undefined"){h=function(j,k){var i=new FileReader();i.onload=function(l){k(i.result)};i.readAsDataURL(j)}}else{f=false}}var a=["image/png","image/jpeg","image/gif","image/svg+xml"];if($.inArray(c.type,a)==-1){f=false}var g=8*1024*1024;if(c.size>g){f=false}if(f){h(c,function(j){var i=$("<img>").attr("title",e).attr("alt",e).attr("src",j).attr("style","height: 120px");d.find(".attach-status").append(i)})}else{var b=$("<div></div>").text(e);d.find(".attach-status").append(b)}},NoticeLocationAttach:function(a){var e=a.find("[name=lat]");var k=a.find("[name=lon]");var g=a.find("[name=location_ns]").val();var l=a.find("[name=location_id]").val();var b="";var d=a.find("[name=notice_data-geo]");var c=a.find("[name=notice_data-geo]");var j=a.find("label.notice_data-geo");function f(n){j.attr("title",jQuery.trim(j.text())).removeClass("checked");a.find("[name=lat]").val("");a.find("[name=lon]").val("");a.find("[name=location_ns]").val("");a.find("[name=location_id]").val("");a.find("[name=notice_data-geo]").attr("checked",false);$.cookie(SN.C.S.NoticeDataGeoCookie,"disabled",{path:"/"});if(n){a.find(".geo_status_wrapper").removeClass("success").addClass("error");a.find(".geo_status_wrapper .geo_status").text(n)}else{a.find(".geo_status_wrapper").remove()}}function m(n,o){SN.U.NoticeGeoStatus(a,"Looking up place name...");$.getJSON(n,o,function(p){var q,r;if(typeof(p.location_ns)!="undefined"){a.find("[name=location_ns]").val(p.location_ns);q=p.location_ns}if(typeof(p.location_id)!="undefined"){a.find("[name=location_id]").val(p.location_id);r=p.location_id}if(typeof(p.name)=="undefined"){NLN_text=o.lat+";"+o.lon}else{NLN_text=p.name}SN.U.NoticeGeoStatus(a,NLN_text,o.lat,o.lon,p.url);j.attr("title",NoticeDataGeo_text.ShareDisable+" ("+NLN_text+")");a.find("[name=lat]").val(o.lat);a.find("[name=lon]").val(o.lon);a.find("[name=location_ns]").val(q);a.find("[name=location_id]").val(r);a.find("[name=notice_data-geo]").attr("checked",true);var s={NLat:o.lat,NLon:o.lon,NLNS:q,NLID:r,NLN:NLN_text,NLNU:p.url,NDG:true};$.cookie(SN.C.S.NoticeDataGeoCookie,JSON.stringify(s),{path:"/"})})}if(c.length>0){if($.cookie(SN.C.S.NoticeDataGeoCookie)=="disabled"){c.attr("checked",false)}else{c.attr("checked",true)}var h=a.find(".notice_data-geo_wrap");var i=h.attr("title");h.removeAttr("title");j.attr("title",j.text());c.change(function(){if(c.attr("checked")===true||$.cookie(SN.C.S.NoticeDataGeoCookie)===null){j.attr("title",NoticeDataGeo_text.ShareDisable).addClass("checked");if($.cookie(SN.C.S.NoticeDataGeoCookie)===null||$.cookie(SN.C.S.NoticeDataGeoCookie)=="disabled"){if(navigator.geolocation){SN.U.NoticeGeoStatus(a,"Requesting location from browser...");navigator.geolocation.getCurrentPosition(function(p){a.find("[name=lat]").val(p.coords.latitude);a.find("[name=lon]").val(p.coords.longitude);var q={lat:p.coords.latitude,lon:p.coords.longitude,token:$("#token").val()};m(i,q)},function(p){switch(p.code){case p.PERMISSION_DENIED:f("Location permission denied.");break;case p.TIMEOUT:f("Location lookup timeout.");break}},{timeout:10000})}else{if(e.length>0&&k.length>0){var n={lat:e,lon:k,token:$("#token").val()};m(i,n)}else{f();c.remove();j.remove()}}}else{var o=JSON.parse($.cookie(SN.C.S.NoticeDataGeoCookie));a.find("[name=lat]").val(o.NLat);a.find("[name=lon]").val(o.NLon);a.find("[name=location_ns]").val(o.NLNS);a.find("[name=location_id]").val(o.NLID);a.find("[name=notice_data-geo]").attr("checked",o.NDG);SN.U.NoticeGeoStatus(a,o.NLN,o.NLat,o.NLon,o.NLNU);j.attr("title",NoticeDataGeo_text.ShareDisable+" ("+o.NLN+")").addClass("checked")}}else{f()}}).change()}},NoticeGeoStatus:function(e,a,f,g,c){var h=e.find(".geo_status_wrapper");if(h.length==0){h=$('<div class="'+SN.C.S.Success+' geo_status_wrapper"><button class="close" style="float:right">&#215;</button><div class="geo_status"></div></div>');h.find("button.close").click(function(){e.find("[name=notice_data-geo]").removeAttr("checked").change()});e.append(h)}var b;if(c){b=$("<a></a>").attr("href",c)}else{b=$("<span></span>")}b.text(a);if(f||g){var d=f+";"+g;b.attr("title",d);if(!a){b.text(d)}}h.find(".geo_status").empty().append(b)},NewDirectMessage:function(){NDM=$(".entity_send-a-message a");NDM.attr({href:NDM.attr("href")+"&ajax=1"});NDM.bind("click",function(){var a=$(".entity_send-a-message form");if(a.length===0){$(this).addClass(SN.C.S.Processing);$.get(NDM.attr("href"),null,function(b){$(".entity_send-a-message").append(document._importNode($("form",b)[0],true));a=$(".entity_send-a-message .form_notice");SN.U.FormNoticeXHR(a);SN.U.FormNoticeEnhancements(a);a.append('<button class="close">&#215;</button>');$(".entity_send-a-message button").click(function(){a.hide();return false});NDM.removeClass(SN.C.S.Processing)})}else{a.show();$(".entity_send-a-message textarea").focus()}return false})},GetFullYear:function(c,d,a){var b=new Date();b.setFullYear(c,d,a);return b},StatusNetInstance:{Set:function(b){var a=SN.U.StatusNetInstance.Get();if(a!==null){b=$.extend(a,b)}$.cookie(SN.C.S.StatusNetInstance,JSON.stringify(b),{path:"/",expires:SN.U.GetFullYear(2029,0,1)})},Get:function(){var a=$.cookie(SN.C.S.StatusNetInstance);if(a!==null){return JSON.parse(a)}return null},Delete:function(){$.cookie(SN.C.S.StatusNetInstance,null)}},belongsOnTimeline:function(b){var a=$("body").attr("id");if(a=="public"){return true}var c=$("#nav_profile a").attr("href");if(c){var d=$(b).find(".entry-title .author a.url").attr("href");if(d==c){if(a=="all"||a=="showstream"){return true}}}return false}},Init:{NoticeForm:function(){if($("body.user_in").length>0){$("."+SN.C.S.FormNotice).each(function(){var a=$(this);SN.U.NoticeLocationAttach(a);SN.U.FormNoticeXHR(a);SN.U.FormNoticeEnhancements(a);SN.U.NoticeDataAttach(a)})}},Notices:function(){if($("body.user_in").length>0){SN.U.NoticeFavor();SN.U.NoticeRepeat();SN.U.NoticeReply();SN.U.NoticeInlineReplySetup()}SN.U.NoticeAttachments()},EntityActions:function(){if($("body.user_in").length>0){$(".form_user_subscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_unsubscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_group_join").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_group_leave").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_nudge").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_peopletag_subscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_peopletag_unsubscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_add_peopletag").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_remove_peopletag").live("click",function(){SN.U.FormXHR($(this));return false});SN.U.NewDirectMessage()}},ProfileSearch:function(){if($("body.user_in").length>0){$(".form_peopletag_edit_user_search input.submit").live("click",function(){SN.U.FormProfileSearchXHR($(this).parents("form"));return false})}},Login:function(){if(SN.U.StatusNetInstance.Get()!==null){var a=SN.U.StatusNetInstance.Get().Nickname;if(a!==null){$("#form_login #nickname").val(a)}}$("#form_login").bind("submit",function(){SN.U.StatusNetInstance.Set({Nickname:$("#form_login #nickname").val()});return true})},PeopletagAutocomplete:function(){$(".form_tag_user #tags").tagInput({tags:SN.C.PtagACData,tagSeparator:" ",animate:false,formatLine:function(d,g,c,f){var a="<b>"+g.tag.substring(0,c.length)+"</b>"+g.tag.substring(c.length);var b=$("<div/>").addClass("mode-"+g.mode);b.append($("<div class='tagInputLineTag'>"+a+" <em class='privacy_mode'>"+g.mode+"</em></div>"));if(g.freq){b.append("<div class='tagInputLineFreq'>"+g.freq+"</div>")}return b}})},PeopleTags:function(){$(".user_profile_tags .editable").append($('<button class="peopletags_edit_button"/>'));$(".peopletags_edit_button").live("click",function(){var a=$(this).parents("dd").eq(0).find("form");if(typeof SN.C.PtagACData==="undefined"){$.getJSON(_peopletagAC+"?token="+$("#token").val(),function(b){SN.C.PtagACData=b;_loadTagInput(SN.Init.PeopletagAutocomplete)})}else{_loadTagInput(SN.Init.PeopletagAutocomplete)}$(this).parents("ul").eq(0).fadeOut(200,function(){a.fadeIn(200).find("input#tags")})});$(".user_profile_tags form .submit").live("click",function(){SN.U.FormPeopletagsXHR($(this).parents("form"));return false})},UploadForms:function(){$("input[type=file]").change(function(d){if(typeof this.files=="object"&&this.files.length>0){var c=0;for(var b=0;b<this.files.length;b++){c+=this.files[b].size}var a=SN.U.maxFileSize($(this.form));if(a>0&&c>a){var e="File too large: maximum upload size is %d bytes.";alert(e.replace("%d",a));$(this).val("");d.preventDefault();return false}}})}}};$(document).ready(function(){SN.Init.UploadForms();if($("."+SN.C.S.FormNotice).length>0){SN.Init.NoticeForm()}if($("#content .notices").length>0){SN.Init.Notices()}if($("#content .entity_actions").length>0){SN.Init.EntityActions()}if($("#form_login").length>0){SN.Init.Login()}if($("#profile_search_results").length>0){SN.Init.ProfileSearch()}if($(".user_profile_tags .editable").length>0){SN.Init.PeopleTags()}});if(!document.ELEMENT_NODE){document.ELEMENT_NODE=1;document.ATTRIBUTE_NODE=2;document.TEXT_NODE=3;document.CDATA_SECTION_NODE=4;document.ENTITY_REFERENCE_NODE=5;document.ENTITY_NODE=6;document.PROCESSING_INSTRUCTION_NODE=7;document.COMMENT_NODE=8;document.DOCUMENT_NODE=9;document.DOCUMENT_TYPE_NODE=10;document.DOCUMENT_FRAGMENT_NODE=11;document.NOTATION_NODE=12}document._importNode=function(e,a){switch(e.nodeType){case document.ELEMENT_NODE:var d=document.createElement(e.nodeName);if(e.attributes&&e.attributes.length>0){for(var c=0,b=e.attributes.length;c<b;){if(e.attributes[c].nodeName=="class"){d.className=e.getAttribute(e.attributes[c++].nodeName)}else{d.setAttribute(e.attributes[c].nodeName,e.getAttribute(e.attributes[c++].nodeName))}}}if(a&&e.childNodes&&e.childNodes.length>0){for(var c=0,b=e.childNodes.length;c<b;){d.appendChild(document._importNode(e.childNodes[c++],a))}}return d;break;case document.TEXT_NODE:case document.CDATA_SECTION_NODE:case document.COMMENT_NODE:return document.createTextNode(e.nodeValue);break}};if(typeof navigator.geolocation=="undefined"||navigator.geolocation.shim){(function(){(function(){if(window.google&&google.gears){return}var c=null;if(typeof GearsFactory!="undefined"){c=new GearsFactory()}else{try{c=new ActiveXObject("Gears.Factory");if(c.getBuildInfo().indexOf("ie_mobile")!=-1){c.privateSetGlobalObject(this)}}catch(d){if((typeof navigator.mimeTypes!="undefined")&&navigator.mimeTypes["application/x-googlegears"]){c=document.createElement("object");c.style.display="none";c.width=0;c.height=0;c.type="application/x-googlegears";document.documentElement.appendChild(c)}}}if(!c){return}if(!window.google){google={}}if(!google.gears){google.gears={factory:c}}})();var a=(function(){var d=google.gears.factory.create("beta.geolocation");var c=function(f,e){return function(g){f(g);e.lastPosition=g}};return{shim:true,type:"Gears",lastPosition:null,getCurrentPosition:function(e,g,h){var f=this;var i=c(e,f);d.getCurrentPosition(i,g,h)},watchPosition:function(e,f,g){d.watchPosition(e,f,g)},clearWatch:function(e){d.clearWatch(e)},getPermission:function(g,e,f){d.getPermission(g,e,f)}}});var b=(function(){var i=false;var e=function(){if(!d()&&!i){i=true;var j=document.createElement("script");j.src=(document.location.protocol=="https:"?"https://":"http://")+"www.google.com/jsapi?callback=_google_loader_apiLoaded";j.type="text/javascript";document.getElementsByTagName("body")[0].appendChild(j)}};var c=[];var h=function(j){c.push(j)};var f=function(){if(d()){while(c.length>0){var j=c.pop();j()}}};window._google_loader_apiLoaded=function(){f()};var d=function(){return(window.google&&google.loader)};var g=function(j){if(d()){return true}h(j);e();return false};e();return{shim:true,type:"ClientLocation",lastPosition:null,getCurrentPosition:function(k,n,o){var m=this;if(!g(function(){m.getCurrentPosition(k,n,o)})){return}if(google.loader.ClientLocation){var l=google.loader.ClientLocation;var j={coords:{latitude:l.latitude,longitude:l.longitude,altitude:null,accuracy:43000,altitudeAccuracy:null,heading:null,speed:null},address:{city:l.address.city,country:l.address.country,country_code:l.address.country_code,region:l.address.region},timestamp:new Date()};k(j);this.lastPosition=j}else{if(n==="function"){n({code:3,message:"Using the Google ClientLocation API and it is not able to calculate a location."})}}},watchPosition:function(j,l,m){this.getCurrentPosition(j,l,m);var k=this;var n=setInterval(function(){k.getCurrentPosition(j,l,m)},10000);return n},clearWatch:function(j){clearInterval(j)},getPermission:function(l,j,k){return true}}});navigator.geolocation=(window.google&&google.gears)?a():b()})()};
 -var SN={C:{I:{CounterBlackout:false,MaxLength:140,PatternUsername:/^[0-9a-zA-Z\-_.]*$/,HTTP20x30x:[200,201,202,203,204,205,206,300,301,302,303,304,305,306,307],NoticeFormMaster:null},S:{Disabled:"disabled",Warning:"warning",Error:"error",Success:"success",Processing:"processing",CommandResult:"command_result",FormNotice:"form_notice",NoticeDataGeo:"notice_data-geo",NoticeDataGeoCookie:"NoticeDataGeo",NoticeDataGeoSelected:"notice_data-geo_selected",StatusNetInstance:"StatusNetInstance"}},messages:{},msg:function(a){if(typeof SN.messages[a]=="undefined"){return"["+a+"]"}else{return SN.messages[a]}},U:{FormNoticeEnhancements:function(b){if(jQuery.data(b[0],"ElementData")===undefined){MaxLength=b.find(".count").text();if(typeof(MaxLength)=="undefined"){MaxLength=SN.C.I.MaxLength}jQuery.data(b[0],"ElementData",{MaxLength:MaxLength});SN.U.Counter(b);NDT=b.find(".notice_data-text:first");NDT.bind("keyup",function(c){SN.U.Counter(b)});var a=function(c){window.setTimeout(function(){SN.U.Counter(b)},50)};NDT.bind("cut",a).bind("paste",a)}else{b.find(".count").text(jQuery.data(b[0],"ElementData").MaxLength)}},Counter:function(d){SN.C.I.FormNoticeCurrent=d;var b=jQuery.data(d[0],"ElementData").MaxLength;if(b<=0){return}var c=b-SN.U.CharacterCount(d);var a=d.find(".count");if(c.toString()!=a.text()){if(!SN.C.I.CounterBlackout||c===0){if(a.text()!=String(c)){a.text(c)}if(c<0){d.addClass(SN.C.S.Warning)}else{d.removeClass(SN.C.S.Warning)}if(!SN.C.I.CounterBlackout){SN.C.I.CounterBlackout=true;SN.C.I.FormNoticeCurrent=d;window.setTimeout("SN.U.ClearCounterBlackout(SN.C.I.FormNoticeCurrent);",500)}}}},CharacterCount:function(a){return a.find(".notice_data-text:first").val().length},ClearCounterBlackout:function(a){SN.C.I.CounterBlackout=false;SN.U.Counter(a)},RewriteAjaxAction:function(a){if(document.location.protocol=="https:"&&a.substr(0,5)=="http:"){return a.replace(/^http:\/\/[^:\/]+/,"https://"+document.location.host)}else{return a}},FormXHR:function(a){$.ajax({type:"POST",dataType:"xml",url:SN.U.RewriteAjaxAction(a.attr("action")),data:a.serialize()+"&ajax=1",beforeSend:function(b){a.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled)},error:function(d,e,c){var b=null;if(d.responseXML){b=$("#error",d.responseXML).text()}alert(b||c||e);a.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).removeAttr(SN.C.S.Disabled)},success:function(b,c){if(typeof($("form",b)[0])!="undefined"){form_new=document._importNode($("form",b)[0],true);a.replaceWith(form_new)}else{if(typeof($("p",b)[0])!="undefined"){a.replaceWith(document._importNode($("p",b)[0],true))}else{alert("Unknown error.")}}}})},FormNoticeXHR:function(b){SN.C.I.NoticeDataGeo={};b.append('<input type="hidden" name="ajax" value="1"/>');b.attr("action",SN.U.RewriteAjaxAction(b.attr("action")));var c=function(d,e){b.append($('<p class="form_response"></p>').addClass(d).text(e))};var a=function(){b.find(".form_response").remove()};b.ajaxForm({dataType:"xml",timeout:"60000",beforeSend:function(d){if(b.find(".notice_data-text:first").val()==""){b.addClass(SN.C.S.Warning);return false}b.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled);SN.U.normalizeGeoData(b);return true},error:function(f,g,e){b.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).removeAttr(SN.C.S.Disabled,SN.C.S.Disabled);a();if(g=="timeout"){c("error","Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.")}else{var d=SN.U.GetResponseXML(f);if($("."+SN.C.S.Error,d).length>0){b.append(document._importNode($("."+SN.C.S.Error,d)[0],true))}else{if(parseInt(f.status)===0||jQuery.inArray(parseInt(f.status),SN.C.I.HTTP20x30x)>=0){b.resetForm().find(".attach-status").remove();SN.U.FormNoticeEnhancements(b)}else{c("error","(Sorry! We had trouble sending your notice ("+f.status+" "+f.statusText+"). Please report the problem to the site administrator if this happens again.")}}}},success:function(i,f){a();var o=$("#"+SN.C.S.Error,i);if(o.length>0){c("error",o.text())}else{if($("body")[0].id=="bookmarklet"){self.close()}var d=$("#"+SN.C.S.CommandResult,i);if(d.length>0){c("success",d.text())}else{var n=document._importNode($("li",i)[0],true);var j=$("#notices_primary .notices:first");var l=b.closest("li.notice-reply");if(l.length>0){var k=b.closest(".threaded-replies");var m=k.find(".notice-reply-placeholder");l.remove();var e=$(n).attr("id");if($("#"+e).length==0){$(n).insertBefore(m)}else{}m.show()}else{if(j.length>0&&SN.U.belongsOnTimeline(n)){if($("#"+n.id).length===0){var h=b.find("[name=inreplyto]").val();var g="#notices_primary #notice-"+h;if($("body")[0].id=="conversation"){if(h.length>0&&$(g+" .notices").length<1){$(g).append('<ul class="notices"></ul>')}$($(g+" .notices")[0]).append(n)}else{j.prepend(n)}$("#"+n.id).css({display:"none"}).fadeIn(2500);SN.U.NoticeWithAttachment($("#"+n.id));SN.U.switchInputFormTab("placeholder")}}else{c("success",$("title",i).text())}}}b.resetForm();b.find("[name=inreplyto]").val("");b.find(".attach-status").remove();SN.U.FormNoticeEnhancements(b)}},complete:function(d,e){b.removeClass(SN.C.S.Processing).find(".submit").removeAttr(SN.C.S.Disabled).removeClass(SN.C.S.Disabled);b.find("[name=lat]").val(SN.C.I.NoticeDataGeo.NLat);b.find("[name=lon]").val(SN.C.I.NoticeDataGeo.NLon);b.find("[name=location_ns]").val(SN.C.I.NoticeDataGeo.NLNS);b.find("[name=location_id]").val(SN.C.I.NoticeDataGeo.NLID);b.find("[name=notice_data-geo]").attr("checked",SN.C.I.NoticeDataGeo.NDG)}})},normalizeGeoData:function(a){SN.C.I.NoticeDataGeo.NLat=a.find("[name=lat]").val();SN.C.I.NoticeDataGeo.NLon=a.find("[name=lon]").val();SN.C.I.NoticeDataGeo.NLNS=a.find("[name=location_ns]").val();SN.C.I.NoticeDataGeo.NLID=a.find("[name=location_id]").val();SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked");var b=$.cookie(SN.C.S.NoticeDataGeoCookie);if(b!==null&&b!="disabled"){b=JSON.parse(b);SN.C.I.NoticeDataGeo.NLat=a.find("[name=lat]").val(b.NLat).val();SN.C.I.NoticeDataGeo.NLon=a.find("[name=lon]").val(b.NLon).val();if(b.NLNS){SN.C.I.NoticeDataGeo.NLNS=a.find("[name=location_ns]").val(b.NLNS).val();SN.C.I.NoticeDataGeo.NLID=a.find("[name=location_id]").val(b.NLID).val()}else{a.find("[name=location_ns]").val("");a.find("[name=location_id]").val("")}}if(b=="disabled"){SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked",false).attr("checked")}else{SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked",true).attr("checked")}},GetResponseXML:function(b){try{return b.responseXML}catch(a){return(new DOMParser()).parseFromString(b.responseText,"text/xml")}},NoticeReply:function(){$("#content .notice_reply").live("click",function(c){c.preventDefault();var b=$(this).closest("li.notice");var a=($(".author .nickname",b).length>0)?$($(".author .nickname",b)[0]):$(".author .nickname.uid");SN.U.NoticeInlineReplyTrigger(b,"@"+a.text());return false})},NoticeReplyTo:function(a){},NoticeInlineReplyTrigger:function(i,j){var b=$($(".notice_id",i)[0]).text();var e=i;var f=i.closest(".notices");if(f.hasClass("threaded-replies")){e=f.closest(".notice")}else{f=$("ul.threaded-replies",i);if(f.length==0){f=$('<ul class="notices threaded-replies xoxo"></ul>');i.append(f)}}var k=$(".notice-reply-form",f);var d=function(){k.find("input[name=inreplyto]").val(b);var n=k.find("textarea");if(n.length==0){throw"No textarea"}var m="";if(j){m=j+" "}n.val(m+n.val().replace(RegExp(m,"i"),""));n.data("initialText",$.trim(j+""));n.focus();if(n[0].setSelectionRange){var l=n.val().length;n[0].setSelectionRange(l,l)}};if(k.length>0){d()}else{var h=f.find("li.notice-reply-placeholder").hide();var g=$("li.notice-reply",f);if(g.length==0){g=$('<li class="notice-reply"></li>');var c=function(l){var m=document._importNode(l,true);g.append(m);f.append(g);var n=k=$(m);SN.Init.NoticeFormSetup(n);d()};if(SN.C.I.NoticeFormMaster){c(SN.C.I.NoticeFormMaster)}else{var a=$("#form_notice").attr("action");$.get(a,{ajax:1},function(l,n,m){c($("form",l)[0])})}}}},NoticeInlineReplyPlaceholder:function(b){var a=b.find("ul.threaded-replies");var c=$('<li class="notice-reply-placeholder"><input class="placeholder"></li>');c.find("input").val(SN.msg("reply_placeholder"));a.append(c)},NoticeInlineReplySetup:function(){$("li.notice-reply-placeholder input").live("focus",function(){var a=$(this).closest("li.notice");SN.U.NoticeInlineReplyTrigger(a);return false});$("li.notice-reply-comments a").live("click",function(){var a=$(this).attr("href");var b=$(this).closest(".threaded-replies");$.get(a,{ajax:1},function(d,f,e){var c=$(".threaded-replies",d);if(c.length){b.replaceWith(document._importNode(c[0],true))}});return false})},NoticeRepeat:function(){$(".form_repeat").live("click",function(a){a.preventDefault();SN.U.NoticeRepeatConfirmation($(this));return false})},NoticeRepeatConfirmation:function(a){var c=a.find(".submit");var b=c.clone();b.addClass("submit_dialogbox").removeClass("submit");a.append(b);b.bind("click",function(){SN.U.FormXHR(a);return false});c.hide();a.addClass("dialogbox").append('<button class="close">&#215;</button>').closest(".notice-options").addClass("opaque");a.find("button.close").click(function(){$(this).remove();a.removeClass("dialogbox").closest(".notice-options").removeClass("opaque");a.find(".submit_dialogbox").remove();a.find(".submit").show();return false})},NoticeAttachments:function(){$(".notice a.attachment").each(function(){SN.U.NoticeWithAttachment($(this).closest(".notice"))})},NoticeWithAttachment:function(b){if(b.find(".attachment").length===0){return}var a=b.find(".attachment.more");if(a.length>0){$(a[0]).click(function(){var c=$(this);c.addClass(SN.C.S.Processing);$.get(c.attr("href")+"/ajax",null,function(d){c.parent(".entry-content").html($(d).find("#attachment_view .entry-content").html())});return false}).attr("title",SN.msg("showmore_tooltip"))}},NoticeDataAttach:function(b){var a=b.find("input[type=file]");a.change(function(f){b.find(".attach-status").remove();var d=$(this).val();if(!d){return false}var c=$('<div class="attach-status '+SN.C.S.Success+'"><code></code> <button class="close">&#215;</button></div>');c.find("code").text(d);c.find("button").click(function(){c.remove();a.val("");return false});b.append(c);if(typeof this.files=="object"){for(var e=0;e<this.files.length;e++){SN.U.PreviewAttach(b,this.files[e])}}})},maxFileSize:function(b){var a=$(b).find("input[name=MAX_FILE_SIZE]").attr("value");if(a){return parseInt(a)}else{return 0}},PreviewAttach:function(d,c){var e=c.type+" "+Math.round(c.size/1024)+"KB";var f=true;var h;if(typeof window.createObjectURL!="undefined"){h=function(i,j){j(window.createObjectURL(i))}}else{if(typeof window.FileReader!="undefined"){h=function(j,k){var i=new FileReader();i.onload=function(l){k(i.result)};i.readAsDataURL(j)}}else{f=false}}var a=["image/png","image/jpeg","image/gif","image/svg+xml"];if($.inArray(c.type,a)==-1){f=false}var g=8*1024*1024;if(c.size>g){f=false}if(f){h(c,function(j){var i=$("<img>").attr("title",e).attr("alt",e).attr("src",j).attr("style","height: 120px");d.find(".attach-status").append(i)})}else{var b=$("<div></div>").text(e);d.find(".attach-status").append(b)}},NoticeLocationAttach:function(a){var e=a.find("[name=lat]");var k=a.find("[name=lon]");var g=a.find("[name=location_ns]").val();var l=a.find("[name=location_id]").val();var b="";var d=a.find("[name=notice_data-geo]");var c=a.find("[name=notice_data-geo]");var j=a.find("label.notice_data-geo");function f(n){j.attr("title",jQuery.trim(j.text())).removeClass("checked");a.find("[name=lat]").val("");a.find("[name=lon]").val("");a.find("[name=location_ns]").val("");a.find("[name=location_id]").val("");a.find("[name=notice_data-geo]").attr("checked",false);$.cookie(SN.C.S.NoticeDataGeoCookie,"disabled",{path:"/"});if(n){a.find(".geo_status_wrapper").removeClass("success").addClass("error");a.find(".geo_status_wrapper .geo_status").text(n)}else{a.find(".geo_status_wrapper").remove()}}function m(n,o){SN.U.NoticeGeoStatus(a,"Looking up place name...");$.getJSON(n,o,function(p){var q,r;if(typeof(p.location_ns)!="undefined"){a.find("[name=location_ns]").val(p.location_ns);q=p.location_ns}if(typeof(p.location_id)!="undefined"){a.find("[name=location_id]").val(p.location_id);r=p.location_id}if(typeof(p.name)=="undefined"){NLN_text=o.lat+";"+o.lon}else{NLN_text=p.name}SN.U.NoticeGeoStatus(a,NLN_text,o.lat,o.lon,p.url);j.attr("title",NoticeDataGeo_text.ShareDisable+" ("+NLN_text+")");a.find("[name=lat]").val(o.lat);a.find("[name=lon]").val(o.lon);a.find("[name=location_ns]").val(q);a.find("[name=location_id]").val(r);a.find("[name=notice_data-geo]").attr("checked",true);var s={NLat:o.lat,NLon:o.lon,NLNS:q,NLID:r,NLN:NLN_text,NLNU:p.url,NDG:true};$.cookie(SN.C.S.NoticeDataGeoCookie,JSON.stringify(s),{path:"/"})})}if(c.length>0){if($.cookie(SN.C.S.NoticeDataGeoCookie)=="disabled"){c.attr("checked",false)}else{c.attr("checked",true)}var h=a.find(".notice_data-geo_wrap");var i=h.attr("data-api");j.attr("title",j.text());c.change(function(){if(c.attr("checked")===true||$.cookie(SN.C.S.NoticeDataGeoCookie)===null){j.attr("title",NoticeDataGeo_text.ShareDisable).addClass("checked");if($.cookie(SN.C.S.NoticeDataGeoCookie)===null||$.cookie(SN.C.S.NoticeDataGeoCookie)=="disabled"){if(navigator.geolocation){SN.U.NoticeGeoStatus(a,"Requesting location from browser...");navigator.geolocation.getCurrentPosition(function(p){a.find("[name=lat]").val(p.coords.latitude);a.find("[name=lon]").val(p.coords.longitude);var q={lat:p.coords.latitude,lon:p.coords.longitude,token:$("#token").val()};m(i,q)},function(p){switch(p.code){case p.PERMISSION_DENIED:f("Location permission denied.");break;case p.TIMEOUT:f("Location lookup timeout.");break}},{timeout:10000})}else{if(e.length>0&&k.length>0){var n={lat:e,lon:k,token:$("#token").val()};m(i,n)}else{f();c.remove();j.remove()}}}else{var o=JSON.parse($.cookie(SN.C.S.NoticeDataGeoCookie));a.find("[name=lat]").val(o.NLat);a.find("[name=lon]").val(o.NLon);a.find("[name=location_ns]").val(o.NLNS);a.find("[name=location_id]").val(o.NLID);a.find("[name=notice_data-geo]").attr("checked",o.NDG);SN.U.NoticeGeoStatus(a,o.NLN,o.NLat,o.NLon,o.NLNU);j.attr("title",NoticeDataGeo_text.ShareDisable+" ("+o.NLN+")").addClass("checked")}}else{f()}}).change()}},NoticeGeoStatus:function(e,a,f,g,c){var h=e.find(".geo_status_wrapper");if(h.length==0){h=$('<div class="'+SN.C.S.Success+' geo_status_wrapper"><button class="close" style="float:right">&#215;</button><div class="geo_status"></div></div>');h.find("button.close").click(function(){e.find("[name=notice_data-geo]").removeAttr("checked").change();return false});e.append(h)}var b;if(c){b=$("<a></a>").attr("href",c)}else{b=$("<span></span>")}b.text(a);if(f||g){var d=f+";"+g;b.attr("title",d);if(!a){b.text(d)}}h.find(".geo_status").empty().append(b)},NewDirectMessage:function(){NDM=$(".entity_send-a-message a");NDM.attr({href:NDM.attr("href")+"&ajax=1"});NDM.bind("click",function(){var a=$(".entity_send-a-message form");if(a.length===0){$(this).addClass(SN.C.S.Processing);$.get(NDM.attr("href"),null,function(b){$(".entity_send-a-message").append(document._importNode($("form",b)[0],true));a=$(".entity_send-a-message .form_notice");SN.U.FormNoticeXHR(a);SN.U.FormNoticeEnhancements(a);a.append('<button class="close">&#215;</button>');$(".entity_send-a-message button").click(function(){a.hide();return false});NDM.removeClass(SN.C.S.Processing)})}else{a.show();$(".entity_send-a-message textarea").focus()}return false})},GetFullYear:function(c,d,a){var b=new Date();b.setFullYear(c,d,a);return b},StatusNetInstance:{Set:function(b){var a=SN.U.StatusNetInstance.Get();if(a!==null){b=$.extend(a,b)}$.cookie(SN.C.S.StatusNetInstance,JSON.stringify(b),{path:"/",expires:SN.U.GetFullYear(2029,0,1)})},Get:function(){var a=$.cookie(SN.C.S.StatusNetInstance);if(a!==null){return JSON.parse(a)}return null},Delete:function(){$.cookie(SN.C.S.StatusNetInstance,null)}},belongsOnTimeline:function(b){var a=$("body").attr("id");if(a=="public"){return true}var c=$("#nav_profile a").attr("href");if(c){var d=$(b).find(".vcard.author a.url").attr("href");if(d==c){if(a=="all"||a=="showstream"){return true}}}return false},switchInputFormTab:function(a){$(".input_form_nav_tab.current").removeClass("current");if(a=="placeholder"){$("#input_form_nav_status").addClass("current")}else{$("#input_form_nav_"+a).addClass("current")}$(".input_form.current").removeClass("current");$("#input_form_"+a).addClass("current").find(".ajax-notice").each(function(){var b=$(this);SN.Init.NoticeFormSetup(b)}).find("textarea:first").focus()}},Init:{NoticeForm:function(){if($("body.user_in").length>0){$("#input_form_placeholder input.placeholder").focus(function(){SN.U.switchInputFormTab("status")});$("body").bind("click",function(g){var d=$("#content .input_forms div.current");if(d.length>0){if($("#content .input_forms").has(g.target).length==0){var a=d.find('textarea, input[type=text], input[type=""]');var c=false;a.each(function(){c=c||$(this).val()});if(!c){SN.U.switchInputFormTab("placeholder")}}}var b=$("li.notice-reply");if(b.length>0){var f=$(g.target);b.each(function(){var j=$(this);if(j.has(g.target).length==0){var h=j.find(".notice_data-text:first");var i=$.trim(h.val());if(i==""||i==h.data("initialText")){var e=j.closest("li.notice");j.remove();e.find("li.notice-reply-placeholder").show()}}})}})}},NoticeFormSetup:function(a){if(!a.data("NoticeFormSetup")){SN.U.NoticeLocationAttach(a);SN.U.FormNoticeXHR(a);SN.U.FormNoticeEnhancements(a);SN.U.NoticeDataAttach(a);a.data("NoticeFormSetup",true)}},Notices:function(){if($("body.user_in").length>0){var a=$(".form_notice:first");if(a.length>0){SN.C.I.NoticeFormMaster=document._importNode(a[0],true)}SN.U.NoticeRepeat();SN.U.NoticeReply();SN.U.NoticeInlineReplySetup()}SN.U.NoticeAttachments()},EntityActions:function(){if($("body.user_in").length>0){SN.U.NewDirectMessage()}},Login:function(){if(SN.U.StatusNetInstance.Get()!==null){var a=SN.U.StatusNetInstance.Get().Nickname;if(a!==null){$("#form_login #nickname").val(a)}}$("#form_login").bind("submit",function(){SN.U.StatusNetInstance.Set({Nickname:$("#form_login #nickname").val()});return true})},AjaxForms:function(){$("form.ajax").live("submit",function(){SN.U.FormXHR($(this));return false})},UploadForms:function(){$("input[type=file]").change(function(d){if(typeof this.files=="object"&&this.files.length>0){var c=0;for(var b=0;b<this.files.length;b++){c+=this.files[b].size}var a=SN.U.maxFileSize($(this.form));if(a>0&&c>a){var e="File too large: maximum upload size is %d bytes.";alert(e.replace("%d",a));$(this).val("");d.preventDefault();return false}}})}}};$(document).ready(function(){SN.Init.AjaxForms();SN.Init.UploadForms();if($("."+SN.C.S.FormNotice).length>0){SN.Init.NoticeForm()}if($("#content .notices").length>0){SN.Init.Notices()}if($("#content .entity_actions").length>0){SN.Init.EntityActions()}if($("#form_login").length>0){SN.Init.Login()}});if(!document.ELEMENT_NODE){document.ELEMENT_NODE=1;document.ATTRIBUTE_NODE=2;document.TEXT_NODE=3;document.CDATA_SECTION_NODE=4;document.ENTITY_REFERENCE_NODE=5;document.ENTITY_NODE=6;document.PROCESSING_INSTRUCTION_NODE=7;document.COMMENT_NODE=8;document.DOCUMENT_NODE=9;document.DOCUMENT_TYPE_NODE=10;document.DOCUMENT_FRAGMENT_NODE=11;document.NOTATION_NODE=12}document._importNode=function(e,a){switch(e.nodeType){case document.ELEMENT_NODE:var d=document.createElement(e.nodeName);if(e.attributes&&e.attributes.length>0){for(var c=0,b=e.attributes.length;c<b;){if(e.attributes[c].nodeName=="class"){d.className=e.getAttribute(e.attributes[c++].nodeName)}else{d.setAttribute(e.attributes[c].nodeName,e.getAttribute(e.attributes[c++].nodeName))}}}if(a&&e.childNodes&&e.childNodes.length>0){for(var c=0,b=e.childNodes.length;c<b;){d.appendChild(document._importNode(e.childNodes[c++],a))}}return d;break;case document.TEXT_NODE:case document.CDATA_SECTION_NODE:case document.COMMENT_NODE:return document.createTextNode(e.nodeValue);break}};if(typeof navigator.geolocation=="undefined"||navigator.geolocation.shim){(function(){(function(){if(window.google&&google.gears){return}var c=null;if(typeof GearsFactory!="undefined"){c=new GearsFactory()}else{try{c=new ActiveXObject("Gears.Factory");if(c.getBuildInfo().indexOf("ie_mobile")!=-1){c.privateSetGlobalObject(this)}}catch(d){if((typeof navigator.mimeTypes!="undefined")&&navigator.mimeTypes["application/x-googlegears"]){c=document.createElement("object");c.style.display="none";c.width=0;c.height=0;c.type="application/x-googlegears";document.documentElement.appendChild(c)}}}if(!c){return}if(!window.google){google={}}if(!google.gears){google.gears={factory:c}}})();var a=(function(){var d=google.gears.factory.create("beta.geolocation");var c=function(f,e){return function(g){f(g);e.lastPosition=g}};return{shim:true,type:"Gears",lastPosition:null,getCurrentPosition:function(e,g,h){var f=this;var i=c(e,f);d.getCurrentPosition(i,g,h)},watchPosition:function(e,f,g){d.watchPosition(e,f,g)},clearWatch:function(e){d.clearWatch(e)},getPermission:function(g,e,f){d.getPermission(g,e,f)}}});var b=(function(){var i=false;var e=function(){if(!d()&&!i){i=true;var j=document.createElement("script");j.src=(document.location.protocol=="https:"?"https://":"http://")+"www.google.com/jsapi?callback=_google_loader_apiLoaded";j.type="text/javascript";document.getElementsByTagName("body")[0].appendChild(j)}};var c=[];var h=function(j){c.push(j)};var f=function(){if(d()){while(c.length>0){var j=c.pop();j()}}};window._google_loader_apiLoaded=function(){f()};var d=function(){return(window.google&&google.loader)};var g=function(j){if(d()){return true}h(j);e();return false};e();return{shim:true,type:"ClientLocation",lastPosition:null,getCurrentPosition:function(k,n,o){var m=this;if(!g(function(){m.getCurrentPosition(k,n,o)})){return}if(google.loader.ClientLocation){var l=google.loader.ClientLocation;var j={coords:{latitude:l.latitude,longitude:l.longitude,altitude:null,accuracy:43000,altitudeAccuracy:null,heading:null,speed:null},address:{city:l.address.city,country:l.address.country,country_code:l.address.country_code,region:l.address.region},timestamp:new Date()};k(j);this.lastPosition=j}else{if(n==="function"){n({code:3,message:"Using the Google ClientLocation API and it is not able to calculate a location."})}}},watchPosition:function(j,l,m){this.getCurrentPosition(j,l,m);var k=this;var n=setInterval(function(){k.getCurrentPosition(j,l,m)},10000);return n},clearWatch:function(j){clearInterval(j)},getPermission:function(l,j,k){return true}}});navigator.geolocation=(window.google&&google.gears)?a():b()})()};
++var SN={C:{I:{CounterBlackout:false,MaxLength:140,PatternUsername:/^[0-9a-zA-Z\-_.]*$/,HTTP20x30x:[200,201,202,203,204,205,206,300,301,302,303,304,305,306,307],NoticeFormMaster:null},S:{Disabled:"disabled",Warning:"warning",Error:"error",Success:"success",Processing:"processing",CommandResult:"command_result",FormNotice:"form_notice",NoticeDataGeo:"notice_data-geo",NoticeDataGeoCookie:"NoticeDataGeo",NoticeDataGeoSelected:"notice_data-geo_selected",StatusNetInstance:"StatusNetInstance"}},messages:{},msg:function(a){if(typeof SN.messages[a]=="undefined"){return"["+a+"]"}else{return SN.messages[a]}},U:{FormNoticeEnhancements:function(b){if(jQuery.data(b[0],"ElementData")===undefined){MaxLength=b.find(".count").text();if(typeof(MaxLength)=="undefined"){MaxLength=SN.C.I.MaxLength}jQuery.data(b[0],"ElementData",{MaxLength:MaxLength});SN.U.Counter(b);NDT=b.find(".notice_data-text:first");NDT.bind("keyup",function(c){SN.U.Counter(b)});var a=function(c){window.setTimeout(function(){SN.U.Counter(b)},50)};NDT.bind("cut",a).bind("paste",a)}else{b.find(".count").text(jQuery.data(b[0],"ElementData").MaxLength)}},Counter:function(d){SN.C.I.FormNoticeCurrent=d;var b=jQuery.data(d[0],"ElementData").MaxLength;if(b<=0){return}var c=b-SN.U.CharacterCount(d);var a=d.find(".count");if(c.toString()!=a.text()){if(!SN.C.I.CounterBlackout||c===0){if(a.text()!=String(c)){a.text(c)}if(c<0){d.addClass(SN.C.S.Warning)}else{d.removeClass(SN.C.S.Warning)}if(!SN.C.I.CounterBlackout){SN.C.I.CounterBlackout=true;SN.C.I.FormNoticeCurrent=d;window.setTimeout("SN.U.ClearCounterBlackout(SN.C.I.FormNoticeCurrent);",500)}}}},CharacterCount:function(a){return a.find(".notice_data-text:first").val().length},ClearCounterBlackout:function(a){SN.C.I.CounterBlackout=false;SN.U.Counter(a)},RewriteAjaxAction:function(a){if(document.location.protocol=="https:"&&a.substr(0,5)=="http:"){return a.replace(/^http:\/\/[^:\/]+/,"https://"+document.location.host)}else{return a}},FormXHR:function(a){$.ajax({type:"POST",dataType:"xml",url:SN.U.RewriteAjaxAction(a.attr("action")),data:a.serialize()+"&ajax=1",beforeSend:function(b){a.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled)},error:function(d,e,c){var b=null;if(d.responseXML){b=$("#error",d.responseXML).text()}alert(b||c||e);a.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).removeAttr(SN.C.S.Disabled)},success:function(b,c){if(typeof($("form",b)[0])!="undefined"){form_new=document._importNode($("form",b)[0],true);a.replaceWith(form_new)}else{if(typeof($("p",b)[0])!="undefined"){a.replaceWith(document._importNode($("p",b)[0],true))}else{alert("Unknown error.")}}}})},FormNoticeXHR:function(b){SN.C.I.NoticeDataGeo={};b.append('<input type="hidden" name="ajax" value="1"/>');b.attr("action",SN.U.RewriteAjaxAction(b.attr("action")));var c=function(d,e){b.append($('<p class="form_response"></p>').addClass(d).text(e))};var a=function(){b.find(".form_response").remove()};b.ajaxForm({dataType:"xml",timeout:"60000",beforeSend:function(d){if(b.find(".notice_data-text:first").val()==""){b.addClass(SN.C.S.Warning);return false}b.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled);SN.U.normalizeGeoData(b);return true},error:function(f,g,e){b.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).removeAttr(SN.C.S.Disabled,SN.C.S.Disabled);a();if(g=="timeout"){c("error","Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.")}else{var d=SN.U.GetResponseXML(f);if($("."+SN.C.S.Error,d).length>0){b.append(document._importNode($("."+SN.C.S.Error,d)[0],true))}else{if(parseInt(f.status)===0||jQuery.inArray(parseInt(f.status),SN.C.I.HTTP20x30x)>=0){b.resetForm().find(".attach-status").remove();SN.U.FormNoticeEnhancements(b)}else{c("error","(Sorry! We had trouble sending your notice ("+f.status+" "+f.statusText+"). Please report the problem to the site administrator if this happens again.")}}}},success:function(i,f){a();var o=$("#"+SN.C.S.Error,i);if(o.length>0){c("error",o.text())}else{if($("body")[0].id=="bookmarklet"){self.close()}var d=$("#"+SN.C.S.CommandResult,i);if(d.length>0){c("success",d.text())}else{var n=document._importNode($("li",i)[0],true);var j=$("#notices_primary .notices:first");var l=b.closest("li.notice-reply");if(l.length>0){var k=b.closest(".threaded-replies");var m=k.find(".notice-reply-placeholder");l.remove();var e=$(n).attr("id");if($("#"+e).length==0){$(n).insertBefore(m)}else{}m.show()}else{if(j.length>0&&SN.U.belongsOnTimeline(n)){if($("#"+n.id).length===0){var h=b.find("[name=inreplyto]").val();var g="#notices_primary #notice-"+h;if($("body")[0].id=="conversation"){if(h.length>0&&$(g+" .notices").length<1){$(g).append('<ul class="notices"></ul>')}$($(g+" .notices")[0]).append(n)}else{j.prepend(n)}$("#"+n.id).css({display:"none"}).fadeIn(2500);SN.U.NoticeWithAttachment($("#"+n.id));SN.U.switchInputFormTab("placeholder")}}else{c("success",$("title",i).text())}}}b.resetForm();b.find("[name=inreplyto]").val("");b.find(".attach-status").remove();SN.U.FormNoticeEnhancements(b)}},complete:function(d,e){b.removeClass(SN.C.S.Processing).find(".submit").removeAttr(SN.C.S.Disabled).removeClass(SN.C.S.Disabled);b.find("[name=lat]").val(SN.C.I.NoticeDataGeo.NLat);b.find("[name=lon]").val(SN.C.I.NoticeDataGeo.NLon);b.find("[name=location_ns]").val(SN.C.I.NoticeDataGeo.NLNS);b.find("[name=location_id]").val(SN.C.I.NoticeDataGeo.NLID);b.find("[name=notice_data-geo]").attr("checked",SN.C.I.NoticeDataGeo.NDG)}})},FormProfileSearchXHR:function(a){$.ajax({type:"POST",dataType:"xml",url:a.attr("action"),data:a.serialize()+"&ajax=1",beforeSend:function(b){a.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled)},error:function(c,d,b){alert(b||d)},success:function(d,f){var b=$("#profile_search_results");if(typeof($("ul",d)[0])!="undefined"){var c=document._importNode($("ul",d)[0],true);b.replaceWith(c)}else{var e=$("<li/>").append(document._importNode($("p",d)[0],true));b.html(e)}a.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,false)}})},FormPeopletagsXHR:function(a){$.ajax({type:"POST",dataType:"xml",url:a.attr("action"),data:a.serialize()+"&ajax=1",beforeSend:function(b){a.addClass(SN.C.S.Processing).find(".submit").addClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,SN.C.S.Disabled)},error:function(c,d,b){alert(b||d)},success:function(d,e){var c=a.parents(".entity_tags");if(typeof($(".entity_tags",d)[0])!="undefined"){var b=document._importNode($(".entity_tags",d)[0],true);$(b).find(".editable").append($('<button class="peopletags_edit_button"/>'));c.replaceWith(b)}else{c.find("p").remove();c.append(document._importNode($("p",d)[0],true));a.removeClass(SN.C.S.Processing).find(".submit").removeClass(SN.C.S.Disabled).attr(SN.C.S.Disabled,false)}}})},normalizeGeoData:function(a){SN.C.I.NoticeDataGeo.NLat=a.find("[name=lat]").val();SN.C.I.NoticeDataGeo.NLon=a.find("[name=lon]").val();SN.C.I.NoticeDataGeo.NLNS=a.find("[name=location_ns]").val();SN.C.I.NoticeDataGeo.NLID=a.find("[name=location_id]").val();SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked");var b=$.cookie(SN.C.S.NoticeDataGeoCookie);if(b!==null&&b!="disabled"){b=JSON.parse(b);SN.C.I.NoticeDataGeo.NLat=a.find("[name=lat]").val(b.NLat).val();SN.C.I.NoticeDataGeo.NLon=a.find("[name=lon]").val(b.NLon).val();if(b.NLNS){SN.C.I.NoticeDataGeo.NLNS=a.find("[name=location_ns]").val(b.NLNS).val();SN.C.I.NoticeDataGeo.NLID=a.find("[name=location_id]").val(b.NLID).val()}else{a.find("[name=location_ns]").val("");a.find("[name=location_id]").val("")}}if(b=="disabled"){SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked",false).attr("checked")}else{SN.C.I.NoticeDataGeo.NDG=a.find("[name=notice_data-geo]").attr("checked",true).attr("checked")}},GetResponseXML:function(b){try{return b.responseXML}catch(a){return(new DOMParser()).parseFromString(b.responseText,"text/xml")}},NoticeReply:function(){$("#content .notice_reply").live("click",function(c){c.preventDefault();var b=$(this).closest("li.notice");var a=($(".author .nickname",b).length>0)?$($(".author .nickname",b)[0]):$(".author .nickname.uid");SN.U.NoticeInlineReplyTrigger(b,"@"+a.text());return false})},NoticeReplyTo:function(a){},NoticeInlineReplyTrigger:function(i,j){var b=$($(".notice_id",i)[0]).text();var e=i;var f=i.closest(".notices");if(f.hasClass("threaded-replies")){e=f.closest(".notice")}else{f=$("ul.threaded-replies",i);if(f.length==0){f=$('<ul class="notices threaded-replies xoxo"></ul>');i.append(f)}}var k=$(".notice-reply-form",f);var d=function(){k.find("input[name=inreplyto]").val(b);var n=k.find("textarea");if(n.length==0){throw"No textarea"}var m="";if(j){m=j+" "}n.val(m+n.val().replace(RegExp(m,"i"),""));n.data("initialText",$.trim(j+""));n.focus();if(n[0].setSelectionRange){var l=n.val().length;n[0].setSelectionRange(l,l)}};if(k.length>0){d()}else{var h=f.find("li.notice-reply-placeholder").hide();var g=$("li.notice-reply",f);if(g.length==0){g=$('<li class="notice-reply"></li>');var c=function(l){var m=document._importNode(l,true);g.append(m);f.append(g);var n=k=$(m);SN.Init.NoticeFormSetup(n);d()};if(SN.C.I.NoticeFormMaster){c(SN.C.I.NoticeFormMaster)}else{var a=$("#form_notice").attr("action");$.get(a,{ajax:1},function(l,n,m){c($("form",l)[0])})}}}},NoticeInlineReplyPlaceholder:function(b){var a=b.find("ul.threaded-replies");var c=$('<li class="notice-reply-placeholder"><input class="placeholder"></li>');c.find("input").val(SN.msg("reply_placeholder"));a.append(c)},NoticeInlineReplySetup:function(){$("li.notice-reply-placeholder input").live("focus",function(){var a=$(this).closest("li.notice");SN.U.NoticeInlineReplyTrigger(a);return false});$("li.notice-reply-comments a").live("click",function(){var a=$(this).attr("href");var b=$(this).closest(".threaded-replies");$.get(a,{ajax:1},function(d,f,e){var c=$(".threaded-replies",d);if(c.length){b.replaceWith(document._importNode(c[0],true))}});return false})},NoticeRepeat:function(){$(".form_repeat").live("click",function(a){a.preventDefault();SN.U.NoticeRepeatConfirmation($(this));return false})},NoticeRepeatConfirmation:function(a){var c=a.find(".submit");var b=c.clone();b.addClass("submit_dialogbox").removeClass("submit");a.append(b);b.bind("click",function(){SN.U.FormXHR(a);return false});c.hide();a.addClass("dialogbox").append('<button class="close">&#215;</button>').closest(".notice-options").addClass("opaque");a.find("button.close").click(function(){$(this).remove();a.removeClass("dialogbox").closest(".notice-options").removeClass("opaque");a.find(".submit_dialogbox").remove();a.find(".submit").show();return false})},NoticeAttachments:function(){$(".notice a.attachment").each(function(){SN.U.NoticeWithAttachment($(this).closest(".notice"))})},NoticeWithAttachment:function(b){if(b.find(".attachment").length===0){return}var a=b.find(".attachment.more");if(a.length>0){$(a[0]).click(function(){var c=$(this);c.addClass(SN.C.S.Processing);$.get(c.attr("href")+"/ajax",null,function(d){c.parent(".entry-content").html($(d).find("#attachment_view .entry-content").html())});return false}).attr("title",SN.msg("showmore_tooltip"))}},NoticeDataAttach:function(b){var a=b.find("input[type=file]");a.change(function(f){b.find(".attach-status").remove();var d=$(this).val();if(!d){return false}var c=$('<div class="attach-status '+SN.C.S.Success+'"><code></code> <button class="close">&#215;</button></div>');c.find("code").text(d);c.find("button").click(function(){c.remove();a.val("");return false});b.append(c);if(typeof this.files=="object"){for(var e=0;e<this.files.length;e++){SN.U.PreviewAttach(b,this.files[e])}}})},maxFileSize:function(b){var a=$(b).find("input[name=MAX_FILE_SIZE]").attr("value");if(a){return parseInt(a)}else{return 0}},PreviewAttach:function(d,c){var e=c.type+" "+Math.round(c.size/1024)+"KB";var f=true;var h;if(typeof window.createObjectURL!="undefined"){h=function(i,j){j(window.createObjectURL(i))}}else{if(typeof window.FileReader!="undefined"){h=function(j,k){var i=new FileReader();i.onload=function(l){k(i.result)};i.readAsDataURL(j)}}else{f=false}}var a=["image/png","image/jpeg","image/gif","image/svg+xml"];if($.inArray(c.type,a)==-1){f=false}var g=8*1024*1024;if(c.size>g){f=false}if(f){h(c,function(j){var i=$("<img>").attr("title",e).attr("alt",e).attr("src",j).attr("style","height: 120px");d.find(".attach-status").append(i)})}else{var b=$("<div></div>").text(e);d.find(".attach-status").append(b)}},NoticeLocationAttach:function(a){var e=a.find("[name=lat]");var k=a.find("[name=lon]");var g=a.find("[name=location_ns]").val();var l=a.find("[name=location_id]").val();var b="";var d=a.find("[name=notice_data-geo]");var c=a.find("[name=notice_data-geo]");var j=a.find("label.notice_data-geo");function f(n){j.attr("title",jQuery.trim(j.text())).removeClass("checked");a.find("[name=lat]").val("");a.find("[name=lon]").val("");a.find("[name=location_ns]").val("");a.find("[name=location_id]").val("");a.find("[name=notice_data-geo]").attr("checked",false);$.cookie(SN.C.S.NoticeDataGeoCookie,"disabled",{path:"/"});if(n){a.find(".geo_status_wrapper").removeClass("success").addClass("error");a.find(".geo_status_wrapper .geo_status").text(n)}else{a.find(".geo_status_wrapper").remove()}}function m(n,o){SN.U.NoticeGeoStatus(a,"Looking up place name...");$.getJSON(n,o,function(p){var q,r;if(typeof(p.location_ns)!="undefined"){a.find("[name=location_ns]").val(p.location_ns);q=p.location_ns}if(typeof(p.location_id)!="undefined"){a.find("[name=location_id]").val(p.location_id);r=p.location_id}if(typeof(p.name)=="undefined"){NLN_text=o.lat+";"+o.lon}else{NLN_text=p.name}SN.U.NoticeGeoStatus(a,NLN_text,o.lat,o.lon,p.url);j.attr("title",NoticeDataGeo_text.ShareDisable+" ("+NLN_text+")");a.find("[name=lat]").val(o.lat);a.find("[name=lon]").val(o.lon);a.find("[name=location_ns]").val(q);a.find("[name=location_id]").val(r);a.find("[name=notice_data-geo]").attr("checked",true);var s={NLat:o.lat,NLon:o.lon,NLNS:q,NLID:r,NLN:NLN_text,NLNU:p.url,NDG:true};$.cookie(SN.C.S.NoticeDataGeoCookie,JSON.stringify(s),{path:"/"})})}if(c.length>0){if($.cookie(SN.C.S.NoticeDataGeoCookie)=="disabled"){c.attr("checked",false)}else{c.attr("checked",true)}var h=a.find(".notice_data-geo_wrap");var i=h.attr("data-api");j.attr("title",j.text());c.change(function(){if(c.attr("checked")===true||$.cookie(SN.C.S.NoticeDataGeoCookie)===null){j.attr("title",NoticeDataGeo_text.ShareDisable).addClass("checked");if($.cookie(SN.C.S.NoticeDataGeoCookie)===null||$.cookie(SN.C.S.NoticeDataGeoCookie)=="disabled"){if(navigator.geolocation){SN.U.NoticeGeoStatus(a,"Requesting location from browser...");navigator.geolocation.getCurrentPosition(function(p){a.find("[name=lat]").val(p.coords.latitude);a.find("[name=lon]").val(p.coords.longitude);var q={lat:p.coords.latitude,lon:p.coords.longitude,token:$("#token").val()};m(i,q)},function(p){switch(p.code){case p.PERMISSION_DENIED:f("Location permission denied.");break;case p.TIMEOUT:f("Location lookup timeout.");break}},{timeout:10000})}else{if(e.length>0&&k.length>0){var n={lat:e,lon:k,token:$("#token").val()};m(i,n)}else{f();c.remove();j.remove()}}}else{var o=JSON.parse($.cookie(SN.C.S.NoticeDataGeoCookie));a.find("[name=lat]").val(o.NLat);a.find("[name=lon]").val(o.NLon);a.find("[name=location_ns]").val(o.NLNS);a.find("[name=location_id]").val(o.NLID);a.find("[name=notice_data-geo]").attr("checked",o.NDG);SN.U.NoticeGeoStatus(a,o.NLN,o.NLat,o.NLon,o.NLNU);j.attr("title",NoticeDataGeo_text.ShareDisable+" ("+o.NLN+")").addClass("checked")}}else{f()}}).change()}},NoticeGeoStatus:function(e,a,f,g,c){var h=e.find(".geo_status_wrapper");if(h.length==0){h=$('<div class="'+SN.C.S.Success+' geo_status_wrapper"><button class="close" style="float:right">&#215;</button><div class="geo_status"></div></div>');h.find("button.close").click(function(){e.find("[name=notice_data-geo]").removeAttr("checked").change();return false});e.append(h)}var b;if(c){b=$("<a></a>").attr("href",c)}else{b=$("<span></span>")}b.text(a);if(f||g){var d=f+";"+g;b.attr("title",d);if(!a){b.text(d)}}h.find(".geo_status").empty().append(b)},NewDirectMessage:function(){NDM=$(".entity_send-a-message a");NDM.attr({href:NDM.attr("href")+"&ajax=1"});NDM.bind("click",function(){var a=$(".entity_send-a-message form");if(a.length===0){$(this).addClass(SN.C.S.Processing);$.get(NDM.attr("href"),null,function(b){$(".entity_send-a-message").append(document._importNode($("form",b)[0],true));a=$(".entity_send-a-message .form_notice");SN.U.FormNoticeXHR(a);SN.U.FormNoticeEnhancements(a);a.append('<button class="close">&#215;</button>');$(".entity_send-a-message button").click(function(){a.hide();return false});NDM.removeClass(SN.C.S.Processing)})}else{a.show();$(".entity_send-a-message textarea").focus()}return false})},GetFullYear:function(c,d,a){var b=new Date();b.setFullYear(c,d,a);return b},StatusNetInstance:{Set:function(b){var a=SN.U.StatusNetInstance.Get();if(a!==null){b=$.extend(a,b)}$.cookie(SN.C.S.StatusNetInstance,JSON.stringify(b),{path:"/",expires:SN.U.GetFullYear(2029,0,1)})},Get:function(){var a=$.cookie(SN.C.S.StatusNetInstance);if(a!==null){return JSON.parse(a)}return null},Delete:function(){$.cookie(SN.C.S.StatusNetInstance,null)}},belongsOnTimeline:function(b){var a=$("body").attr("id");if(a=="public"){return true}var c=$("#nav_profile a").attr("href");if(c){var d=$(b).find(".vcard.author a.url").attr("href");if(d==c){if(a=="all"||a=="showstream"){return true}}}return false},switchInputFormTab:function(a){$(".input_form_nav_tab.current").removeClass("current");if(a=="placeholder"){$("#input_form_nav_status").addClass("current")}else{$("#input_form_nav_"+a).addClass("current")}$(".input_form.current").removeClass("current");$("#input_form_"+a).addClass("current").find(".ajax-notice").each(function(){var b=$(this);SN.Init.NoticeFormSetup(b)}).find("textarea:first").focus()}},Init:{NoticeForm:function(){if($("body.user_in").length>0){$("#input_form_placeholder input.placeholder").focus(function(){SN.U.switchInputFormTab("status")});$("body").bind("click",function(g){var d=$("#content .input_forms div.current");if(d.length>0){if($("#content .input_forms").has(g.target).length==0){var a=d.find('textarea, input[type=text], input[type=""]');var c=false;a.each(function(){c=c||$(this).val()});if(!c){SN.U.switchInputFormTab("placeholder")}}}var b=$("li.notice-reply");if(b.length>0){var f=$(g.target);b.each(function(){var j=$(this);if(j.has(g.target).length==0){var h=j.find(".notice_data-text:first");var i=$.trim(h.val());if(i==""||i==h.data("initialText")){var e=j.closest("li.notice");j.remove();e.find("li.notice-reply-placeholder").show()}}})}})}},NoticeFormSetup:function(a){if(!a.data("NoticeFormSetup")){SN.U.NoticeLocationAttach(a);SN.U.FormNoticeXHR(a);SN.U.FormNoticeEnhancements(a);SN.U.NoticeDataAttach(a);a.data("NoticeFormSetup",true)}},Notices:function(){if($("body.user_in").length>0){var a=$(".form_notice:first");if(a.length>0){SN.C.I.NoticeFormMaster=document._importNode(a[0],true)}SN.U.NoticeRepeat();SN.U.NoticeReply();SN.U.NoticeInlineReplySetup()}SN.U.NoticeAttachments()},EntityActions:function(){if($("body.user_in").length>0){$(".form_user_subscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_unsubscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_group_join").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_group_leave").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_nudge").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_peopletag_subscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_peopletag_unsubscribe").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_add_peopletag").live("click",function(){SN.U.FormXHR($(this));return false});$(".form_user_remove_peopletag").live("click",function(){SN.U.FormXHR($(this));return false});SN.U.NewDirectMessage()}},ProfileSearch:function(){if($("body.user_in").length>0){$(".form_peopletag_edit_user_search input.submit").live("click",function(){SN.U.FormProfileSearchXHR($(this).parents("form"));return false})}},Login:function(){if(SN.U.StatusNetInstance.Get()!==null){var a=SN.U.StatusNetInstance.Get().Nickname;if(a!==null){$("#form_login #nickname").val(a)}}$("#form_login").bind("submit",function(){SN.U.StatusNetInstance.Set({Nickname:$("#form_login #nickname").val()});return true})},PeopletagAutocomplete:function(){$(".form_tag_user #tags").tagInput({tags:SN.C.PtagACData,tagSeparator:" ",animate:false,formatLine:function(d,g,c,f){var a="<b>"+g.tag.substring(0,c.length)+"</b>"+g.tag.substring(c.length);var b=$("<div/>").addClass("mode-"+g.mode);b.append($("<div class='tagInputLineTag'>"+a+" <em class='privacy_mode'>"+g.mode+"</em></div>"));if(g.freq){b.append("<div class='tagInputLineFreq'>"+g.freq+"</div>")}return b}})},PeopleTags:function(){$(".user_profile_tags .editable").append($('<button class="peopletags_edit_button"/>'));$(".peopletags_edit_button").live("click",function(){var a=$(this).parents("dd").eq(0).find("form");if(typeof SN.C.PtagACData==="undefined"){$.getJSON(_peopletagAC+"?token="+$("#token").val(),function(b){SN.C.PtagACData=b;_loadTagInput(SN.Init.PeopletagAutocomplete)})}else{_loadTagInput(SN.Init.PeopletagAutocomplete)}$(this).parents("ul").eq(0).fadeOut(200,function(){a.fadeIn(200).find("input#tags")})});$(".user_profile_tags form .submit").live("click",function(){SN.U.FormPeopletagsXHR($(this).parents("form"));return false})},AjaxForms:function(){$("form.ajax").live("submit",function(){SN.U.FormXHR($(this));return false})},UploadForms:function(){$("input[type=file]").change(function(d){if(typeof this.files=="object"&&this.files.length>0){var c=0;for(var b=0;b<this.files.length;b++){c+=this.files[b].size}var a=SN.U.maxFileSize($(this.form));if(a>0&&c>a){var e="File too large: maximum upload size is %d bytes.";alert(e.replace("%d",a));$(this).val("");d.preventDefault();return false}}})}}};$(document).ready(function(){SN.Init.AjaxForms();SN.Init.UploadForms();if($("."+SN.C.S.FormNotice).length>0){SN.Init.NoticeForm()}if($("#content .notices").length>0){SN.Init.Notices()}if($("#content .entity_actions").length>0){SN.Init.EntityActions()}if($("#form_login").length>0){SN.Init.Login()}if($("#profile_search_results").length>0){SN.Init.ProfileSearch()}if($(".user_profile_tags .editable").length>0){SN.Init.PeopleTags()}});if(!document.ELEMENT_NODE){document.ELEMENT_NODE=1;document.ATTRIBUTE_NODE=2;document.TEXT_NODE=3;document.CDATA_SECTION_NODE=4;document.ENTITY_REFERENCE_NODE=5;document.ENTITY_NODE=6;document.PROCESSING_INSTRUCTION_NODE=7;document.COMMENT_NODE=8;document.DOCUMENT_NODE=9;document.DOCUMENT_TYPE_NODE=10;document.DOCUMENT_FRAGMENT_NODE=11;document.NOTATION_NODE=12}document._importNode=function(e,a){switch(e.nodeType){case document.ELEMENT_NODE:var d=document.createElement(e.nodeName);if(e.attributes&&e.attributes.length>0){for(var c=0,b=e.attributes.length;c<b;){if(e.attributes[c].nodeName=="class"){d.className=e.getAttribute(e.attributes[c++].nodeName)}else{d.setAttribute(e.attributes[c].nodeName,e.getAttribute(e.attributes[c++].nodeName))}}}if(a&&e.childNodes&&e.childNodes.length>0){for(var c=0,b=e.childNodes.length;c<b;){d.appendChild(document._importNode(e.childNodes[c++],a))}}return d;break;case document.TEXT_NODE:case document.CDATA_SECTION_NODE:case document.COMMENT_NODE:return document.createTextNode(e.nodeValue);break}};if(typeof navigator.geolocation=="undefined"||navigator.geolocation.shim){(function(){(function(){if(window.google&&google.gears){return}var c=null;if(typeof GearsFactory!="undefined"){c=new GearsFactory()}else{try{c=new ActiveXObject("Gears.Factory");if(c.getBuildInfo().indexOf("ie_mobile")!=-1){c.privateSetGlobalObject(this)}}catch(d){if((typeof navigator.mimeTypes!="undefined")&&navigator.mimeTypes["application/x-googlegears"]){c=document.createElement("object");c.style.display="none";c.width=0;c.height=0;c.type="application/x-googlegears";document.documentElement.appendChild(c)}}}if(!c){return}if(!window.google){google={}}if(!google.gears){google.gears={factory:c}}})();var a=(function(){var d=google.gears.factory.create("beta.geolocation");var c=function(f,e){return function(g){f(g);e.lastPosition=g}};return{shim:true,type:"Gears",lastPosition:null,getCurrentPosition:function(e,g,h){var f=this;var i=c(e,f);d.getCurrentPosition(i,g,h)},watchPosition:function(e,f,g){d.watchPosition(e,f,g)},clearWatch:function(e){d.clearWatch(e)},getPermission:function(g,e,f){d.getPermission(g,e,f)}}});var b=(function(){var i=false;var e=function(){if(!d()&&!i){i=true;var j=document.createElement("script");j.src=(document.location.protocol=="https:"?"https://":"http://")+"www.google.com/jsapi?callback=_google_loader_apiLoaded";j.type="text/javascript";document.getElementsByTagName("body")[0].appendChild(j)}};var c=[];var h=function(j){c.push(j)};var f=function(){if(d()){while(c.length>0){var j=c.pop();j()}}};window._google_loader_apiLoaded=function(){f()};var d=function(){return(window.google&&google.loader)};var g=function(j){if(d()){return true}h(j);e();return false};e();return{shim:true,type:"ClientLocation",lastPosition:null,getCurrentPosition:function(k,n,o){var m=this;if(!g(function(){m.getCurrentPosition(k,n,o)})){return}if(google.loader.ClientLocation){var l=google.loader.ClientLocation;var j={coords:{latitude:l.latitude,longitude:l.longitude,altitude:null,accuracy:43000,altitudeAccuracy:null,heading:null,speed:null},address:{city:l.address.city,country:l.address.country,country_code:l.address.country_code,region:l.address.region},timestamp:new Date()};k(j);this.lastPosition=j}else{if(n==="function"){n({code:3,message:"Using the Google ClientLocation API and it is not able to calculate a location."})}}},watchPosition:function(j,l,m){this.getCurrentPosition(j,l,m);var k=this;var n=setInterval(function(){k.getCurrentPosition(j,l,m)},10000);return n},clearWatch:function(j){clearInterval(j)},getPermission:function(l,j,k){return true}}});navigator.geolocation=(window.google&&google.gears)?a():b()})()};
index 0000000000000000000000000000000000000000,a8bdb4715bf5794e8025ac0c699d3b1434dab15e..61b65435abff40a7f49f1e1ab3e530456eee7147
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,290 +1,290 @@@
 -}
+ <?php
+ /**
+  * StatusNet - the distributed open-source microblogging tool
+  * Copyright (C) 2011, StatusNet, Inc.
+  *
+  * Profile block to show for an account
+  *
+  * PHP version 5
+  *
+  * This program is free software: you can redistribute it and/or modify
+  * it under the terms of the GNU Affero General Public License as published by
+  * the Free Software Foundation, either version 3 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  * GNU Affero General Public License for more details.
+  *
+  * You should have received a copy of the GNU Affero General Public License
+  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  *
+  * @category  Widget
+  * @package   StatusNet
+  * @author    Evan Prodromou <evan@status.net>
+  * @copyright 2011 StatusNet, Inc.
+  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+  * @link      http://status.net/
+  */
+ if (!defined('STATUSNET')) {
+     // This check helps protect against security problems;
+     // your code file can't be executed directly from the web.
+     exit(1);
+ }
+ /**
+  * Profile block to show for an account
+  *
+  * @category  Widget
+  * @package   StatusNet
+  * @author    Evan Prodromou <evan@status.net>
+  * @copyright 2011 StatusNet, Inc.
+  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+  * @link      http://status.net/
+  */
+ class AccountProfileBlock extends ProfileBlock
+ {
+     protected $profile = null;
+     protected $user    = null;
+     function __construct($out, $profile)
+     {
+         parent::__construct($out);
+         $this->profile = $profile;
+         $this->user    = User::staticGet('id', $profile->id);
+     }
+     function avatar()
+     {
+         $avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
+         if (empty($avatar)) {
+             $avatar = $this->profile->getAvatar(73);
+         }
+         return (!empty($avatar)) ?
+             $avatar->displayUrl() :
+             Avatar::defaultImage(AVATAR_PROFILE_SIZE);
+     }
+     function name()
+     {
+         return $this->profile->getBestName();
+     }
+     function url()
+     {
+         return $this->profile->profileurl;
+     }
+     function location()
+     {
+         return $this->profile->location;
+     }
+     function homepage()
+     {
+         return $this->profile->homepage;
+     }
+     function description()
+     {
+         return $this->profile->bio;
+     }
+     function showActions()
+     {
+         if (Event::handle('StartProfilePageActionsSection', array($this->out, $this->profile))) {
+             if ($this->profile->hasRole(Profile_role::DELETED)) {
+                 $this->out->elementStart('div', 'entity_actions');
+                 // TRANS: H2 for user actions in a profile.
+                 $this->out->element('h2', null, _('User actions'));
+                 $this->out->elementStart('ul');
+                 $this->out->elementStart('p', array('class' => 'profile_deleted'));
+                 // TRANS: Text shown in user profile of not yet compeltely deleted users.
+                 $this->out->text(_('User deletion in progress...'));
+                 $this->out->elementEnd('p');
+                 $this->out->elementEnd('ul');
+                 $this->out->elementEnd('div');
+                 return;
+             }
+             $cur = common_current_user();
+             $this->out->elementStart('div', 'entity_actions');
+             // TRANS: H2 for entity actions in a profile.
+             $this->out->element('h2', null, _('User actions'));
+             $this->out->elementStart('ul');
+             if (Event::handle('StartProfilePageActionsElements', array($this->out, $this->profile))) {
+                 if (empty($cur)) { // not logged in
+                     if (Event::handle('StartProfileRemoteSubscribe', array($this->out, $this->profile))) {
+                         $this->out->elementStart('li', 'entity_subscribe');
+                         $this->showRemoteSubscribeLink();
+                         $this->out->elementEnd('li');
+                         Event::handle('EndProfileRemoteSubscribe', array($this->out, $this->profile));
+                     }
+                 } else {
+                     if ($cur->id == $this->profile->id) { // your own page
+                         $this->out->elementStart('li', 'entity_edit');
+                         $this->out->element('a', array('href' => common_local_url('profilesettings'),
+                                                   // TRANS: Link title for link on user profile.
+                                                   'title' => _('Edit profile settings')),
+                                        // TRANS: Link text for link on user profile.
+                                        _('Edit'));
+                         $this->out->elementEnd('li');
+                     } else { // someone else's page
+                         // subscribe/unsubscribe button
+                         $this->out->elementStart('li', 'entity_subscribe');
+                         if ($cur->isSubscribed($this->profile)) {
+                             $usf = new UnsubscribeForm($this->out, $this->profile);
+                             $usf->show();
+                         } else {
+                             $sf = new SubscribeForm($this->out, $this->profile);
+                             $sf->show();
+                         }
+                         $this->out->elementEnd('li');
+                         if ($cur->mutuallySubscribed($this->profile)) {
+                             // message
+                             $this->out->elementStart('li', 'entity_send-a-message');
+                             $this->out->element('a', array('href' => common_local_url('newmessage', array('to' => $this->user->id)),
+                                                       // TRANS: Link title for link on user profile.
+                                                       'title' => _('Send a direct message to this user')),
+                                            // TRANS: Link text for link on user profile.
+                                            _('Message'));
+                             $this->out->elementEnd('li');
+                             // nudge
+                             if ($this->user && $this->user->email && $this->user->emailnotifynudge) {
+                                 $this->out->elementStart('li', 'entity_nudge');
+                                 $nf = new NudgeForm($this->out, $this->user);
+                                 $nf->show();
+                                 $this->out->elementEnd('li');
+                             }
+                         }
+                         // return-to args, so we don't have to keep re-writing them
+                         list($action, $r2args) = $this->out->returnToArgs();
+                         // push the action into the list
+                         $r2args['action'] = $action;
+                         // block/unblock
+                         $blocked = $cur->hasBlocked($this->profile);
+                         $this->out->elementStart('li', 'entity_block');
+                         if ($blocked) {
+                             $ubf = new UnblockForm($this->out, $this->profile, $r2args);
+                             $ubf->show();
+                         } else {
+                             $bf = new BlockForm($this->out, $this->profile, $r2args);
+                             $bf->show();
+                         }
+                         $this->out->elementEnd('li');
+                         // Some actions won't be applicable to non-local users.
+                         $isLocal = !empty($this->user);
+                         if ($cur->hasRight(Right::SANDBOXUSER) ||
+                             $cur->hasRight(Right::SILENCEUSER) ||
+                             $cur->hasRight(Right::DELETEUSER)) {
+                             $this->out->elementStart('li', 'entity_moderation');
+                             // TRANS: Label text on user profile to select a user role.
+                             $this->out->element('p', null, _('Moderate'));
+                             $this->out->elementStart('ul');
+                             if ($cur->hasRight(Right::SANDBOXUSER)) {
+                                 $this->out->elementStart('li', 'entity_sandbox');
+                                 if ($this->profile->isSandboxed()) {
+                                     $usf = new UnSandboxForm($this->out, $this->profile, $r2args);
+                                     $usf->show();
+                                 } else {
+                                     $sf = new SandboxForm($this->out, $this->profile, $r2args);
+                                     $sf->show();
+                                 }
+                                 $this->out->elementEnd('li');
+                             }
+                             if ($cur->hasRight(Right::SILENCEUSER)) {
+                                 $this->out->elementStart('li', 'entity_silence');
+                                 if ($this->profile->isSilenced()) {
+                                     $usf = new UnSilenceForm($this->out, $this->profile, $r2args);
+                                     $usf->show();
+                                 } else {
+                                     $sf = new SilenceForm($this->out, $this->profile, $r2args);
+                                     $sf->show();
+                                 }
+                                 $this->out->elementEnd('li');
+                             }
+                             if ($isLocal && $cur->hasRight(Right::DELETEUSER)) {
+                                 $this->out->elementStart('li', 'entity_delete');
+                                 $df = new DeleteUserForm($this->out, $this->profile, $r2args);
+                                 $df->show();
+                                 $this->out->elementEnd('li');
+                             }
+                             $this->out->elementEnd('ul');
+                             $this->out->elementEnd('li');
+                         }
+                         if ($isLocal && $cur->hasRight(Right::GRANTROLE)) {
+                             $this->out->elementStart('li', 'entity_role');
+                             // TRANS: Label text on user profile to select a user role.
+                             $this->out->element('p', null, _('User role'));
+                             $this->out->elementStart('ul');
+                             // TRANS: Role that can be set for a user profile.
+                             $this->roleButton('administrator', _m('role', 'Administrator'));
+                             // TRANS: Role that can be set for a user profile.
+                             $this->roleButton('moderator', _m('role', 'Moderator'));
+                             $this->out->elementEnd('ul');
+                             $this->out->elementEnd('li');
+                         }
+                     }
+                 }
+                 Event::handle('EndProfilePageActionsElements', array($this->out, $this->profile));
+             }
+             $this->out->elementEnd('ul');
+             $this->out->elementEnd('div');
+             Event::handle('EndProfilePageActionsSection', array($this->out, $this->profile));
+         }
+     }
+     function roleButton($role, $label)
+     {
+         list($action, $r2args) = $this->out->returnToArgs();
+         $r2args['action'] = $action;
+         $this->out->elementStart('li', "entity_role_$role");
+         if ($this->profile->hasRole($role)) {
+             $rf = new RevokeRoleForm($role, $label, $this->out, $this->profile, $r2args);
+             $rf->show();
+         } else {
+             $rf = new GrantRoleForm($role, $label, $this->out, $this->profile, $r2args);
+             $rf->show();
+         }
+         $this->out->elementEnd('li');
+     }
+     function showRemoteSubscribeLink()
+     {
+         $url = common_local_url('remotesubscribe',
+                                 array('nickname' => $this->profile->nickname));
+         $this->out->element('a', array('href' => $url,
+                                   'class' => 'entity_remote_subscribe'),
+                        // TRANS: Link text for link that will subscribe to a remote profile.
+                        _('Subscribe'));
+     }
++}
diff --combined lib/action.php
index 80b31762538d34063fdf897a592411d5a174c005,654ec7aa43e533b6c94ca8ba8bfc580935c9357a..2089debc4906e1259ddeea7ca5b1e811192a7bb6
@@@ -83,6 -83,11 +83,11 @@@ class Action extends HTMLOutputter // l
      function prepare($argarray)
      {
          $this->args =& common_copy_args($argarray);
+         if ($this->boolean('ajax')) {
+             StatusNet::setAjax(true);
+         }
          return true;
      }
  
      {
          if (Event::handle('StartShowHTML', array($this))) {
              $this->startHTML();
+             $this->flush();
              Event::handle('EndShowHTML', array($this));
          }
          if (Event::handle('StartShowHead', array($this))) {
              $this->showHead();
+             $this->flush();
              Event::handle('EndShowHead', array($this));
          }
          if (Event::handle('StartShowBody', array($this))) {
                  Event::handle('EndShowLaconicaStyles', array($this));
              }
  
+             $this->cssLink(common_path('js/css/smoothness/jquery-ui.css'));
              if (Event::handle('StartShowUAStyles', array($this))) {
                  $this->comment('[if IE]><link rel="stylesheet" type="text/css" '.
                                 'href="'.Theme::path('css/ie.css', 'base').'?version='.STATUSNET_VERSION.'" /><![endif]');
  
      function primaryCssLink($mainTheme=null, $media=null)
      {
+         $theme = new Theme($mainTheme);
+         // Some themes may have external stylesheets, such as using the
+         // Google Font APIs to load webfonts.
+         foreach ($theme->getExternals() as $url) {
+             $this->cssLink($url, $mainTheme, $media);
+         }
          // If the currently-selected theme has dependencies on other themes,
          // we'll need to load their display.css files as well in order.
-         $theme = new Theme($mainTheme);
          $baseThemes = $theme->getDeps();
          foreach ($baseThemes as $baseTheme) {
              $this->cssLink('css/display.css', $baseTheme, $media);
      {
          if (Event::handle('StartShowScripts', array($this))) {
              if (Event::handle('StartShowJQueryScripts', array($this))) {
-                 $this->script('jquery.min.js');
-                 $this->script('jquery.form.min.js');
-                 $this->script('jquery.cookie.min.js');
-                 $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/json2.min.js').'"); }');
-                 $this->script('jquery.joverlay.min.js');
-                 $this->inlineScript('function _loadTagInput(init) { $.getScript("'.common_path('js/jquery.timers.js'). '"); $.getScript("'.common_path('js/jquery.tagInput.js').'", init); } var _peopletagAC = "' . common_local_url('peopletagautocomplete') . '";');
+                 if (common_config('site', 'minify')) {
+                     $this->script('jquery.min.js');
+                     $this->script('jquery.form.min.js');
+                     $this->script('jquery-ui.min.js');
+                     $this->script('jquery.cookie.min.js');
+                     $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/json2.min.js').'"); }');
+                     $this->script('jquery.joverlay.min.js');
+                 } else {
+                     $this->script('jquery.js');
+                     $this->script('jquery.form.js');
+                     $this->script('jquery-ui.min.js');
+                     $this->script('jquery.cookie.js');
+                     $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/json2.js').'"); }');
+                     $this->script('jquery.joverlay.js');
+                 }
 +
                  Event::handle('EndShowJQueryScripts', array($this));
              }
              if (Event::handle('StartShowStatusNetScripts', array($this)) &&
                      $this->script('util.js');
                      $this->script('xbImportNode.js');
                      $this->script('geometa.js');
++
                  }
++                $this->inlineScript('function _loadTagInput(init) { $.getScript("'.common_path('js/jquery.timers.js') .
++                    '"); $.getScript("'.common_path('js/jquery.tagInput.js').'", init); } var _peopletagAC = "' .
++
++                    common_local_url('peopletagautocomplete') . '";');
                  $this->showScriptMessages();
                  // Frame-busting code to avoid clickjacking attacks.
                  $this->inlineScript('if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
          $this->elementStart('div', array('id' => 'wrap'));
          if (Event::handle('StartShowHeader', array($this))) {
              $this->showHeader();
+             $this->flush();
              Event::handle('EndShowHeader', array($this));
          }
          $this->showCore();
+         $this->flush();
          if (Event::handle('StartShowFooter', array($this))) {
              $this->showFooter();
+             $this->flush();
              Event::handle('EndShowFooter', array($this));
          }
          $this->elementEnd('div');
  
              Event::handle('EndShowSiteNotice', array($this));
          }
-         if (common_logged_in()) {
-             if (Event::handle('StartShowNoticeForm', array($this))) {
-                 $this->showNoticeForm();
-                 Event::handle('EndShowNoticeForm', array($this));
-             }
-         } else {
-             $this->showAnonymousMessage();
-         }
          $this->elementEnd('div');
      }
  
       */
      function showPrimaryNav()
      {
-         $user = common_current_user();
-         $this->elementStart('ul', array('class' => 'nav',
-                                         'id' => 'site_nav_global_primary'));
-         if (Event::handle('StartPrimaryNav', array($this))) {
-             if (!empty($user)) {
-                 $this->menuItem(common_local_url('all', 
-                                                  array('nickname' => $user->nickname)),
-                                 _m('Home'),
-                                 _m('Friends timeline'),
-                                 false,
-                                 'nav_home');
-                 $this->menuItem(common_local_url('showstream', 
-                                                  array('nickname' => $user->nickname)),
-                                 _m('Profile'),
-                                 _m('Your profile'),
-                                 false,
-                                 'nav_profile');
-                 $this->menuItem(common_local_url('public'),
-                                 _m('Public'),
-                                 _m('Everyone on this site'),
-                                 false,
-                                 'nav_public');
-                 $this->menuItem(common_local_url('profilesettings'),
-                                 _m('Settings'),
-                                 _m('Change your personal settings'),
-                                 false,
-                                 'nav_account');
-                 if ($user->hasRight(Right::CONFIGURESITE)) {
-                     $this->menuItem(common_local_url('siteadminpanel'),
-                                     _m('Admin'), 
-                                     _m('Site configuration'),
-                                     false,
-                                     'nav_admin');
-                 }
-                 $this->menuItem(common_local_url('logout'),
-                                 _m('Logout'), 
-                                 _m('Logout from the site'),
-                                 false,
-                                 'nav_logout');
-             } else {
-                 $this->menuItem(common_local_url('public'),
-                                 _m('Public'),
-                                 _m('Everyone on this site'),
-                                 false,
-                                 'nav_public');
-                 $this->menuItem(common_local_url('login'),
-                                 _m('Login'), 
-                                 _m('Login to the site'),
-                                 false,
-                                 'nav_login');
-             }
-             if (!empty($user) || !common_config('site', 'private')) {
-                 $this->menuItem(common_local_url('noticesearch'),
-                                 _m('Search'),
-                                 _m('Search the site'),
-                                 false,
-                                 'nav_search');
-             }
-             Event::handle('EndPrimaryNav', array($this));
-         }
-         $this->elementEnd('ul');
+         $this->elementStart('div', array('id' => 'site_nav_global_primary'));
+         $pn = new PrimaryNav($this);
+         $pn->show();
+         $this->elementEnd('div');
      }
  
      /**
       */
      function showNoticeForm()
      {
-         $notice_form = new NoticeForm($this);
-         $notice_form->show();
+         $tabs = array('status' => _('Status'));
+         $this->elementStart('div', 'input_forms');
+         if (Event::handle('StartShowEntryForms', array(&$tabs))) {
+             $this->elementStart('ul', array('class' => 'nav',
+                                             'id' => 'input_form_nav'));
+             foreach ($tabs as $tag => $title) {
+                 $attrs = array('id' => 'input_form_nav_'.$tag,
+                                'class' => 'input_form_nav_tab');
+                 if ($tag == 'status') {
+                     // We're actually showing the placeholder form,
+                     // but we special-case the 'Status' tab as if
+                     // it were a small version of it.
+                     $attrs['class'] .= ' current';
+                 }
+                 $this->elementStart('li', $attrs);
+                 $this->element('a',
+                                array('href' => 'javascript:SN.U.switchInputFormTab("'.$tag.'")'),
+                                $title);
+                 $this->elementEnd('li');
+             }
+             $this->elementEnd('ul');
+             $attrs = array('class' => 'input_form current',
+                            'id' => 'input_form_placeholder');
+             $this->elementStart('div', $attrs);
+             $form = new NoticePlaceholderForm($this);
+             $form->show();
+             $this->elementEnd('div');
+             foreach ($tabs as $tag => $title) {
+                 $attrs = array('class' => 'input_form',
+                                'id' => 'input_form_'.$tag);
+                 $this->elementStart('div', $attrs);
+                 $form = null;
+                 if (Event::handle('StartMakeEntryForm', array($tag, $this, &$form))) {
+                     if ($tag == 'status') {
+                         $form = new NoticeForm($this);
+                     }
+                     Event::handle('EndMakeEntryForm', array($tag, $this, $form));
+                 }
+                 if (!empty($form)) {
+                     $form->show();
+                 }
+                 $this->elementEnd('div');
+             }
+         }
+         $this->elementEnd('div');
      }
  
      /**
      function showCore()
      {
          $this->elementStart('div', array('id' => 'core'));
+         $this->elementStart('div', array('id' => 'aside_primary_wrapper'));
+         $this->elementStart('div', array('id' => 'content_wrapper'));
+         $this->elementStart('div', array('id' => 'site_nav_local_views_wrapper'));
          if (Event::handle('StartShowLocalNavBlock', array($this))) {
              $this->showLocalNavBlock();
+             $this->flush();
              Event::handle('EndShowLocalNavBlock', array($this));
          }
          if (Event::handle('StartShowContentBlock', array($this))) {
              $this->showContentBlock();
+             $this->flush();
              Event::handle('EndShowContentBlock', array($this));
          }
          if (Event::handle('StartShowAside', array($this))) {
              $this->showAside();
+             $this->flush();
              Event::handle('EndShowAside', array($this));
          }
          $this->elementEnd('div');
+         $this->elementEnd('div');
+         $this->elementEnd('div');
+         $this->elementEnd('div');
      }
  
      /**
          // Need to have this ID for CSS; I'm too lazy to add it to
          // all menus
          $this->elementStart('div', array('id' => 'site_nav_local_views'));
+         // Cheat cheat cheat!
          $this->showLocalNav();
          $this->elementEnd('div');
      }
  
+     /**
+      * If there's a logged-in user, show a bit of login context
+      *
+      * @return nothing
+      */
+     function showProfileBlock()
+     {
+         if (common_logged_in()) {
+             $block = new DefaultProfileBlock($this);
+             $block->show();
+         }
+     }
      /**
       * Show local navigation.
       *
       */
      function showLocalNav()
      {
-         // does nothing by default
+         $nav = new DefaultLocalNav($this);
+         $nav->show();
+     }
+     /**
+      * Show menu for an object (group, profile)
+      *
+      * This block will only show if a subclass has overridden
+      * the showObjectNav() method.
+      *
+      * @return nothing
+      */
+     function showObjectNavBlock()
+     {
+         $rmethod = new ReflectionMethod($this, 'showObjectNav');
+         $dclass = $rmethod->getDeclaringClass()->getName();
+         if ($dclass != 'Action') {
+             // Need to have this ID for CSS; I'm too lazy to add it to
+             // all menus
+             $this->elementStart('div', array('id' => 'site_nav_object',
+                                              'class' => 'section'));
+             $this->showObjectNav();
+             $this->elementEnd('div');
+         }
+     }
+     /**
+      * Show object navigation.
+      *
+      * If there are things to do with this object, show it here.
+      *
+      * @return nothing
+      */
+     function showObjectNav()
+     {
+         /* Nothing here. */
      }
  
      /**
      function showContentBlock()
      {
          $this->elementStart('div', array('id' => 'content'));
+         if (common_logged_in()) {
+             if (Event::handle('StartShowNoticeForm', array($this))) {
+                 $this->showNoticeForm();
+                 Event::handle('EndShowNoticeForm', array($this));
+             }
+         }
          if (Event::handle('StartShowPageTitle', array($this))) {
              $this->showPageTitle();
              Event::handle('EndShowPageTitle', array($this));
      {
          $this->elementStart('div', array('id' => 'aside_primary',
                                           'class' => 'aside'));
+         $this->showProfileBlock();
+         if (Event::handle('StartShowObjectNavBlock', array($this))) {
+             $this->showObjectNavBlock();
+             Event::handle('EndShowObjectNavBlock', array($this));
+         }
          if (Event::handle('StartShowSections', array($this))) {
              $this->showSections();
              Event::handle('EndShowSections', array($this));
       */
      function showSecondaryNav()
      {
-         $this->elementStart('ul', array('class' => 'nav',
-                                         'id' => 'site_nav_global_secondary'));
-         if (Event::handle('StartSecondaryNav', array($this))) {
-             $this->menuItem(common_local_url('doc', array('title' => 'help')),
-                             // TRANS: Secondary navigation menu option leading to help on StatusNet.
-                             _('Help'));
-             $this->menuItem(common_local_url('doc', array('title' => 'about')),
-                             // TRANS: Secondary navigation menu option leading to text about StatusNet site.
-                             _('About'));
-             $this->menuItem(common_local_url('doc', array('title' => 'faq')),
-                             // TRANS: Secondary navigation menu option leading to Frequently Asked Questions.
-                             _('FAQ'));
-             $bb = common_config('site', 'broughtby');
-             if (!empty($bb)) {
-                 $this->menuItem(common_local_url('doc', array('title' => 'tos')),
-                                 // TRANS: Secondary navigation menu option leading to Terms of Service.
-                                 _('TOS'));
-             }
-             $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
-                             // TRANS: Secondary navigation menu option leading to privacy policy.
-                             _('Privacy'));
-             $this->menuItem(common_local_url('doc', array('title' => 'source')),
-                             // TRANS: Secondary navigation menu option. Leads to information about StatusNet and its license.
-                             _('Source'));
-             $this->menuItem(common_local_url('version'),
-                             // TRANS: Secondary navigation menu option leading to version information on the StatusNet site.
-                             _('Version'));
-             $this->menuItem(common_local_url('doc', array('title' => 'contact')),
-                             // TRANS: Secondary navigation menu option leading to e-mail contact information on the
-                             // TRANS: StatusNet site, where to report bugs, ...
-                             _('Contact'));
-             $this->menuItem(common_local_url('doc', array('title' => 'badge')),
-                             // TRANS: Secondary navigation menu option. Leads to information about embedding a timeline widget.
-                             _('Badge'));
-             Event::handle('EndSecondaryNav', array($this));
-         }
-         $this->elementEnd('ul');
+         $sn = new SecondaryNav($this);
+         $sn->show();
      }
  
      /**
diff --combined lib/activityobject.php
index 7771455443d217f53a28511de82dce7081664121,241f99564f214287cfd2bae9480054be916a334b..a47928e871b0f2d7af6f651027bf207ddf4e0e0c
@@@ -64,7 -64,6 +64,7 @@@ class ActivityObjec
      const BOOKMARK  = 'http://activitystrea.ms/schema/1.0/bookmark';
      const PERSON    = 'http://activitystrea.ms/schema/1.0/person';
      const GROUP     = 'http://activitystrea.ms/schema/1.0/group';
 +    const _LIST     = 'http://activitystrea.ms/schema/1.0/list'; // LIST is reserved
      const PLACE     = 'http://activitystrea.ms/schema/1.0/place';
      const COMMENT   = 'http://activitystrea.ms/schema/1.0/comment';
      // ^^^^^^^^^^ tea!
@@@ -93,7 -92,6 +93,7 @@@
      public $title;
      public $summary;
      public $content;
 +    public $owner;
      public $link;
      public $source;
      public $avatarLinks = array();
                  Activity::MEDIA
              );
          }
 +        if ($this->type == self::_LIST) {
 +            $owner = ActivityUtils::child($this->element, Activity::AUTHOR, Activity::SPEC);
 +            $this->owner = new ActivityObject($owner);
 +        }
      }
  
      private function _fromAuthor($element)
  
                if (Event::handle('StartActivityObjectFromNotice', array($notice, &$object))) {
  
-                       $object->type    = ActivityObject::NOTE;
+                       $object->type    = (empty($notice->object_type)) ? ActivityObject::NOTE : $notice->object_type;
  
                        $object->id      = $notice->uri;
                        $object->title   = $notice->content;
                                                                                                                          AVATAR_MINI_SIZE);
  
                        $object->poco = PoCo::fromGroup($group);
 -
 -                      Event::handle('EndActivityObjectFromGroup', array($group, &$object));
 +                  Event::handle('EndActivityObjectFromGroup', array($group, &$object));
                }
  
          return $object;
      }
  
 +    static function fromPeopletag($ptag)
 +    {
 +        $object = new ActivityObject();
 +        if (Event::handle('StartActivityObjectFromPeopletag', array($ptag, &$object))) {
 +            $object->type    = ActivityObject::_LIST;
 +
 +            $object->id      = $ptag->getUri();
 +            $object->title   = $ptag->tag;
 +            $object->summary = $ptag->description;
 +            $object->link    = $ptag->homeUrl();
 +            $object->owner   = Profile::staticGet('id', $ptag->tagger);
 +            $object->poco    = PoCo::fromProfile($object->owner);
 +                  Event::handle('EndActivityObjectFromPeopletag', array($ptag, &$object));
 +        }
 +        return $object;
 +    }
 +
        function outputTo($xo, $tag='activity:object')
        {
                if (!empty($tag)) {
                        $xo->elementStart($tag);
                }
  
-         $xo->element('activity:object-type', null, $this->type);
+         if (Event::handle('StartActivityObjectOutputAtom', array($this, $xo))) {
+             $xo->element('activity:object-type', null, $this->type);
  
-         // <author> uses URI
+             // <author> uses URI
  
-         if ($tag == 'author') {
-             $xo->element(self::URI, null, $this->id);
-         } else {
-             $xo->element(self::ID, null, $this->id);
-         }
-         if (!empty($this->title)) {
-             $name = common_xml_safe_str($this->title);
              if ($tag == 'author') {
-                 // XXX: Backward compatibility hack -- atom:name should contain
-                 // full name here, instead of nickname, i.e.: $name. Change
-                 // this in the next version.
-                 $xo->element(self::NAME, null, $this->poco->preferredUsername);
+                 $xo->element(self::URI, null, $this->id);
              } else {
-                 $xo->element(self::TITLE, null, $name);
+                 $xo->element(self::ID, null, $this->id);
              }
-         }
-         if (!empty($this->summary)) {
-             $xo->element(
-                 self::SUMMARY,
-                 null,
-                 common_xml_safe_str($this->summary)
-             );
-         }
  
-         if (!empty($this->content)) {
-             // XXX: assuming HTML content here
-             $xo->element(
-                 ActivityUtils::CONTENT,
-                 array('type' => 'html'),
-                 common_xml_safe_str($this->content)
-             );
-         }
+             if (!empty($this->title)) {
+                 $name = common_xml_safe_str($this->title);
+                 if ($tag == 'author') {
+                     // XXX: Backward compatibility hack -- atom:name should contain
+                     // full name here, instead of nickname, i.e.: $name. Change
+                     // this in the next version.
+                     $xo->element(self::NAME, null, $this->poco->preferredUsername);
+                 } else {
+                     $xo->element(self::TITLE, null, $name);
+                 }
+             }
  
-         if (!empty($this->link)) {
-             $xo->element(
-                 'link',
-                 array(
-                     'rel' => 'alternate',
-                     'type' => 'text/html',
-                     'href' => $this->link
-                 ),
-                 null
-             );
-         }
+             if (!empty($this->summary)) {
+                 $xo->element(
+                     self::SUMMARY,
+                     null,
+                     common_xml_safe_str($this->summary)
+                 );
+             }
  
-         if ($this->type == ActivityObject::PERSON
-             || $this->type == ActivityObject::GROUP) {
+             if (!empty($this->content)) {
+                 // XXX: assuming HTML content here
+                 $xo->element(
+                     ActivityUtils::CONTENT,
+                     array('type' => 'html'),
+                     common_xml_safe_str($this->content)
+                 );
+             }
  
-             foreach ($this->avatarLinks as $avatar) {
+             if (!empty($this->link)) {
                  $xo->element(
-                     'link', array(
-                         'rel'  => 'avatar',
-                         'type'         => $avatar->type,
-                         'media:width'  => $avatar->width,
-                         'media:height' => $avatar->height,
-                         'href' => $avatar->url
+                     'link',
+                     array(
+                         'rel' => 'alternate',
+                         'type' => 'text/html',
+                         'href' => $this->link
                      ),
                      null
                  );
              }
-         }
  
-         if(!empty($this->owner)) {
-             $owner = $this->owner->asActivityNoun(self::AUTHOR);
-             $xo->raw($owner);
-         }
++            if(!empty($this->owner)) {
++                $owner = $this->owner->asActivityNoun(self::AUTHOR);
++                $xo->raw($owner);
++            }
 +
-         if (!empty($this->geopoint)) {
-             $xo->element(
-                 'georss:point',
-                 null,
-                 $this->geopoint
-             );
-         }
+             if ($this->type == ActivityObject::PERSON
+                 || $this->type == ActivityObject::GROUP) {
+                 foreach ($this->avatarLinks as $avatar) {
+                     $xo->element(
+                         'link', array(
+                             'rel'  => 'avatar',
+                             'type'         => $avatar->type,
+                             'media:width'  => $avatar->width,
+                             'media:height' => $avatar->height,
+                             'href' => $avatar->url
+                         ),
+                         null
+                     );
+                 }
+             }
  
-         if (!empty($this->poco)) {
-             $this->poco->outputTo($xo);
-         }
 +
-         foreach ($this->extra as $el) {
-             list($extraTag, $attrs, $content) = $el;
-             $xo->element($extraTag, $attrs, $content);
+             if (!empty($this->geopoint)) {
+                 $xo->element(
+                     'georss:point',
+                     null,
+                     $this->geopoint
+                 );
+             }
+             if (!empty($this->poco)) {
+                 $this->poco->outputTo($xo);
+             }
+             // @fixme there's no way here to make a tree; elements can only contain plaintext
+             // @fixme these may collide with JSON extensions
+             foreach ($this->extra as $el) {
+                 list($extraTag, $attrs, $content) = $el;
+                 $xo->element($extraTag, $attrs, $content);
+             }
+             Event::handle('EndActivityObjectOutputAtom', array($this, $xo));
          }
  
                if (!empty($tag)) {
      {
          $object = array();
  
-         // XXX: attachedObjects are added by Activity
+         if (Event::handle('StartActivityObjectOutputJson', array($this, &$object))) {
+             // XXX: attachedObjects are added by Activity
  
-         // displayName
-         $object['displayName'] = $this->title;
+             // displayName
+             $object['displayName'] = $this->title;
  
-         // TODO: downstreamDuplicates
+             // TODO: downstreamDuplicates
  
-         // embedCode (used for video)
+             // embedCode (used for video)
  
-         // id
-         //
-         // XXX: Should we use URL here? or a crazy tag URI?
-         $object['id'] = $this->id;
+             // id
+             //
+             // XXX: Should we use URL here? or a crazy tag URI?
+             $object['id'] = $this->id;
  
-         if ($this->type == ActivityObject::PERSON
-             || $this->type == ActivityObject::GROUP) {
+             if ($this->type == ActivityObject::PERSON
+                 || $this->type == ActivityObject::GROUP) {
  
-             // XXX: Not sure what the best avatar is to use for the
-             // author's "image". For now, I'm using the large size.
+                 // XXX: Not sure what the best avatar is to use for the
+                 // author's "image". For now, I'm using the large size.
  
-             $avatarLarge      = null;
-             $avatarMediaLinks = array();
+                 $avatarLarge      = null;
+                 $avatarMediaLinks = array();
  
-             foreach ($this->avatarLinks as $a) {
+                 foreach ($this->avatarLinks as $a) {
  
-                 // Make a MediaLink for every other Avatar
-                 $avatar = new ActivityStreamsMediaLink(
-                     $a->url,
-                     $a->width,
-                     $a->height,
-                     $a->type,
-                     'avatar'
-                 );
+                     // Make a MediaLink for every other Avatar
+                     $avatar = new ActivityStreamsMediaLink(
+                         $a->url,
+                         $a->width,
+                         $a->height,
+                         $a->type,
+                         'avatar'
+                     );
  
-                 // Find the big avatar to use as the "image"
-                 if ($a->height == AVATAR_PROFILE_SIZE) {
-                     $imgLink = $avatar;
-                 }
+                     // Find the big avatar to use as the "image"
+                     if ($a->height == AVATAR_PROFILE_SIZE) {
+                         $imgLink = $avatar;
+                     }
  
-                 $avatarMediaLinks[] = $avatar->asArray();
-             }
-             $object['avatarLinks'] = $avatarMediaLinks; // extension
+                     $avatarMediaLinks[] = $avatar->asArray();
+                 }
  
-             // image
-             $object['image']  = $imgLink->asArray();
-         }
+                 $object['avatarLinks'] = $avatarMediaLinks; // extension
  
-         // objectType
-         //
-         // We can probably use the whole schema URL here but probably the
-         // relative simple name is easier to parse
-         $object['type'] = substr($this->type, strrpos($this->type, '/') + 1);
+                 // image
+                 $object['image']  = $imgLink->asArray();
+             }
  
-         // summary
-         $object['summary'] = $this->summary;
+             // objectType
+             //
+             // We can probably use the whole schema URL here but probably the
+             // relative simple name is easier to parse
+             // @fixme this breaks extension URIs
+             $object['type'] = substr($this->type, strrpos($this->type, '/') + 1);
  
-         // TODO: upstreamDuplicates
+             // summary
+             $object['summary'] = $this->summary;
  
-         // url (XXX: need to put the right thing here...)
-         $object['url'] = $this->id;
+             // TODO: upstreamDuplicates
  
-         /* Extensions */
+             // url (XXX: need to put the right thing here...)
+             $object['url'] = $this->id;
  
-         foreach ($this->extra as $e) {
-             list($objectName, $props, $txt) = $e;
-             $object[$objectName] = $props;
-         }
+             /* Extensions */
+             // @fixme these may collide with XML extensions
+             // @fixme multiple tags of same name will overwrite each other
+             // @fixme text content from XML extensions will be lost
+             foreach ($this->extra as $e) {
+                 list($objectName, $props, $txt) = $e;
+                 $object[$objectName] = $props;
+             }
  
-         // GeoJSON
+             // GeoJSON
  
-         if (!empty($this->geopoint)) {
+             if (!empty($this->geopoint)) {
  
-             list($lat, $long) = explode(' ', $this->geopoint);
+                 list($lat, $long) = explode(' ', $this->geopoint);
  
-             $object['geopoint'] = array(
-                 'type'        => 'Point',
-                 'coordinates' => array($lat, $long)
-             );
-         }
+                 $object['geopoint'] = array(
+                     'type'        => 'Point',
+                     'coordinates' => array($lat, $long)
+                 );
+             }
  
-         if (!empty($this->poco)) {
-             $object['contact'] = $this->poco->asArray();
+             if (!empty($this->poco)) {
+                 $object['contact'] = $this->poco->asArray();
+             }
+             Event::handle('EndActivityObjectOutputJson', array($this, &$object));
          }
          return array_filter($object);
      }
  }
diff --combined lib/command.php
index 750b1c88a26808f688474d4490930dafd8049ce7,03baa8212d998a569b1125be893a4b2030d4d771..39865234b6611d1cd79bb38409d13363cdc25c39
@@@ -420,104 -420,6 +420,104 @@@ class DropCommand extends Comman
      }
  }
  
 +class TagCommand extends Command
 +{
 +    var $other = null;
 +    var $tags = null;
 +    function __construct($user, $other, $tags)
 +    {
 +        parent::__construct($user);
 +        $this->other = $other;
 +        $this->tags = $tags;
 +    }
 +
 +    function handle($channel)
 +    {
 +        $profile = $this->getProfile($this->other);
 +        $cur     = $this->user->getProfile();
 +
 +        if (!$profile) {
 +            $channel->error($cur, _('No such profile.'));
 +            return;
 +        }
 +        if (!$cur->canTag($profile)) {
 +            $channel->error($cur, _('You cannot tag this user.'));
 +            return;
 +        }
 +
 +        $privs = array();
 +        $tags = preg_split('/[\s,]+/', $this->tags);
 +        $clean_tags = array();
 +
 +        foreach ($tags as $tag) {
 +            $private = @$tag[0] === '.';
 +            $tag = $clean_tags[] = common_canonical_tag($tag);
 +
 +            if (!common_valid_profile_tag($tag)) {
 +                $channel->error($cur, sprintf(_('Invalid tag: "%s"'), $tag));
 +                return;
 +            }
 +            $privs[$tag] = $private;
 +        }
 +
 +        try {
 +            foreach ($clean_tags as $tag) {
 +                Profile_tag::setTag($cur->id, $profile->id, $tag, null, $privs[$tag]);
 +            }
 +        } catch (Exception $e) {
 +            $channel->error($cur, sprintf(_('Error tagging %s: %s'),
 +                                          $profile->nickname, $e->getMessage()));
 +            return;
 +        }
 +
 +        $channel->output($cur, sprintf(_('%1$s was tagged %2$s'),
 +                                              $profile->nickname,
 +                                              implode(', ', $clean_tags)));
 +    }
 +}
 +
 +
 +class UntagCommand extends TagCommand
 +{
 +    function handle($channel)
 +    {
 +        $profile = $this->getProfile($this->other);
 +        $cur     = $this->user->getProfile();
 +
 +        if (!$profile) {
 +            $channel->error($cur, _('No such profile.'));
 +            return;
 +        }
 +        if (!$cur->canTag($profile)) {
 +            $channel->error($cur, _('You cannot tag this user.'));
 +            return;
 +        }
 +
 +        $tags = array_map('common_canonical_tag', preg_split('/[\s,]+/', $this->tags));
 +
 +        foreach ($tags as $tag) {
 +            if (!common_valid_profile_tag($tag)) {
 +                $channel->error($cur, sprintf(_('Invalid tag: "%s"'), $tag));
 +                return;
 +            }
 +        }
 +
 +        try {
 +            foreach ($tags as $tag) {
 +                Profile_tag::unTag($cur->id, $profile->id, $tag);
 +            }
 +        } catch (Exception $e) {
 +            $channel->error($cur, sprintf(_('Error untagging %s: %s'),
 +                                          $profile->nickname, $e->getMessage()));
 +            return;
 +        }
 +
 +        $channel->output($cur, sprintf(_('The following tag(s) were removed from user %1$s: %2$s.'),
 +                                              $profile->nickname,
 +                                              implode(', ', $tags)));
 +    }
 +}
 +
  class WhoisCommand extends Command
  {
      var $other = null;
@@@ -1009,47 -911,88 +1009,92 @@@ class HelpCommand extends Comman
  {
      function handle($channel)
      {
-         $channel->output($this->user,
-                          // TRANS: Help text for commands. Do not translate the command names themselves; they are fixed strings.
-                          _("Commands:\n".
-                            "on - turn on notifications\n".
-                            "off - turn off notifications\n".
-                            "help - show this help\n".
-                            "follow <nickname> - subscribe to user\n".
-                            "groups - lists the groups you have joined\n".
-                            "tag <nickname> <tags> - tag a user\n".
-                            "untag <nickname> <tags> - untag a user\n".
-                            "subscriptions - list the people you follow\n".
-                            "subscribers - list the people that follow you\n".
-                            "leave <nickname> - unsubscribe from user\n".
-                            "d <nickname> <text> - direct message to user\n".
-                            "get <nickname> - get last notice from user\n".
-                            "whois <nickname> - get profile info on user\n".
-                            "lose <nickname> - force user to stop following you\n".
-                            "fav <nickname> - add user's last notice as a 'fave'\n".
-                            "fav #<notice_id> - add notice with the given id as a 'fave'\n".
-                            "repeat #<notice_id> - repeat a notice with a given id\n".
-                            "repeat <nickname> - repeat the last notice from user\n".
-                            "reply #<notice_id> - reply to notice with a given id\n".
-                            "reply <nickname> - reply to the last notice from user\n".
-                            "join <group> - join group\n".
-                            "login - Get a link to login to the web interface\n".
-                            "drop <group> - leave group\n".
-                            "stats - get your stats\n".
-                            "stop - same as 'off'\n".
-                            "quit - same as 'off'\n".
-                            "sub <nickname> - same as 'follow'\n".
-                            "unsub <nickname> - same as 'leave'\n".
-                            "last <nickname> - same as 'get'\n".
-                            "on <nickname> - not yet implemented.\n".
-                            "off <nickname> - not yet implemented.\n".
-                            "nudge <nickname> - remind a user to update.\n".
-                            "invite <phone number> - not yet implemented.\n".
-                            "track <word> - not yet implemented.\n".
-                            "untrack <word> - not yet implemented.\n".
-                            "track off - not yet implemented.\n".
-                            "untrack all - not yet implemented.\n".
-                            "tracks - not yet implemented.\n".
-                            "tracking - not yet implemented.\n"));
+         // TRANS: Header line of help text for commands.
+         $out = array(_m('COMMANDHELP', "Commands:"));
+         $commands = array(// TRANS: Help message for IM/SMS command "on"
+                           "on" => _m('COMMANDHELP', "turn on notifications"),
+                           // TRANS: Help message for IM/SMS command "off"
+                           "off" => _m('COMMANDHELP', "turn off notifications"),
+                           // TRANS: Help message for IM/SMS command "help"
+                           "help" => _m('COMMANDHELP', "show this help"),
+                           // TRANS: Help message for IM/SMS command "follow <nickname>"
+                           "follow <nickname>" => _m('COMMANDHELP', "subscribe to user"),
+                           // TRANS: Help message for IM/SMS command "groups"
+                           "groups" => _m('COMMANDHELP', "lists the groups you have joined"),
++                          // TRANS: Help message for IM/SMS command "tag"
++                          "tag <nickname> <tags>" => _m('COMMANDHELP',"tag a user"),
++                          // TRANS: Help message for IM/SMS command "untag"
++                          "untag <nickname> <tags>" => _m('COMMANDHELP',"untag a user"),
+                           // TRANS: Help message for IM/SMS command "subscriptions"
+                           "subscriptions" => _m('COMMANDHELP', "list the people you follow"),
+                           // TRANS: Help message for IM/SMS command "subscribers"
+                           "subscribers" => _m('COMMANDHELP', "list the people that follow you"),
+                           // TRANS: Help message for IM/SMS command "leave <nickname>"
+                           "leave <nickname>" => _m('COMMANDHELP', "unsubscribe from user"),
+                           // TRANS: Help message for IM/SMS command "d <nickname> <text>"
+                           "d <nickname> <text>" => _m('COMMANDHELP', "direct message to user"),
+                           // TRANS: Help message for IM/SMS command "get <nickname>"
+                           "get <nickname>" => _m('COMMANDHELP', "get last notice from user"),
+                           // TRANS: Help message for IM/SMS command "whois <nickname>"
+                           "whois <nickname>" => _m('COMMANDHELP', "get profile info on user"),
+                           // TRANS: Help message for IM/SMS command "lose <nickname>"
+                           "lose <nickname>" => _m('COMMANDHELP', "force user to stop following you"),
+                           // TRANS: Help message for IM/SMS command "fav <nickname>"
+                           "fav <nickname>" => _m('COMMANDHELP', "add user's last notice as a 'fave'"),
+                           // TRANS: Help message for IM/SMS command "fav #<notice_id>"
+                           "fav #<notice_id>" => _m('COMMANDHELP', "add notice with the given id as a 'fave'"),
+                           // TRANS: Help message for IM/SMS command "repeat #<notice_id>"
+                           "repeat #<notice_id>" => _m('COMMANDHELP', "repeat a notice with a given id"),
+                           // TRANS: Help message for IM/SMS command "repeat <nickname>"
+                           "repeat <nickname>" => _m('COMMANDHELP', "repeat the last notice from user"),
+                           // TRANS: Help message for IM/SMS command "reply #<notice_id>"
+                           "reply #<notice_id>" => _m('COMMANDHELP', "reply to notice with a given id"),
+                           // TRANS: Help message for IM/SMS command "reply <nickname>"
+                           "reply <nickname>" => _m('COMMANDHELP', "reply to the last notice from user"),
+                           // TRANS: Help message for IM/SMS command "join <group>"
+                           "join <group>" => _m('COMMANDHELP', "join group"),
+                           // TRANS: Help message for IM/SMS command "login"
+                           "login" => _m('COMMANDHELP', "Get a link to login to the web interface"),
+                           // TRANS: Help message for IM/SMS command "drop <group>"
+                           "drop <group>" => _m('COMMANDHELP', "leave group"),
+                           // TRANS: Help message for IM/SMS command "stats"
+                           "stats" => _m('COMMANDHELP', "get your stats"),
+                           // TRANS: Help message for IM/SMS command "stop"
+                           "stop" => _m('COMMANDHELP', "same as 'off'"),
+                           // TRANS: Help message for IM/SMS command "quit"
+                           "quit" => _m('COMMANDHELP', "same as 'off'"),
+                           // TRANS: Help message for IM/SMS command "sub <nickname>"
+                           "sub <nickname>" => _m('COMMANDHELP', "same as 'follow'"),
+                           // TRANS: Help message for IM/SMS command "unsub <nickname>"
+                           "unsub <nickname>" => _m('COMMANDHELP', "same as 'leave'"),
+                           // TRANS: Help message for IM/SMS command "last <nickname>"
+                           "last <nickname>" => _m('COMMANDHELP', "same as 'get'"),
+                           // TRANS: Help message for IM/SMS command "on <nickname>"
+                           "on <nickname>" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "off <nickname>"
+                           "off <nickname>" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "nudge <nickname>"
+                           "nudge <nickname>" => _m('COMMANDHELP', "remind a user to update."),
+                           // TRANS: Help message for IM/SMS command "invite <phone number>"
+                           "invite <phone number>" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "track <word>"
+                           "track <word>" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "untrack <word>"
+                           "untrack <word>" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "track off"
+                           "track off" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "untrack all"
+                           "untrack all" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "tracks"
+                           "tracks" => _m('COMMANDHELP', "not yet implemented."),
+                           // TRANS: Help message for IM/SMS command "tracking"
+                           "tracking" => _m('COMMANDHELP', "not yet implemented."));
+         // Give plugins a chance to add or override...
+         Event::handle('HelpCommandMessages', array($this, &$commands));
+         foreach ($commands as $command => $help) {
+             $out[] = "$command - $help";
+         }
+         $channel->output($this->user, implode("\n", $out));
      }
  }
index 9aaa8228c7f3eaf4e5b3b45b2fb85420a5f6cec4,6b1b70055e06c5c9ed29b87acd075009fb450deb..1e8114701d1d3120e73f1dcfbc751048e961051b
@@@ -274,32 -274,6 +274,32 @@@ class CommandInterprete
                      }
                  }
                  break;
 +             case 'list':
 +             case 'tag':
 +                if (!$arg) {
 +                    $result = null;
 +                    break;
 +                }
 +                list($other, $tags) = $this->split_arg($arg);
 +                if (!$tags) {
 +                    $result = null;
 +                } else {
 +                    $result = new TagCommand($user, $other, $tags);
 +                }
 +                break;
 +             case 'unlist':
 +             case 'untag':
 +                if (!$arg) {
 +                    $result = null;
 +                    break;
 +                }
 +                list($other, $tags) = $this->split_arg($arg);
 +                if (!$tags) {
 +                    $result = null;
 +                } else {
 +                    $result = new UntagCommand($user, $other, $tags);
 +                }
 +                break;
              case 'track':
                  if (!$arg) {
                      $result = null;
                  $result = false;
              }
                  
-             Event::handle('EndInterpretCommand', array($cmd, $arg, $user, $result));
+             Event::handle('EndInterpretCommand', array($cmd, $arg, $user, &$result));
          }
  
          return $result;
diff --combined lib/default.php
index 40372e756b6a731e2c683687c7d3561b8b3f4d4b,e6caf0301a636df07ecc288477f227019ced1edf..8489caab504c90369ce1a6556d20289cd156e33e
@@@ -33,7 -33,7 +33,7 @@@ $default 
                'nickname' => 'statusnet',
                'wildcard' => null,
                'server' => $_server,
-               'theme' => 'default',
+               'theme' => 'neo',
                'path' => $_path,
                'logfile' => null,
                'logo' => null,
          'group' =>
          array('maxaliases' => 3,
                'desclimit' => null),
 +        'peopletag' =>
 +        array('maxtags' => 100, // maximum number of tags a user can create.
 +              'maxpeople' => 500, // maximum no. of people with the same tag by the same user
 +              'allow_tagging' => array('all' => true), // equivalent to array('local' => true, 'remote' => true)
 +              'desclimit' => null),
          'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
          'search' =>
          array('type' => 'fulltext'),
diff --combined lib/framework.php
index b624e312e17511d5c332c08be7e7fadcc0d6cc39,da96c8e1d4069fa5ff1ba64f452eabb0dd881ba1..4b99d296a205a89107dee681264dca785a074794
@@@ -33,6 -33,7 +33,7 @@@ define('AVATAR_MINI_SIZE', 24)
  
  define('NOTICES_PER_PAGE', 20);
  define('PROFILES_PER_PAGE', 20);
+ define('MESSAGES_PER_PAGE', 20);
  
  define('FOREIGN_NOTICE_SEND', 1);
  define('FOREIGN_NOTICE_RECV', 2);
@@@ -45,7 -46,6 +46,7 @@@ define('NOTICE_INBOX_SOURCE_SUB', 1)
  define('NOTICE_INBOX_SOURCE_GROUP', 2);
  define('NOTICE_INBOX_SOURCE_REPLY', 3);
  define('NOTICE_INBOX_SOURCE_FORWARD', 4);
 +define('NOTICE_INBOX_SOURCE_PROFILE_TAG', 5);
  define('NOTICE_INBOX_SOURCE_GATEWAY', -1);
  
  # append our extlib dir as the last-resort place to find libs
diff --combined lib/personalgroupnav.php
index ccd4ab506fcc4b992dd7af31ab749e410180896b,2e15ca5f6a02d3c9eec40802f97dd6759d8ac435..28bf8c529fd1091df9f019605febef408f957a4b
@@@ -2,7 -2,7 +2,7 @@@
  /**
   * StatusNet, the distributed open-source microblogging tool
   *
-  * Base class for all actions (~views)
+  * Menu for personal group of actions
   *
   * PHP version 5
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   *
-  * @category  Action
+  * @category  Menu
   * @package   StatusNet
   * @author    Evan Prodromou <evan@status.net>
   * @author    Sarven Capadisli <csarven@status.net>
-  * @copyright 2008 StatusNet, Inc.
+  * @copyright 2008-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/
   */
@@@ -32,41 -32,20 +32,20 @@@ if (!defined('STATUSNET') && !defined('
      exit(1);
  }
  
- require_once INSTALLDIR.'/lib/widget.php';
  /**
-  * Base class for all actions
-  *
-  * This is the base class for all actions in the package. An action is
-  * more or less a "view" in an MVC framework.
-  *
-  * Actions are responsible for extracting and validating parameters; using
-  * model classes to read and write to the database; and doing ouput.
+  * Menu for personal group of actions
   *
-  * @category Output
+  * @category Menu
   * @package  StatusNet
   * @author   Evan Prodromou <evan@status.net>
   * @author   Sarven Capadisli <csarven@status.net>
+  * @copyright 2008-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/
-  *
-  * @see      HTMLOutputter
   */
- class PersonalGroupNav extends Widget
- {
-     var $action = null;
-     /**
-      * Construction
-      *
-      * @param Action $action current action, used for output
-      */
-     function __construct($action=null)
-     {
-         parent::__construct($action);
-         $this->action = $action;
-     }
  
+ class PersonalGroupNav extends Menu
+ {
      /**
       * Show the menu
       *
       */
      function show()
      {
-         $user = null;
+         $user         = common_current_user();
  
-         // FIXME: we should probably pass this in
+         if (empty($user)) {
+             throw new ServerException('Do not show personal group nav with no current user.');
+         }
  
-         $action = $this->action->trimmed('action');
-         $nickname = $this->action->trimmed('nickname');
+         $user_profile = $user->getProfile();
+         $nickname     = $user->nickname;
+         $name         = $user_profile->getBestName();
  
-         if ($nickname) {
-             $user = User::staticGet('nickname', $nickname);
-             $user_profile = $user->getProfile();
-             $name = $user_profile->getBestName();
-         } else {
-             // @fixme can this happen? is this valid?
-             $user_profile = false;
-             $name = $nickname;
-         }
+         $action = $this->actionName;
+         $mine = ($this->action->arg('nickname') == $nickname); // @fixme kinda vague
  
          $this->out->elementStart('ul', array('class' => 'nav'));
  
                                                                 $nickname)),
                                   _('Home'),
                                   sprintf(_('%s and friends'), $name),
-                                  $action == 'all', 'nav_timeline_personal');
+                                  $mine && $action =='all', 'nav_timeline_personal');
+             $this->out->menuItem(common_local_url('showstream', array('nickname' =>
+                                                                       $nickname)),
+                                  _('Profile'),
+                                  _('Your profile'),
+                                  $mine && $action =='showstream',
+                                  'nav_profile');
              $this->out->menuItem(common_local_url('replies', array('nickname' =>
                                                                     $nickname)),
                                   _('Replies'),
                                   sprintf(_('Replies to %s'), $name),
-                                  $action == 'replies', 'nav_timeline_replies');
+                                  $mine && $action =='replies', 'nav_timeline_replies');
              $this->out->menuItem(common_local_url('showfavorites', array('nickname' =>
                                                                           $nickname)),
                                   _('Favorites'),
                                   sprintf(_('%s\'s favorite notices'), ($user_profile) ? $name : _('User')),
-                                  $action == 'showfavorites', 'nav_timeline_favorites');
+                                  $mine && $action =='showfavorites', 'nav_timeline_favorites');
 +            $this->out->menuItem(common_local_url('peopletagsbyuser', array('nickname' =>
 +                                                                  $nickname)),
 +                             _('People tags'),
-                              sprintf(_('People tags by %s'), ($user_profile) ? $user_profile->getBestName() : _('User')),
++                             sprintf(_('People tags by %s'), ($user_profile) ? $name : _('User')),
 +                             in_array($action, array('peopletagsbyuser', 'peopletagsforuser')),
 +                             'nav_timeline_peopletags');
  
              $cur = common_current_user();
  
  
                  $this->out->menuItem(common_local_url('inbox', array('nickname' =>
                                                                       $nickname)),
-                                      _('Inbox'),
+                                      _('Messages'),
                                       _('Your incoming messages'),
-                                      $action == 'inbox');
-                 $this->out->menuItem(common_local_url('outbox', array('nickname' =>
-                                                                       $nickname)),
-                                      _('Outbox'),
-                                      _('Your sent messages'),
-                                      $action == 'outbox');
+                                      $mine && $action =='inbox');
              }
              Event::handle('EndPersonalGroupNav', array($this));
          }
          $this->out->elementEnd('ul');
diff --combined lib/publicgroupnav.php
index c2dc11831a1e8d631b0ed17a00c8ae43b6c05b87,bd2ad5312419467b243090491019e49e8415db05..16916ca6bb0e6702bc92ce3f8ac1b5e25eb4a703
@@@ -46,22 -46,8 +46,8 @@@ require_once INSTALLDIR.'/lib/widget.ph
   * @see      Widget
   */
  
- class PublicGroupNav extends Widget
+ class PublicGroupNav extends Menu
  {
-     var $action = null;
-     /**
-      * Construction
-      *
-      * @param Action $action current action, used for output
-      */
-     function __construct($action=null)
-     {
-         parent::__construct($action);
-         $this->action = $action;
-     }
      /**
       * Show the menu
       *
              $this->out->menuItem(common_local_url('publictagcloud'), _('Recent tags'),
                  _('Recent tags'), $action_name == 'publictagcloud', 'nav_recent-tags');
  
 +            $this->out->menuItem(common_local_url('publicpeopletagcloud'), _('People tags'),
 +                _('People tags'), in_array($action_name, array('publicpeopletagcloud',
 +                                    'peopletag', 'selftag')), 'nav_people-tags');
 +
              if (count(common_config('nickname', 'featured')) > 0) {
                  $this->out->menuItem(common_local_url('featured'), _('Featured'),
                      _('Featured users'), $action_name == 'featured', 'nav_featured');
diff --combined lib/router.php
index 7f17f2d0a5145f683e398a47a781be60e842e8ab,efbd2c6cddee3003010b0b709b539d23b8bfa121..5ee7fcc5c0966c89350b2e0aed2169fc3137bb1d
@@@ -227,9 -227,7 +227,9 @@@ class Route
              $m->connect('main/sup/:seconds', array('action' => 'sup'),
                          array('seconds' => '[0-9]+'));
  
 -            $m->connect('main/tagother/:id', array('action' => 'tagother'));
 +            $m->connect('main/tagprofile', array('action' => 'tagprofile'));
 +            $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'),
 +                                               array('id' => '[0-9]+'));
  
              $m->connect('main/oembed',
                          array('action' => 'oembed'));
              $m->connect('conversation/:id',
                          array('action' => 'conversation'),
                          array('id' => '[0-9]+'));
+             $m->connect('conversation/:id/replies',
+                         array('action' => 'conversationreplies'),
+                         array('id' => '[0-9]+'));
  
              $m->connect('message/new', array('action' => 'newmessage'));
              $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => Nickname::DISPLAY_FMT));
                          array('action' => 'tag'),
                          array('tag' => self::REGEX_TAG));
  
 -            $m->connect('peopletag/:tag',
 -                        array('action' => 'peopletag'),
 -                        array('tag' => self::REGEX_TAG));
 -
              // groups
  
              $m->connect('group/new', array('action' => 'newgroup'));
                                'id' => '[a-zA-Z0-9]+',
                                'format' => '(xml|json)'));
  
 +            // Lists (people tags)
 +
 +            $m->connect('api/lists/memberships.:format',
 +                        array('action' => 'ApiListMemberships',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/lists/memberships.:format',
 +                        array('action' => 'ApiListMemberships',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/lists/subscriptions.:format',
 +                        array('action' => 'ApiListSubscriptions',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/lists/subscriptions.:format',
 +                        array('action' => 'ApiListSubscriptions',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +            $m->connect('api/lists.:format',
 +                        array('action' => 'ApiLists',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/lists.:format',
 +                        array('action' => 'ApiLists',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/lists/:id.:format',
 +                        array('action' => 'ApiList',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'id' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/lists/:id/statuses.:format',
 +                        array('action' => 'ApiTimelineList',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'id' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json|rss|atom)'));
 +
 +            $m->connect('api/:user/:list_id/members.:format',
 +                        array('action' => 'ApiListMembers',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'list_id' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/:list_id/subscribers.:format',
 +                        array('action' => 'ApiListSubscribers',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'list_id' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/:list_id/members/:id.:format',
 +                        array('action' => 'ApiListMember',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'list_id' => '[a-zA-Z0-9]+',
 +                              'id' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +
 +            $m->connect('api/:user/:list_id/subscribers/:id.:format',
 +                        array('action' => 'ApiListSubscriber',
 +                              'user' => '[a-zA-Z0-9]+',
 +                              'list_id' => '[a-zA-Z0-9]+',
 +                              'id' => '[a-zA-Z0-9]+',
 +                              'format' => '(xml|json)'));
 +
              // Tags
              $m->connect('api/statusnet/tags/timeline/:tag.:format',
                          array('action' => 'ApiTimelineTag',
                                  array('nickname' => Nickname::DISPLAY_FMT));
                  }
  
 +                // people tags
 +
 +                $m->connect('peopletags', array('action' => 'publicpeopletagcloud'));
 +
 +                $m->connect('peopletag/:tag', array('action' => 'peopletag',
 +                                                    'tag'    => self::REGEX_TAG));
 +
 +                $m->connect('selftag/:tag', array('action' => 'selftag',
 +                                                  'tag'    => self::REGEX_TAG));
 +
 +                $m->connect('main/addpeopletag', array('action' => 'addpeopletag'));
 +
 +                $m->connect('main/removepeopletag', array('action' => 'removepeopletag'));
 +
 +                $m->connect('main/profilecompletion', array('action' => 'profilecompletion'));
 +
 +                $m->connect('main/peopletagautocomplete', array('action' => 'peopletagautocomplete'));
 +
 +                $m->connect(':nickname/peopletags',
 +                                array('action' => 'peopletagsbyuser',
 +                                      'nickname' => Nickname::DISPLAY_FMT));
 +
 +                $m->connect(':nickname/peopletags/private',
 +                                array('action' => 'peopletagsbyuser',
 +                                      'nickname' => Nickname::DISPLAY_FMT,
 +                                      'private' => 1));
 +
 +                $m->connect(':nickname/peopletags/public',
 +                                array('action' => 'peopletagsbyuser',
 +                                      'nickname' => Nickname::DISPLAY_FMT,
 +                                      'public' => 1));
 +
 +                $m->connect(':nickname/othertags',
 +                                array('action' => 'peopletagsforuser',
 +                                      'nickname' => Nickname::DISPLAY_FMT));
 +
 +                $m->connect(':nickname/peopletagsubscriptions',
 +                                array('action' => 'peopletagsubscriptions',
 +                                      'nickname' => Nickname::DISPLAY_FMT));
 +
 +                $m->connect(':tagger/all/:tag/subscribers',
 +                                array('action' => 'peopletagsubscribers',
 +                                      'tagger' => Nickname::DISPLAY_FMT,
 +                                      'tag' => self::REGEX_TAG));
 +
 +                $m->connect(':tagger/all/:tag/tagged',
 +                                array('action' => 'peopletagged',
 +                                      'tagger' => Nickname::DISPLAY_FMT,
 +                                      'tag' => self::REGEX_TAG));
 +
 +                $m->connect(':tagger/all/:tag/edit',
 +                                array('action' => 'editpeopletag',
 +                                      'tagger' => Nickname::DISPLAY_FMT,
 +                                      'tag' => self::REGEX_TAG));
 +
 +                foreach(array('subscribe', 'unsubscribe') as $v) {
 +                    $m->connect('peopletag/:id/'.$v,
 +                                    array('action' => $v.'peopletag',
 +                                          'id' => '[0-9]{1,64}'));
 +                }
 +                $m->connect('user/:tagger_id/profiletag/:id/id',
 +                                array('action' => 'profiletagbyid',
 +                                      'tagger_id' => '[0-9]+',
 +                                      'id' => '[0-9]+'));
 +
 +                $m->connect(':tagger/all/:tag',
 +                                array('action' => 'showprofiletag',
 +                                      'tagger' => Nickname::DISPLAY_FMT,
 +                                      'tag' => self::REGEX_TAG));
 +
                  foreach (array('subscriptions', 'subscribers') as $a) {
                      $m->connect(':nickname/'.$a.'/:tag',
                                  array('action' => $a),
diff --combined lib/subgroupnav.php
index 5fccb157442dc04447d66beabcbebf04eca26f34,ee4b0a8dffc482242c94d0bccb92e8cfd2f10f26..0227e1fb5ed96b9c73a904356ce57c4fb883584a
@@@ -22,7 -22,7 +22,7 @@@
   * @category  Subs
   * @package   StatusNet
   * @author    Evan Prodromou <evan@status.net>
-  * @copyright 2008-2009 StatusNet, Inc.
+  * @copyright 2008-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/
   */
@@@ -43,9 -43,8 +43,8 @@@ require_once INSTALLDIR.'/lib/widget.ph
   * @link     http://status.net/
   */
  
- class SubGroupNav extends Widget
+ class SubGroupNav extends Menu
  {
-     var $action = null;
      var $user = null;
  
      /**
@@@ -57,7 -56,6 +56,6 @@@
      function __construct($action=null, $user=null)
      {
          parent::__construct($action);
-         $this->action = $action;
          $this->user = $user;
      }
  
                                           $this->user->nickname),
                                   $action == 'usergroups',
                                   'nav_usergroups');
 +            $this->out->menuItem(common_local_url('peopletagsbyuser',
 +                                                  array('nickname' =>
 +                                                        $this->user->nickname)),
 +                                 _('People tags'),
 +                                 sprintf(_('People tags by %s'),
 +                                         $this->user->nickname),
 +                                 in_array($action, array('peopletagsbyuser', 'peopletagsforuser')),
 +                                 'nav_timeline_peopletags');
 +
              if (common_config('invite', 'enabled') && !is_null($cur) && $this->user->id === $cur->id) {
                  $this->out->menuItem(common_local_url('invite'),
                                       _('Invite'),
diff --combined lib/util.php
index 2e6768f90e34448d15c7781438b04e193aaf1fb9,511dcdfb867ed4eb3acf00a0a1d105e8d8b25903..db090cb288171e247e95e109141af3fdb26f6c0c
@@@ -318,6 -318,7 +318,7 @@@ function common_set_user($user
          if (Event::handle('StartSetUser', array(&$user))) {
              if (!empty($user)) {
                  if (!$user->hasRight(Right::WEBLOGIN)) {
+                     // TRANS: Authorisation exception thrown when a user a not allowed to login.
                      throw new AuthorizationException(_('Not allowed to log in.'));
                  }
                  common_ensure_session();
@@@ -753,19 -754,17 +754,19 @@@ function common_find_mentions($text, $n
          foreach ($hmatches[1] as $hmatch) {
  
              $tag = common_canonical_tag($hmatch[0]);
 -
 -            $tagged = Profile_tag::getTagged($sender->id, $tag);
 -
 -            $url = common_local_url('subscriptions',
 -                                    array('nickname' => $sender->nickname,
 -                                          'tag' => $tag));
 -
 -            $mentions[] = array('mentioned' => $tagged,
 -                                'text' => $hmatch[0],
 -                                'position' => $hmatch[1],
 -                                'url' => $url);
 +            $plist = Profile_list::getByTaggerAndTag($sender->id, $tag);
 +            if (!empty($plist) && !$plist->private) {
 +                $tagged = $sender->getTaggedSubscribers($tag);
 +
 +                $url = common_local_url('showprofiletag',
 +                                        array('tagger' => $sender->nickname,
 +                                              'tag' => $tag));
 +
 +                $mentions[] = array('mentioned' => $tagged,
 +                                    'text' => $hmatch[0],
 +                                    'position' => $hmatch[1],
 +                                    'url' => $url);
 +            }
          }
  
          Event::handle('EndFindMentions', array($sender, $text, &$mentions));
@@@ -869,7 -868,7 +870,7 @@@ function common_replace_urls_callback($
   * @param callable $callback
   * @param mixed $arg optional argument to pass on as second param to callback
   * @return string
-  * 
+  *
   * @access private
   */
  function callback_helper($matches, $callback, $arg=null) {
@@@ -1033,19 -1032,13 +1034,13 @@@ function common_linkify($url) 
   */
  function common_shorten_links($text, $always = false, User $user=null)
  {
-     common_debug("common_shorten_links() called");
      $user = common_current_user();
  
      $maxLength = User_urlshortener_prefs::maxNoticeLength($user);
  
-     common_debug("maxLength = $maxLength");
      if ($always || mb_strlen($text) > $maxLength) {
-         common_debug("Forcing shortening");
          return common_replace_urls_callback($text, array('File_redirection', 'forceShort'), $user);
      } else {
-         common_debug("Not forcing shortening");
          return common_replace_urls_callback($text, array('File_redirection', 'makeShort'), $user);
      }
  }
@@@ -1343,28 -1336,28 +1338,28 @@@ function common_date_string($dt
      } else if ($diff < 3300) {
          $minutes = round($diff/60);
          // TRANS: Used in notices to indicate when the notice was made compared to now.
-         return sprintf( ngettext('about one minute ago', 'about %d minutes ago', $minutes), $minutes);
+         return sprintf( _m('about one minute ago', 'about %d minutes ago', $minutes), $minutes);
      } else if ($diff < 5400) {
          // TRANS: Used in notices to indicate when the notice was made compared to now.
          return _('about an hour ago');
      } else if ($diff < 22 * 3600) {
          $hours = round($diff/3600);
          // TRANS: Used in notices to indicate when the notice was made compared to now.
-         return sprintf( ngettext('about one hour ago', 'about %d hours ago', $hours), $hours);
+         return sprintf( _m('about one hour ago', 'about %d hours ago', $hours), $hours);
      } else if ($diff < 37 * 3600) {
          // TRANS: Used in notices to indicate when the notice was made compared to now.
          return _('about a day ago');
      } else if ($diff < 24 * 24 * 3600) {
          $days = round($diff/(24*3600));
          // TRANS: Used in notices to indicate when the notice was made compared to now.
-         return sprintf( ngettext('about one day ago', 'about %d days ago', $days), $days);
+         return sprintf( _m('about one day ago', 'about %d days ago', $days), $days);
      } else if ($diff < 46 * 24 * 3600) {
          // TRANS: Used in notices to indicate when the notice was made compared to now.
          return _('about a month ago');
      } else if ($diff < 330 * 24 * 3600) {
          $months = round($diff/(30*24*3600));
          // TRANS: Used in notices to indicate when the notice was made compared to now.
-         return sprintf( ngettext('about one month ago', 'about %d months ago',$months), $months);
+         return sprintf( _m('about one month ago', 'about %d months ago',$months), $months);
      } else if ($diff < 480 * 24 * 3600) {
          // TRANS: Used in notices to indicate when the notice was made compared to now.
          return _('about a year ago');
@@@ -2068,28 -2061,22 +2063,22 @@@ function common_database_tablename($tab
   */
  function common_shorten_url($long_url, User $user=null, $force = false)
  {
-     common_debug("Shortening URL '$long_url' (force = $force)");
      $long_url = trim($long_url);
  
      $user = common_current_user();
  
      $maxUrlLength = User_urlshortener_prefs::maxUrlLength($user);
-     common_debug("maxUrlLength = $maxUrlLength");
  
      // $force forces shortening even if it's not strictly needed
      // I doubt URL shortening is ever 'strictly' needed. - ESP
  
      if (mb_strlen($long_url) < $maxUrlLength && !$force) {
-         common_debug("Skipped shortening URL.");
          return $long_url;
      }
  
      $shortenerName = User_urlshortener_prefs::urlShorteningService($user);
  
-     common_debug("Shortener name = '$shortenerName'");
-     if (Event::handle('StartShortenUrl', 
+     if (Event::handle('StartShortenUrl',
                        array($long_url, $shortenerName, &$shortenedUrl))) {
          if ($shortenerName == 'internal') {
              $f = File::processNew($long_url);
index 666683affc4d8a8389e6e212115d8b2b46f2a181,738481149c89ee6636408780011ab14aecf49c1a..77b64dbb1d3fc3c6c777a22251f1699a5e8831d5
@@@ -56,25 -56,14 +56,25 @@@ class OStatusPlugin extends Plugi
                      array('action' => 'ownerxrd'));
          $m->connect('main/ostatus',
                      array('action' => 'ostatusinit'));
 +        $m->connect('main/ostatustag',
 +                    array('action' => 'ostatustag'));
 +        $m->connect('main/ostatustag?nickname=:nickname',
 +                    array('action' => 'ostatustag'), array('nickname' => '[A-Za-z0-9_-]+'));
          $m->connect('main/ostatus?nickname=:nickname',
                    array('action' => 'ostatusinit'), array('nickname' => '[A-Za-z0-9_-]+'));
          $m->connect('main/ostatus?group=:group',
                    array('action' => 'ostatusinit'), array('group' => '[A-Za-z0-9_-]+'));
 +        $m->connect('main/ostatus?peopletag=:peopletag&tagger=:tagger',
 +                  array('action' => 'ostatusinit'), array('tagger' => '[A-Za-z0-9_-]+',
 +                                                          'peopletag' => '[A-Za-z0-9_-]+'));
 +
 +        // Remote subscription actions
          $m->connect('main/ostatussub',
                      array('action' => 'ostatussub'));
          $m->connect('main/ostatusgroup',
                      array('action' => 'ostatusgroup'));
 +        $m->connect('main/ostatuspeopletag',
 +                    array('action' => 'ostatuspeopletag'));
  
          // PuSH actions
          $m->connect('main/push/hub', array('action' => 'pushhub'));
@@@ -90,9 -79,6 +90,9 @@@
          $m->connect('main/salmon/group/:id',
                      array('action' => 'groupsalmon'),
                      array('id' => '[0-9]+'));
 +        $m->connect('main/salmon/peopletag/:id',
 +                    array('action' => 'peopletagsalmon'),
 +                    array('id' => '[0-9]+'));
          return true;
      }
  
              $salmonAction = 'groupsalmon';
              $group = $feed->getGroup();
              $id = $group->id;
 +        } else if ($feed instanceof AtomListNoticeFeed) {
 +            $salmonAction = 'peopletagsalmon';
 +            $peopletag = $feed->getList();
 +            $id = $peopletag->id;
          } else {
              return true;
          }
       * Add in an OStatus subscribe button
       */
      function onStartProfileRemoteSubscribe($output, $profile)
-     function onStartGroupSubscribe($output, $group)
 +    {
 +        $this->onStartProfileListItemActionElements($output, $profile);
 +        return false;
 +    }
 +
++    function onStartGroupSubscribe($widget, $group)
      {
          $cur = common_current_user();
  
          if (empty($cur)) {
--            // Add an OStatus subscribe
+             $output->elementStart('li', 'entity_subscribe');
++            $profile = $peopletag->getTagger();
              $url = common_local_url('ostatusinit',
 -                                    array('nickname' => $profile->nickname));
 -            $output->element('a', array('href' => $url,
 +                                    array('group' => $group->nickname));
-             $output->element('a', array('href' => $url,
++            $widget->out->element('a', array('href' => $url,
                                          'class' => 'entity_remote_subscribe'),
-                                 _m('Join'));
 -                                // TRANS: Link description for link to subscribe to a remote user.
+                                 _m('Subscribe'));
  
+             $output->elementEnd('li');
++            return false;
          }
  
 -        return false;
 +        return true;
      }
  
 -    function onStartGroupSubscribe($widget, $group)
 +    function onStartSubscribePeopletagForm($output, $peopletag)
      {
          $cur = common_current_user();
  
          if (empty($cur)) {
 -            // Add an OStatus subscribe
 +            $output->elementStart('li', 'entity_subscribe');
 +            $profile = $peopletag->getTagger();
              $url = common_local_url('ostatusinit',
 -                                    array('group' => $group->nickname));
 -            $widget->out->element('a', array('href' => $url,
 +                                    array('tagger' => $profile->nickname, 'peopletag' => $peopletag->tag));
 +            $output->element('a', array('href' => $url,
                                          'class' => 'entity_remote_subscribe'),
 -                                // TRANS: Link description for link to join a remote group.
 -                                _m('Join'));
 +                                _m('Subscribe'));
 +
 +            $output->elementEnd('li');
 +            return false;
          }
  
          return true;
      }
  
 +    function onStartShowTagProfileForm($action, $profile)
 +    {
 +        $action->elementStart('form', array('method' => 'post',
 +                                           'id' => 'form_tag_user',
 +                                           'class' => 'form_settings',
 +                                           'name' => 'tagprofile',
 +                                           'action' => common_local_url('tagprofile', array('id' => @$profile->id))));
 +
 +        $action->elementStart('fieldset');
 +        $action->element('legend', null, _('Tag remote profile'));
 +        $action->hidden('token', common_session_token());
 +
 +        $user = common_current_user();
 +
 +        $action->elementStart('ul', 'form_data');
 +        $action->elementStart('li');
 +
 +        $action->input('uri', _('Remote profile'), $action->trimmed('uri'),
 +                     _('OStatus user\'s address, like nickname@example.com or http://example.net/nickname'));
 +        $action->elementEnd('li');
 +        $action->elementEnd('ul');
 +        $action->submit('fetch', _('Fetch'));
 +        $action->elementEnd('fieldset');
 +        $action->elementEnd('form');
 +    }
 +
 +    function onStartTagProfileAction($action, $profile)
 +    {
 +        $err = null;
 +        $uri = $action->trimmed('uri');
 +
 +        if (!$profile && $uri) {
 +            try {
 +                if (Validate::email($uri)) {
 +                    $oprofile = Ostatus_profile::ensureWebfinger($uri);
 +                } else if (Validate::uri($uri)) {
 +                    $oprofile = Ostatus_profile::ensureProfileURL($uri);
 +                } else {
 +                    throw new Exception('Invalid URI');
 +                }
 +
 +                // redirect to the new profile.
 +                common_redirect(common_local_url('tagprofile', array('id' => $oprofile->profile_id)), 303);
 +                return false;
 +
 +            } catch (Exception $e) {
 +                $err = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
 +            }
 +
 +            $action->showForm($err);
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    /*
 +     * If the field being looked for is URI look for the profile
 +     */
 +    function onStartProfileCompletionSearch($action, $profile, $search_engine) {
 +        if ($action->field == 'uri') {
 +            $user = new User();
 +            $profile->joinAdd($user);
 +            $profile->whereAdd('uri LIKE "%' . $profile->escape($q) . '%"');
 +            $profile->query();
 +
 +            if ($profile->N == 0) {
 +                try {
 +                    if (Validate::email($q)) {
 +                        $oprofile = Ostatus_profile::ensureWebfinger($q);
 +                    } else if (Validate::uri($q)) {
 +                        $oprofile = Ostatus_profile::ensureProfileURL($q);
 +                    } else {
 +                        throw new Exception('Invalid URI');
 +                    }
 +                    return $this->filter(array($oprofile->localProfile()));
 +
 +                } catch (Exception $e) {
 +                    $this->msg = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
 +                    return array();
 +                }
 +            }
 +            return false;
 +        }
 +        return true;
 +    }
 +
      /**
       * Find any explicit remote mentions. Accepted forms:
       *   Webfinger: @user@example.com
          }
      }
  
 +    /**
 +     * When one of our local users tries to subscribe to a remote peopletag,
 +     * notify the remote server. If the notification is rejected,
 +     * deny the subscription.
 +     *
 +     * @param Profile_list $peopletag
 +     * @param User         $user
 +     *
 +     * @return mixed hook return value
 +     */
 +
 +    function onStartSubscribePeopletag($peopletag, $user)
 +    {
 +        $oprofile = Ostatus_profile::staticGet('peopletag_id', $peopletag->id);
 +        if ($oprofile) {
 +            if (!$oprofile->subscribe()) {
 +                throw new Exception(_m('Could not set up remote peopletag subscription.'));
 +            }
 +
 +            $sub = $user->getProfile();
 +            $tagger = Profile::staticGet($peopletag->tagger);
 +
 +            $act = new Activity();
 +            $act->id = TagURI::mint('subscribe_peopletag:%d:%d:%s',
 +                                    $sub->id,
 +                                    $peopletag->id,
 +                                    common_date_iso8601(time()));
 +
 +            $act->actor = ActivityObject::fromProfile($sub);
 +            $act->verb = ActivityVerb::FOLLOW;
 +            $act->object = $oprofile->asActivityObject();
 +
 +            $act->time = time();
 +            $act->title = _m("Follow list");
 +            $act->content = sprintf(_m("%s is now following people tagged %s by %s."),
 +                                    $sub->getBestName(),
 +                                    $oprofile->getBestName(),
 +                                    $tagger->getBestName());
 +
 +            if ($oprofile->notifyActivity($act, $sub)) {
 +                return true;
 +            } else {
 +                $oprofile->garbageCollect();
 +                throw new Exception(_m("Failed subscribing to remote peopletag."));
 +            }
 +        }
 +    }
 +
 +    /**
 +     * When one of our local users unsubscribes to a remote peopletag, notify the remote
 +     * server.
 +     *
 +     * @param Profile_list $peopletag
 +     * @param User         $user
 +     *
 +     * @return mixed hook return value
 +     */
 +
 +    function onEndUnsubscribePeopletag($peopletag, $user)
 +    {
 +        $oprofile = Ostatus_profile::staticGet('peopletag_id', $peopletag->id);
 +        if ($oprofile) {
 +            // Drop the PuSH subscription if there are no other subscribers.
 +            $oprofile->garbageCollect();
 +
 +            $sub = Profile::staticGet($user->id);
 +            $tagger = Profile::staticGet($peopletag->tagger);
 +
 +            $act = new Activity();
 +            $act->id = TagURI::mint('unsubscribe_peopletag:%d:%d:%s',
 +                                    $sub->id,
 +                                    $peopletag->id,
 +                                    common_date_iso8601(time()));
 +
 +            $act->actor = ActivityObject::fromProfile($member);
 +            $act->verb = ActivityVerb::UNFOLLOW;
 +            $act->object = $oprofile->asActivityObject();
 +
 +            $act->time = time();
 +            $act->title = _m("Unfollow peopletag");
 +            $act->content = sprintf(_m("%s stopped following the list %s by %s."),
 +                                    $sub->getBestName(),
 +                                    $oprofile->getBestName(),
 +                                    $tagger->getBestName());
 +
 +            $oprofile->notifyActivity($act, $user);
 +        }
 +    }
 +
      /**
       * Notify remote users when their notices get favorited.
       *
          return true;
      }
  
 +    function onEndTagProfile($ptag)
 +    {
 +        $oprofile = Ostatus_profile::staticGet('profile_id', $ptag->tagged);
 +
 +        if (empty($oprofile)) {
 +            return true;
 +        }
 +
 +        $plist = $ptag->getMeta();
 +        if ($plist->private) {
 +            return true;
 +        }
 +
 +        $act = new Activity();
 +
 +        $tagger = $plist->getTagger();
 +        $tagged = Profile::staticGet('id', $ptag->tagged);
 +
 +        $act->verb = ActivityVerb::TAG;
 +        $act->id   = TagURI::mint('tag_profile:%d:%d:%s',
 +                                  $plist->tagger, $plist->id,
 +                                  common_date_iso8601(time()));
 +        $act->time = time();
 +        $act->title = _("Tag");
 +        $act->content = sprintf(_("%s tagged %s in the list %s"),
 +                                $tagger->getBestName(),
 +                                $tagged->getBestName(),
 +                                $plist->getBestName());
 +
 +        $act->actor  = ActivityObject::fromProfile($tagger);
 +        $act->objects = array(ActivityObject::fromProfile($tagged));
 +        $act->target = ActivityObject::fromPeopletag($plist);
 +
 +        $oprofile->notifyActivity($act, $tagger);
 +
 +        // initiate a PuSH subscription for the person being tagged
 +        if (!$oprofile->subscribe()) {
 +            throw new Exception(sprintf(_('Could not complete subscription to remote '.
 +                                          'profile\'s feed. Tag %s could not be saved.'), $ptag->tag));
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    function onEndUntagProfile($ptag)
 +    {
 +        $oprofile = Ostatus_profile::staticGet('profile_id', $ptag->tagged);
 +
 +        if (empty($oprofile)) {
 +            return true;
 +        }
 +
 +        $plist = $ptag->getMeta();
 +        if ($plist->private) {
 +            return true;
 +        }
 +
 +        $act = new Activity();
 +
 +        $tagger = $plist->getTagger();
 +        $tagged = Profile::staticGet('id', $ptag->tagged);
 +
 +        $act->verb = ActivityVerb::UNTAG;
 +        $act->id   = TagURI::mint('untag_profile:%d:%d:%s',
 +                                  $plist->tagger, $plist->id,
 +                                  common_date_iso8601(time()));
 +        $act->time = time();
 +        $act->title = _("Untag");
 +        $act->content = sprintf(_("%s untagged %s from the list %s"),
 +                                $tagger->getBestName(),
 +                                $tagged->getBestName(),
 +                                $plist->getBestName());
 +
 +        $act->actor  = ActivityObject::fromProfile($tagger);
 +        $act->objects = array(ActivityObject::fromProfile($tagged));
 +        $act->target = ActivityObject::fromPeopletag($plist);
 +
 +        $oprofile->notifyActivity($act, $tagger);
 +
 +        // unsubscribe to PuSH feed if no more required
 +        $oprofile->garbageCollect();
 +
 +        return true;
 +    }
 +
      /**
       * Notify remote users when their notices get de-favorited.
       *
          return true;
      }
  
 -    function onStartProfileListItemActionElements($item)
 +    function onStartProfileListItemActionElements($item, $profile=null)
      {
          if (!common_logged_in()) {
  
  
              if (!empty($profileUser)) {
  
 -                $output = $item->out;
 +                if ($item instanceof Action) {
 +                    $output = $item;
 +                    $profile = $item->profile;
 +                } else {
 +                    $output = $item->out;
 +                }
  
                  // Add an OStatus subscribe
                  $output->elementStart('li', 'entity_subscribe');
                                    // TRANS: Link text for a user to subscribe to an OStatus user.
                                   _m('Subscribe'));
                  $output->elementEnd('li');
 +
 +                $output->elementStart('li', 'entity_tag');
 +                $url = common_local_url('ostatustag',
 +                                        array('nickname' => $profileUser->nickname));
 +                $output->element('a', array('href' => $url,
 +                                            'class' => 'entity_remote_tag'),
 +                                 _m('Tag'));
 +                $output->elementEnd('li');
              }
          }
  
index 177b92e6926f98914d211b3551b2d9374c902ca0,cd0e00d8607490a3c9105be79331369a574cc8a5..325820a0d9bfbbfeb8376f467e9a778f39762082
@@@ -218,7 -218,7 +218,7 @@@ font-weight:bold
  #form_settings_avatar legend,
  #newgroup legend,
  #editgroup legend,
 -#form_tag_user legend,
 +.form_tag_user legend,
  #form_remote_subscribe legend,
  #form_openid_login legend,
  #form_search legend,
  #form_password_change legend,
  .form_entity_block legend,
  #form_filter_bytag legend,
 -#apioauthauthorize_allowdeny {
 +#apioauthauthorize_allowdeny,
 +.form_tag_user_wrap form,
 +.form_tag_user_wrap label,
 +.form_tag_user_wrap legend {
  display:none;
  }
 -
 +.form_tag_user_wrap {
 +clear:both;
 +}
 +.form_tag_user {
 +float:left;
 +width:auto;
 +}
 +.form_tag_user input.submit {
 +width:50px;
 +}
  .form_settings .form_data p.form_guide {
  clear:both;
  margin-left:26%;
@@@ -609,19 -597,19 +609,19 @@@ float:left
  font-size:1.3em;
  margin-bottom:7px;
  }
- .form_notice label[for=notice_data-attach],
- .form_notice #notice_data-attach {
+ .form_notice label.notice_data-attach,
+ .form_notice input.notice_data-attach {
  position:absolute;
  top:25px;
  right:10.5%;
  cursor:pointer;
  }
- .form_notice label[for=notice_data-attach] {
+ .form_notice label.notice_data-attach {
  text-indent:-9999px;
  width:16px;
  height:16px;
  }
- .form_notice #notice_data-attach {
+ .form_notice input.notice_data-attach {
  padding:0;
  height:16px;
  }
@@@ -658,7 -646,8 +658,8 @@@ float:left
  max-width:322px;
  }
  .form_notice .error,
- .form_notice .success {
+ .form_notice .success,
+ .form_notice .notice-status {
  float:left;
  clear:both;
  width:81.5%;
@@@ -673,7 -662,8 +674,8 @@@ overflow:auto
  margin-right:2.5%;
  font-size:1.1em;
  }
- .form_notice .attach-status button.close {
+ .form_notice .attach-status button.close,
+ .form_notice .notice-status button.close,{
  float:right;
  font-size:0.8em;
  }
@@@ -719,7 -709,7 +721,7 @@@ min-height:123px
  float:left;
  margin-bottom:18px;
  margin-left:0;
 -overflow:hidden;
 +overflow:visible;
  }
  .entity_profile dt,
  #entity_statistics dt {
@@@ -747,17 -737,6 +749,17 @@@ margin-bottom:18px
  margin-left:113px;
  margin-bottom:4px;
  }
 +.entity_tags p.error {
 +clear:both;
 +}
 +
 +.peopletags_edit_button {
 +cursor:pointer;
 +border:0;
 +padding:0;
 +width:16px;
 +height:16px;
 +}
  
  .entity_profile .entity_fn,
  .entity_profile .entity_nickname {
@@@ -798,6 -777,7 +800,6 @@@ font-style:italic
  .entity_actions {
  float:right;
  margin-left:2%;
 -margin-bottom:18px;
  min-width:21%;
  }
  .entity_actions h2 {
@@@ -986,89 -966,13 +988,89 @@@ min-height:60px
  .profile .form_group_join legend,
  .profile .form_group_leave legend,
  .profile .form_user_subscribe legend,
 -.profile .form_user_unsubscribe legend {
 +.profile .form_user_unsubscribe legend,
 +.form_user_add_peopletag legend,
 +.form_user_remove_peopletag legend {
  display:none;
  }
 -
 +.profile_search_wrap h3 {
 +float:left;
 +font-weight:normal;
 +margin-right:10px;
 +}
  .profiles {
  list-style-type:none;
  }
 +.peopletag .entry-content {
 +width:auto;
 +}
 +.peopletag .tagged-count a:after,
 +.peopletag .subscriber-count a:after {
 +content: ':';
 +}
 +.peopletag .updated {
 +display:none;
 +}
 +.peopletag .tag a{
 +font-weight: bold;
 +text-decoration: none;
 +}
 +.peopletag .tag:before {
 +/* raquo */
 +content: "\00BB";
 +}
 +.peopletag .entity_statistics {
 +font-size:80%;
 +}
 +.peopletag .entity_statistics a {
 +text-decoration:none;
 +}
 +
 +.profile-lister {
 +list-style-type:none;
 +}
 +.profile-lister li {
 +min-height:30px;
 +padding:5px;
 +clear:both;
 +}
 +.profile-lister li a {
 +text-decoration:none;
 +}
 +.profile-lister li .photo {
 +display:inline;
 +margin-right:7px;
 +margin-bottom:-5px;
 +}
 +.profile-lister li .fn {
 +font-weight:bold;
 +}
 +#profile_search_results {
 +border-radius:4px;
 +-moz-border-radius:4px;
 +-webkit-border-radius:4px;
 +}
 +.form_peopletag_edit_user_search legend,
 +.form_peopletag_edit_user_search label,
 +.form_peopletag_edit_user_search .form_guide {
 +display:none;
 +}
 +.form_peopletag_edit_user_search #field {
 +height:30px;
 +}
 +.form_peopletag_edit_user_search .submit {
 +width:60px;
 +}
 +.form_user_remove_peopletag,
 +.form_user_add_peopletag {
 +float:right;
 +}
 +.form_user_add_peopletag input.submit,
 +.form_user_remove_peopletag input.submit {
 +width:100px;
 +padding-left:25px;
 +text-align:left;
 +}
  .profile .entity_profile .fn.nickname,
  .profile .entity_profile .url[rel~=contact] {
  margin-left:0;
@@@ -1088,19 -992,15 +1090,19 @@@ clear:none
  .profile .entity_profile .entity_tags,
  .profile .entity_profile .form_subscription_edit {
  margin-left:59px;
 +margin-bottom:7px;
  clear:none;
  display:block;
  width:auto;
  }
 -.profile .entity_profile .entity_tags dt {
 +.entity_profile .entity_tags dt {
  display:inline;
 +float:left;
  margin-right:11px;
  }
 -
 +.profile .entity_profile .form_subscription_edit {
 +clear:left;
 +}
  .profile .entity_profile .form_subscription_edit label {
  font-weight:normal;
  margin-right:11px;
@@@ -1185,8 -1085,7 +1187,8 @@@ width:14.5%
  /* NOTICE */
  .notice,
  .profile,
 -.application {
 +.application,
 +#content .peopletag {
  position:relative;
  padding-top:11px;
  padding-bottom:11px;
@@@ -1454,8 -1353,7 +1456,8 @@@ margin-left:0
  }
  .notice-options input,
  .notice-options a,
 -.notice-options .repeated {
 +.notice-options .repeated,
 +.peopletags_edit_button {
  text-indent:-9999px;
  outline:none;
  }
@@@ -1499,8 -1397,7 +1501,8 @@@ height:16px
  position:relative;
  padding-left:16px;
  }
 -.notice .attachment.more {
 +.notice .attachment.more,
 +.mode-private .privacy_mode {
  text-indent:-9999px;
  width:16px;
  height:16px;
@@@ -1613,22 -1510,16 +1615,22 @@@ padding-left:7px
  border-left-width:1px;
  border-left-style:solid;
  }
 -#filter_tags #filter_tags_all {
 +#filter_tags #filter_tags_all,
 +#filter_tags #filter_tags_for {
  margin-left:0;
  border-left:0;
  padding-left:0;
  }
 -#filter_tags_all a {
 +#filter_tags_all a,
 +#filter_tags_for a  {
 +text-decoration:none;
  font-weight:bold;
  margin-top:7px;
  float:left;
  }
 +#filter_tags_for a {
 +margin:0;
 +}
  
  #filter_tags_item label {
  margin-right:7px;
@@@ -1647,15 -1538,6 +1649,15 @@@ position:relative
  top:3px;
  left:3px;
  }
 +#filter_tags #form_filter_bymode .form_guide {
 +display:none;
 +}
 +#filter_tags #form_filter_bymode .checkbox {
 +float:none;
 +}
 +#filter_tags #form_filter_bymode legend {
 +display:none;
 +}
  
  .pagination {
  float:left;
@@@ -1911,33 -1793,6 +1913,33 @@@ width:auto
  min-width:0;
  }
  
 +/* tag autocomplete */
 +
 +.tagInputDiv {
 +display: none;
 +position: absolute;
 +overflow: auto;
 +margin-top:-1px;
 +z-index: 99;
 +}
 +
 +.tagInputLine {
 +font-weight: normal;
 +padding:4px;
 +}
 +
 +.tagInputLineTag {
 +min-width: 150px;
 +display: inline-block;
 +}
 +
 +.tagInputLineFreq {
 +min-width: 50px;
 +text-align: right;
 +display: inline-block;
 +float:right;
 +}
 +
  .inline-attachment img {
      /* Why on earth is this changed to block at the top? */
      display: inline;
index 666529743975d0afa6cc70ac9c78ec9342eca352,3e3bfdfa86a0c60cc4456cd2e08fa51407df8126..8f92c2d82028c4ba8240bf5142d4b353d5d5ca03
@@@ -136,7 -136,6 +136,7 @@@ color:#002FA7
  .notice,
  .profile,
  .application,
 +.peopletag,
  #content tbody tr {
  border-top-color:#C8D1D5;
  }
@@@ -153,14 -152,14 +153,14 @@@ color:#333333
  .entity_actions .dialogbox input {
  color:#000000;
  }
- .form_notice label[for=notice_data-attach] {
+ .form_notice label.notice_data-attach {
  background-position:0 -328px;
  }
- .form_notice #notice_data-attach {
+ .form_notice input.notice_data-attach {
  opacity:0;
  }
  
- .form_notice label[for=notice_data-attach],
+ .form_notice label.notice_data-attach,
  #export_data li a.rss,
  #export_data li a.atom,
  #export_data li a.foaf,
@@@ -187,13 -186,8 +187,13 @@@ button.close
  .form_user_unsubscribe input.submit,
  .form_group_join input.submit,
  .form_user_subscribe input.submit,
 +.form_user_remove_peopletag input.submit,
 +.form_user_add_peopletag input.submit,
 +.form_peopletag_subscribe input.submit,
 +.form_peopletag_unsubscribe input.submit,
  .form_remote_authorize input.submit,
  .entity_subscribe a,
 +.entity_tag a,
  .entity_moderation p,
  .entity_sandbox input.submit,
  .entity_silence input.submit,
@@@ -211,9 -205,7 +211,9 @@@ button.minimize
  .entity_subscribe input.submit,
  #realtime_play,
  #realtime_pause,
 -#realtime_popup {
 +#realtime_popup,
 +.peopletags_edit_button,
 +.mode-private .privacy_mode {
  background-image:url(../../base/images/icons/icons-01.gif);
  background-repeat:no-repeat;
  background-color:transparent;
@@@ -308,66 -300,33 +308,66 @@@ background-position:0 1px
  .form_group_join input.submit,
  .form_group_leave input.submit,
  .form_user_subscribe input.submit,
 +.form_peopletag_subscribe input.submit,
 +.form_peopletag_unsubscribe input.submit,
  .form_user_unsubscribe input.submit,
  .form_remote_authorize input.submit,
 +.form_user_add_peopletag input.submit,
 +.form_user_remove_peopletag input.submit,
  .entity_subscribe a {
  background-color:#AAAAAA;
  color:#FFFFFF;
  }
  .form_group_leave input.submit,
 -.form_user_unsubscribe input.submit {
 +.form_user_unsubscribe input.submit,
 +.form_user_remove_peopletag input.submit,
 +.form_peopletag_unsubscribe input.submit {
  background-position:5px -1246px;
  }
  .form_group_join input.submit,
  .form_user_subscribe input.submit,
 +.form_peopletag_subscribe input.submit,
  .form_remote_authorize input.submit,
 -.entity_subscribe a {
 +.form_user_add_peopletag input.submit,
 +.entity_subscribe a,
 +.entity_tag a {
  background-position:5px -1181px;
  }
  
 -.entity_edit a {
 +.profile-lister li {
 +border-top: 1px #eee solid;
 +}
 +.profile-lister li:first-child {
 +border:0;
 +}
 +#profile_search_results.profile-lister {
 +max-height:800px;
 +margin:10px 0;
 +border:1px #ddd solid;
 +}
 +
 +.entity_edit a, .peopletags_edit_button {
  background-position: 5px -719px;
  }
 +
 +.peopletags_edit_button {
 +background-position: 0px -724px;
 +}
 +.entity_tags li.mode-private {
 +color: #829D25;
 +}
 +.mode-private .privacy_mode {
 +background-position: 0px -1978px;
 +}
 +
  .entity_send-a-message a {
  background-position: 5px -852px;
  }
  .entity_send-a-message .form_notice,
  .entity_moderation:hover ul,
  .entity_role:hover ul,
 -.dialogbox {
 +.dialogbox,
 +#profile_search_results {
  box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
  -moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
  -webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.7);
@@@ -556,18 -515,4 +556,18 @@@ background-position:90% 47%
  background-position:10% 47%;
  }
  
 +.tagInputDiv {
 +background-color: white;
 +border: 1px solid lightgray;
 +}
 +
 +.tagInputDiv .mode-public .privacy_mode {
 +display:none;
 +}
 +
 +.tagInputSel {
 +background-color: gray;
 +color:white;
 +}
 +
  }/*end of @media screen, projection, tv*/