diff --git a/app/assets/javascripts/app/views/publisher/aspects_selector.js b/app/assets/javascripts/app/views/publisher/aspects_selector.js
new file mode 100644
index 0000000000000000000000000000000000000000..ecb2c576433f43c8e06c05cfd7aae322a5979569
--- /dev/null
+++ b/app/assets/javascripts/app/views/publisher/aspects_selector.js
@@ -0,0 +1,62 @@
+/*   Copyright (c) 2010-2012, Diaspora Inc.  This file is
+ *   licensed under the Affero General Public License version 3 or later.  See
+ *   the COPYRIGHT file.
+ */
+
+(function(){
+  // mixin-object, used in conjunction with the publisher to provide the
+  // functionality for selecting aspects
+  app.views.PublisherAspectsSelector = {
+
+    // event handler for aspect selection
+    toggleAspect: function(evt) {
+      var el = $(evt.target);
+      var btn = el.parent('.dropdown').find('.button');
+
+      // visually toggle the aspect selection
+      if( el.is('.radio') ) {
+        AspectsDropdown.toggleRadio(el);
+      } else {
+        AspectsDropdown.toggleCheckbox(el);
+      }
+
+      // update the selection summary
+      AspectsDropdown.updateNumber(
+        el.closest(".dropdown_list"),
+        null,
+        el.parent().find('li.selected').length,
+        ''
+      );
+
+      this._updateSelectedAspectIds();
+    },
+
+    // take care of the form fields that will indicate the selected aspects
+    _updateSelectedAspectIds: function() {
+      var self = this;
+
+      // remove previous selection
+      this.$('input[name="aspect_ids[]"]').remove();
+
+      // create fields for current selection
+      this.$('.dropdown .dropdown_list li.selected').each(function() {
+        var el = $(this);
+        var aspectId = el.data('aspect_id');
+
+        self._addHiddenAspectInput(aspectId);
+
+        // close the dropdown when a radio item was selected
+        if( el.is('.radio') ) {
+          el.closest('.dropdown').removeClass('active');
+        }
+      });
+    },
+
+    _addHiddenAspectInput: function(id) {
+      var uid = _.uniqueId('aspect_ids_');
+      this.$('.content_creation form').append(
+        '<input id="'+uid+'" name="aspect_ids[]" type="hidden" value="'+id+'">'
+      );
+    }
+  };
+})();
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/publisher/getting_started.js b/app/assets/javascripts/app/views/publisher/getting_started.js
new file mode 100644
index 0000000000000000000000000000000000000000..815f05585473d4c03506063c2eb4f52f1ecc3ad9
--- /dev/null
+++ b/app/assets/javascripts/app/views/publisher/getting_started.js
@@ -0,0 +1,65 @@
+/*   Copyright (c) 2010-2012, Diaspora Inc.  This file is
+ *   licensed under the Affero General Public License version 3 or later.  See
+ *   the COPYRIGHT file.
+ */
+
+(function(){
+  // mixin-object, used in conjunction with the publisher to provide the
+  // functionality for displaying 'getting-started' information
+  app.views.PublisherGettingStarted = {
+
+    // initiate all the popover message boxes
+    triggerGettingStarted: function() {
+      this._addPopover(this.el_input, {
+        trigger: 'manual',
+        offset: 30,
+        id: 'first_message_explain',
+        placement: 'right',
+        html: true
+      }, 600);
+      this._addPopover(this.$('.dropdown'), {
+        trigger: 'manual',
+        offset: 10,
+        id: 'message_visibility_explain',
+        placement: 'bottom',
+        html: true
+      }, 1000);
+      this._addPopover($('#gs-shim'), {
+        trigger: 'manual',
+        offset: -5,
+        id: 'stream_explain',
+        placement: 'left',
+        html: true
+      }, 1400);
+
+      // hide some popovers when a post is created
+      this.$('.button.creation').click(function() {
+        this.$('.dropdown').popover('hide');
+        this.el_input.popover('hide');
+      });
+    },
+
+    _addPopover: function(el, opts, timeout) {
+      el.popover(opts);
+      el.click(function() {
+        el.popover('hide');
+      });
+
+      // show the popover after the given timeout
+      setTimeout(function() {
+        el.popover('show');
+
+        // disable 'getting started' when the last popover is closed
+        var popup = el.data('popover').$tip[0];
+        var close = $(popup).find('.close');
+
+        close.click(function() {
+          if( $('.popover').length==1 ) {
+            $.get('/getting_started_completed');
+          }
+          el.popover('hide');
+        });
+      }, timeout);
+    }
+  };
+})();
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/publisher/services.js b/app/assets/javascripts/app/views/publisher/services.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8f250093e13e62030fcca77b2222dfc9f1feecd
--- /dev/null
+++ b/app/assets/javascripts/app/views/publisher/services.js
@@ -0,0 +1,51 @@
+/*   Copyright (c) 2010-2012, Diaspora Inc.  This file is
+ *   licensed under the Affero General Public License version 3 or later.  See
+ *   the COPYRIGHT file.
+ */
+
+(function(){
+  // mixin-object, used in conjunction with the publisher to provide the
+  // functionality for selecting services for cross-posting
+  app.views.PublisherServices = {
+
+    // visually toggle the icon and kick-off all other actions for cross-posting
+    toggleService: function(evt) {
+      var el = $(evt.target);
+      var provider = el.attr('id');
+
+      el.toggleClass("dim");
+
+      this._createCounter();
+      this._toggleServiceField(provider);
+    },
+
+    // keep track of character count
+    _createCounter: function() {
+      // remove obsolete counter
+      this.$('.counter').remove();
+
+      // create new counter
+      var min = 40000;
+      var a = this.$('.service_icon:not(.dim)');
+      if(a.length > 0){
+        $.each(a, function(index, value){
+          var num = parseInt($(value).attr('maxchar'));
+          if (min > num) { min = num; }
+        });
+        this.el_input.charCount({allowed: min, warning: min/10 });
+      }
+    },
+
+    // add or remove the input containing the selected service
+    _toggleServiceField: function(provider) {
+      var hidden_field = this.$('input[name="services[]"][value="'+provider+'"]');
+      if(hidden_field.length > 0){
+        hidden_field.remove();
+      } else {
+        var uid = _.uniqueId('services_');
+        this.$(".content_creation form").append(
+        '<input id="'+uid+'" name="services[]" type="hidden" value="'+provider+'">');
+      }
+    }
+  };
+})();
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/publisher_view.js b/app/assets/javascripts/app/views/publisher_view.js
index 24b61ff7f8fbea814b484a7a9fae22b8259b607f..c8c952f24331744a7d81876941e8de051180048e 100644
--- a/app/assets/javascripts/app/views/publisher_view.js
+++ b/app/assets/javascripts/app/views/publisher_view.js
@@ -1,19 +1,57 @@
-//this file is the scary no-no-zone bad-touch of our backbone code.
-//after re-writing/eliminating the existing Publisher let's re-write
-//this with PANACHE!    <333 Dennis
+/*   Copyright (c) 2010-2012, Diaspora Inc.  This file is
+ *   licensed under the Affero General Public License version 3 or later.  See
+ *   the COPYRIGHT file.
+ */
+
+//= require ./publisher/services
+//= require ./publisher/aspects_selector
+//= require ./publisher/getting_started
+
+app.views.Publisher = Backbone.View.extend(_.extend(
+  app.views.PublisherServices,
+  app.views.PublisherAspectsSelector,
+  app.views.PublisherGettingStarted, {
 
-app.views.Publisher = Backbone.View.extend({
-  
   el : "#publisher",
 
   events : {
     "focus textarea" : "open",
     "click #hide_publisher" : "clear",
-    "submit form" : "createStatusMessage"
+    "submit form" : "createStatusMessage",
+    "click .service_icon": "toggleService",
+    "textchange #status_message_fake_text": "handleTextchange",
+    "click .dropdown .dropdown_list li": "toggleAspect"
   },
 
   initialize : function(){
-    this.collection = this.collection //takes a Posts collection
+    // init shortcut references to the various elements
+    this.el_input = this.$('#status_message_fake_text');
+    this.el_hiddenInput = this.$('#status_message_text');
+    this.el_wrapper = this.$('#publisher_textarea_wrapper');
+    this.el_submit = this.$('input[type=submit]');
+    this.el_photozone = this.$('#photodropzone');
+
+    // init mentions plugin
+    Mentions.initialize(this.el_input);
+
+    // init autoresize plugin
+    this.el_input.autoResize({ 'extraSpace' : 10, 'maxHeight' : Infinity });
+
+    // sync textarea content
+    if( this.el_hiddenInput.val() == "" ) {
+      this.el_hiddenInput.val( this.el_input.val() );
+    }
+
+    // hide close button, in case publisher is standalone
+    // (e.g. bookmarklet, mentions popup)
+    if( this.options.standalone ) {
+      this.$('#hide_publisher').hide();
+    }
+
+    // this has to be here, otherwise for some reason the callback for the
+    // textchange event won't be called in Backbone...
+    this.el_input.bind('textchange', $.noop);
+
     return this;
   },
 
@@ -49,36 +87,72 @@ app.views.Publisher = Backbone.View.extend({
   },
 
   clear : function() {
-    this.$('textarea').val("");
-    this.$('#publisher_textarea_wrapper').removeClass("with_attachments");
+    // clear text(s)
+    this.el_input.val('');
+    this.el_hiddenInput.val('');
+
+    // remove mentions
+    this.el_input.mentionsInput('reset');
 
     // remove photos
-    this.$("#photodropzone").find('li').remove();
+    this.el_photozone.find('li').remove();
     this.$("input[name='photos[]']").remove();
+    this.el_wrapper.removeClass("with_attachments");
 
     // close publishing area (CSS)
     this.close();
 
-    Publisher.clear()
+    // disable submitting
+    this.checkSubmitAvailability();
 
     return this;
   },
 
   open : function() {
-    $(this.el).removeClass('closed');
-    this.$("#publisher_textarea_wrapper").addClass('active');
+    // visually 'open' the publisher
+    this.$el.removeClass('closed');
+    this.el_wrapper.addClass('active');
+
+    // fetch contacts for mentioning
+    Mentions.fetchContacts();
 
     return this;
   },
 
   close : function() {
     $(this.el).addClass("closed");
-    this.$("#publisher_textarea_wrapper").removeClass("active");
-    this.$("textarea").css('height', '');
+    this.el_wrapper.removeClass("active");
+    this.el_input.css('height', '');
 
     return this;
+  },
+
+  checkSubmitAvailability: function() {
+    if( this._submittable() ) {
+      this.el_submit.removeAttr('disabled');
+    } else {
+      this.el_submit.attr('disabled','disabled');
+    }
+  },
+
+  // determine submit availability
+  _submittable: function() {
+    var onlyWhitespaces = ($.trim(this.el_input.val()) === ''),
+        isPhotoAttached = (this.el_photozone.children().length > 0);
+
+    return (!onlyWhitespaces || isPhotoAttached);
+  },
+
+  handleTextchange: function() {
+    var self = this;
+
+    this.checkSubmitAvailability();
+    this.el_input.mentionsInput("val", function(value){
+      self.el_hiddenInput.val(value);
+    });
   }
-});
+
+}));
 
 // jQuery helper for serializing a <form> into JSON
 $.fn.serializeObject = function()
diff --git a/app/assets/javascripts/aspects-dropdown.js b/app/assets/javascripts/aspects-dropdown.js
index 61f035d17a630487e1c2193801b2b21d3b58bd32..50ee48fab0f1296a2e165fb90934706a89943afd 100644
--- a/app/assets/javascripts/aspects-dropdown.js
+++ b/app/assets/javascripts/aspects-dropdown.js
@@ -1,4 +1,4 @@
-//   Copyright (c) 2010-2011, Diaspora Inc.  This file is
+//   Copyright (c) 2010-2012, Diaspora Inc.  This file is
 //   licensed under the Affero General Public License version 3 or later.  See
 //   the COPYRIGHT file.
 
diff --git a/app/assets/javascripts/home.js b/app/assets/javascripts/home.js
index 7eb2db8743210b85343a131a898cf374d39168a0..e53601da111a71450248a75204411d4c32905af3 100644
--- a/app/assets/javascripts/home.js
+++ b/app/assets/javascripts/home.js
@@ -2,7 +2,7 @@
  *   licensed under the Affero General Public License version 3 or later.  See
  *   the COPYRIGHT file.
  */
-//= require publisher
+
 //= require jquery.textchange
 //= require aspect-edit-pane
 //= require fileuploader-custom
\ No newline at end of file
diff --git a/app/assets/javascripts/publisher.js b/app/assets/javascripts/publisher.js
deleted file mode 100644
index 40eeb458024de798c2c2d3db2b7b379d6ec5d22c..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/publisher.js
+++ /dev/null
@@ -1,221 +0,0 @@
-/*   Copyright (c) 2010-2011, Diaspora Inc.  This file is
- *   licensed under the Affero General Public License version 3 or later.  See
- *   the COPYRIGHT file.
- */
-
-//TODO: make this a widget
-var Publisher = {
-  bookmarklet : false,
-
-  form: function(){
-    return Publisher.cachedForm = Publisher.cachedForm || $('#publisher');
-  },
-
-  input: function(){
-    return Publisher.cachedInput = Publisher.cachedInput || Publisher.form().find('#status_message_fake_text');
-  },
-
-  wrapper: function(){
-    return Publisher.cachedWrapper = Publisher.cachedWrapper || Publisher.form().find('#publisher_textarea_wrapper');
-  },
-
-  hiddenInput: function(){
-    return Publisher.cachedHiddenInput= Publisher.cachedHiddenInput || Publisher.form().find('#status_message_text');
-  },
-
-  submit: function(){
-    return Publisher.cachedSubmit = Publisher.cachedSubmit || Publisher.form().find("input[type='submit']");
-  },
-
-  determineSubmitAvailability: function(){
-    var onlyWhitespaces = ($.trim(Publisher.input().val()) === ''),
-        isSubmitDisabled = Publisher.submit().attr('disabled'),
-        isPhotoAttached = ($("#photodropzone").children().length > 0);
-
-    if ((onlyWhitespaces && !isPhotoAttached) && !isSubmitDisabled) {
-      Publisher.submit().attr('disabled', 'disabled');
-    } else if ((!onlyWhitespaces || isPhotoAttached) && isSubmitDisabled) {
-      Publisher.submit().removeAttr('disabled');
-    }
-  },
-
-  clear: function(){
-    $("#photodropzone").find('li').remove();
-    Publisher.input().mentionsInput("reset");
-    Publisher.wrapper().removeClass("with_attachments");
-    Publisher.hiddenInput().val('');
-    Publisher.determineSubmitAvailability()
-  },
-
-  bindServiceIcons: function(){
-    $(".service_icon").bind("click", function(evt){
-      $(this).toggleClass("dim");
-      Publisher.toggleServiceField($(this));
-    });
-  },
-
-  toggleServiceField: function(service){
-    Publisher.createCounter(service);
-
-    var provider = service.attr('id');
-    var hidden_field = $('#publisher [name="services[]"][value="'+provider+'"]');
-    if(hidden_field.length > 0){
-      hidden_field.remove();
-    } else {
-      $("#publisher .content_creation form").append(
-      '<input id="services_" name="services[]" type="hidden" value="'+provider+'">');
-    }
-  },
-
-  isPublicPost: function(){
-    return $('#publisher [name="aspect_ids[]"]').first().val() == "public";
-  },
-
-  isToAllAspects: function(){
-    return $('#publisher [name="aspect_ids[]"]').first().val() == "all_aspects";
-  },
-
-  selectedAspectIds: function() {
-    var aspects = $('#publisher [name="aspect_ids[]"]');
-    var aspectIds = [];
-    aspects.each(function() { aspectIds.push( parseInt($(this).attr('value'))); });
-    return aspectIds;
-  },
-
-  removeRadioSelection: function(hiddenFields){
-    $.each(hiddenFields, function(index, value){
-      var el = $(value);
-
-      if(el.val() == "all_aspects" || el.val() == "public") {
-        el.remove();
-      }
-    });
-  },
-
-  toggleAspectIds: function(li) {
-    var aspectId = li.attr('data-aspect_id'),
-        hiddenFields = $('#publisher [name="aspect_ids[]"]'),
-        appendId = function(){
-          $("#publisher .content_creation form").append(
-          '<input id="aspect_ids_" name="aspect_ids[]" type="hidden" value="'+aspectId+'">');
-        };
-
-    if(li.hasClass('radio')){
-      $.each(hiddenFields, function(index, value){
-        $(value).remove();
-      });
-      appendId();
-
-      // close dropdown after selecting a binary option
-      li.closest('.dropdown').removeClass('active');
-
-    } else {
-      var hiddenField = $('#publisher [name="aspect_ids[]"][value="'+aspectId+'"]');
-
-      // remove all radio selections
-      Publisher.removeRadioSelection(hiddenFields);
-
-      if(hiddenField.length > 0){
-        hiddenField.remove();
-      } else {
-        appendId();
-      }
-    }
-  },
-
-  createCounter: function(service){
-    var counter = $("#publisher .counter");
-    counter.remove();
-
-    var min = 40000;
-    var a = $('.service_icon:not(.dim)');
-    if(a.length > 0){
-      $.each(a, function(index, value){
-        var num = parseInt($(value).attr('maxchar'));
-        if (min > num) { min = num; }
-      });
-      $('#status_message_fake_text').charCount({allowed: min, warning: min/10 });
-    }
-  },
-
-  bindAspectToggles: function() {
-    $('#publisher .dropdown .dropdown_list li').bind("click", function(evt){
-      var li = $(this),
-          button = li.parent('.dropdown').find('.button');
-
-      if(li.hasClass('radio')){
-        AspectsDropdown.toggleRadio(li);
-      } else {
-        AspectsDropdown.toggleCheckbox(li);
-      }
-
-      AspectsDropdown.updateNumber(li.closest(".dropdown_list"), null, li.parent().find('li.selected').length, '');
-
-      Publisher.toggleAspectIds(li);
-    });
-  },
-
-  textChange : function(){
-    Publisher.determineSubmitAvailability();
-    Publisher.input().mentionsInput("val", function(value) {
-      Publisher.hiddenInput().val(value);
-    });
-  },
-
-  triggerGettingStarted: function(){
-    Publisher.setUpPopovers("#publisher .dropdown", {trigger: 'manual', offset: 10, id: "message_visibility_explain", placement:'bottom', html:true}, 1000);
-    Publisher.setUpPopovers("#publisher #status_message_fake_text", {trigger: 'manual', placement: 'right', offset: 30, id: "first_message_explain", html:true}, 600);
-    Publisher.setUpPopovers("#gs-shim", {trigger: 'manual', placement: 'left', id:"stream_explain", offset: -5, html:true}, 1400);
-
-    $("#publisher .button.creation").bind("click", function(){
-       $("#publisher .dropdown").popover("hide");
-       $("#publisher #status_message_fake_text").popover("hide");
-    });
-  },
-
-  setUpPopovers: function(selector, options, timeout){
-    var selection = $(selector);
-    selection.popover(options);
-    selection.bind("click", function(){$(this).popover("hide")});
-
-    setTimeout(function(){
-      selection.popover("show");
-
-      var popup = selection.data('popover').$tip[0],
-          closeIcon = $(popup).find(".close");
-
-      closeIcon.bind("click",function(){
-        if($(".popover").length == 1){
-          $.get("/getting_started_completed");
-        };
-        selection.popover("hide");
-      });
-    }, timeout);
-  },
-
-  initialize: function() {
-    Publisher.cachedForm = Publisher.cachedSubmit =
-      Publisher.cachedInput = Publisher.cachedHiddenInput = false;
-
-    Publisher.bindServiceIcons();
-    Publisher.bindAspectToggles();
-
-    Mentions.initialize(Publisher.input());
-
-    Publisher.input().bind("focus", function(){
-      Mentions.fetchContacts();
-    })
-
-    if(Publisher.hiddenInput().val() === "") {
-      Publisher.hiddenInput().val(Publisher.input().val());
-    }
-
-    Publisher.input().autoResize({ 'extraSpace' : 10, 'maxHeight' : Infinity });
-    Publisher.input().bind('textchange', Publisher.textChange);
-  }
-};
-
-$(document).ready(function() {
-  Publisher.initialize();
-  Diaspora.page.subscribe("stream/reloaded", Publisher.initialize);
-});
diff --git a/app/views/photos/_new_photo.haml b/app/views/photos/_new_photo.haml
index b5f418f2f0b7d02a37de44baeb405ffa8c4cd577..b4aec04b3d69d284bac98ccb1b8433513835349e 100644
--- a/app/views/photos/_new_photo.haml
+++ b/app/views/photos/_new_photo.haml
@@ -29,7 +29,7 @@
         $('#file-upload').addClass("loading");
         $('#publisher').find("input[type='submit']").attr('disabled','disabled');
 
-        Publisher.wrapper().addClass("with_attachments");
+        app.publisher.el_wrapper.addClass("with_attachments");
         $('#photodropzone').append(
           "<li class='publisher_photo loading' style='position:relative;'>" +
             "#{escape_javascript(image_tag('ajax-loader2.gif'))}" +
@@ -43,7 +43,7 @@
             url = responseJSON.data.photo.unprocessed_image.url,
             currentPlaceholder = $('li.loading').first();
 
-        Publisher.wrapper().addClass("with_attachments");
+        app.publisher.el_wrapper.addClass("with_attachments");
         $('#new_status_message').append("<input type='hidden' value='" + id + "' name='photos[]' />");
 
         // replace image placeholders
@@ -70,7 +70,7 @@
                             photo.fadeOut(400, function(){
                               photo.remove();
                               if ( $('.publisher_photo').length  == 0){
-                                Publisher.wrapper().removeClass("with_attachments");
+                                app.publisher.el_wrapper.removeClass("with_attachments");
                               }
                             });
                           }
diff --git a/app/views/shared/_publisher.html.haml b/app/views/shared/_publisher.html.haml
index 4c8d86094b1cf76088bd3e0e2f493720a2d17be1..2de6bcdb5870686803d387f8487225666c3e166b 100644
--- a/app/views/shared/_publisher.html.haml
+++ b/app/views/shared/_publisher.html.haml
@@ -5,7 +5,7 @@
 -if publisher_explain
   :javascript
     $(document).ready(function() {
-        Publisher.triggerGettingStarted();
+        if( app.publisher ) app.publisher.triggerGettingStarted();
       });
 
 #publisher{:class => ((aspect == :profile || publisher_open) ? "mention_popup" : "closed")}
diff --git a/app/views/status_messages/bookmarklet.html.haml b/app/views/status_messages/bookmarklet.html.haml
index b1e7e2c366f7faf63e2a6e7fb245b28dc7c46519..992da8686a4c42e0c2ba57509cfb77b82e9749db 100644
--- a/app/views/status_messages/bookmarklet.html.haml
+++ b/app/views/status_messages/bookmarklet.html.haml
@@ -9,8 +9,9 @@
     = render :partial => 'shared/publisher', :locals => { :aspect => :profile, :selected_aspects => @aspects,  :aspect_ids => @aspect_ids }
 
 :javascript
-  Publisher.bookmarklet = true;
-  app.publisher = new app.views.Publisher();
+  app.publisher = new app.views.Publisher({
+    standalone: true
+  });
 
   var contents = "#{escape_javascript params[:title]} - #{escape_javascript params[:url]}";
   var notes    = "#{escape_javascript params[:notes]}";
diff --git a/app/views/status_messages/new.html.haml b/app/views/status_messages/new.html.haml
index 53185ce1161051317b1b3018cf16b45ee8688cc3..fb87afa891585329c24574554112e8a4b132e2ec 100644
--- a/app/views/status_messages/new.html.haml
+++ b/app/views/status_messages/new.html.haml
@@ -1,13 +1,8 @@
 -#   Copyright (c) 2010-2011, Diaspora Inc.  This file is
 -#   licensed under the Affero General Public License version 3 or later.  See
 -#   the COPYRIGHT file.
-= javascript_include_tag  'jquery.textchange.js', "publisher.js"
 
-:javascript
-  $(function() {
-    $("#publisher").bind('ajax:success', function(){ location.reload(); });
-    Publisher.bookmarklet = true;
-  });
+= javascript_include_tag :home
 
 #new_status_message_pane
   .span-15.last
@@ -17,3 +12,10 @@
 
     = render :partial => 'shared/publisher', :locals => { :aspect => @aspect, :aspect_ids => @aspect_ids, :selected_aspects => @aspects_with_person, :person => @person}
 
+:javascript
+  $(function() {
+    app.publisher = new app.views.Publisher({
+      standalone: true
+    });
+    $("#publisher").bind('ajax:success', function(){ location.reload(); });
+  });
\ No newline at end of file
diff --git a/features/mentions_from_profile_page.feature b/features/mentions_from_profile_page.feature
index a3b86c3d7cd186f0827ff8a76266a64c5e180898..be8e5d113bb9f0a66daee03ed3e7e95236eb09d1 100644
--- a/features/mentions_from_profile_page.feature
+++ b/features/mentions_from_profile_page.feature
@@ -24,8 +24,7 @@ Feature: mentioning a contact from their profile page
     Scenario: mentioning while posting to all aspects
       Given I am on "alice@alice.alice"'s page
       And I have turned off jQuery effects
-      And I click "Mention" button
-      And I expand the publisher in the modal window
+      And I want to mention her from the profile
       And I append "I am eating a yogurt" to the publisher
       And I press "Share" in the modal window
       And I wait for the ajax to finish
@@ -42,9 +41,7 @@ Feature: mentioning a contact from their profile page
     Scenario: mentioning while posting to just one aspect
       Given I am on "alice@alice.alice"'s page
       And I have turned off jQuery effects
-      And I click "Mention" button
-      And I wait for the ajax to finish
-      And I expand the publisher in the modal window
+      And I want to mention her from the profile
       And I append "I am eating a yogurt" to the publisher
       And I press the aspect dropdown in the modal window
       And I toggle the aspect "NotPostingThingsHere" in the modal window
diff --git a/features/step_definitions/custom_web_steps.rb b/features/step_definitions/custom_web_steps.rb
index 67b95e7eb872ec6334ceb7f6b2cada9292f52bf0..6942655d0ca3862547ef0a1bd2bc2bc95a0408b4 100644
--- a/features/step_definitions/custom_web_steps.rb
+++ b/features/step_definitions/custom_web_steps.rb
@@ -46,10 +46,18 @@ end
 
 When /^I append "([^"]*)" to the publisher$/ do |stuff|
   elem = find('#status_message_fake_text')
-  elem.native.send_keys ' ' + stuff
+  elem.native.send_keys(' ' + stuff)
 
   wait_until do
-    page.find("#status_message_text").value.match(/#{stuff}/)
+    find('#status_message_text').value.include?(stuff)
+  end
+end
+
+And /^I want to mention (?:him|her) from the profile$/ do
+  click_link("Mention")
+  wait_for_ajax_to_finish
+  within('#facebox') do
+    click_publisher
   end
 end
 
diff --git a/features/support/publishing_cuke_helpers.rb b/features/support/publishing_cuke_helpers.rb
index 6bfa7a290423fff983f8842ec75c288a8a0c55dd..de447cc7a3195b2884929f949f2e439ad9ae092e 100644
--- a/features/support/publishing_cuke_helpers.rb
+++ b/features/support/publishing_cuke_helpers.rb
@@ -13,7 +13,7 @@ module PublishingCukeHelpers
   def click_publisher
     page.execute_script('
       $("#publisher").removeClass("closed");
-      $("#publisher").find("textarea").focus();
+      $("#publisher").find("#status_message_fake_text").focus();
     ')
   end
 
@@ -79,7 +79,7 @@ module PublishingCukeHelpers
   end
 
   def comment_on_show_page(comment_text)
-    within("#post-interactions") do 
+    within("#post-interactions") do
       focus_comment_box(".label.comment")
       make_comment(comment_text, "new-comment-text")
     end
diff --git a/vendor/assets/javascripts/jquery.autoresize.js b/lib/assets/javascripts/jquery.autoresize.js
similarity index 100%
rename from vendor/assets/javascripts/jquery.autoresize.js
rename to lib/assets/javascripts/jquery.autoresize.js
diff --git a/spec/javascripts/app/views/publisher_view_spec.js b/spec/javascripts/app/views/publisher_view_spec.js
index c18408a9f47c376bd1349198a5e93a02a25928ed..0e301847c3c75ce5623d46e994353258e7b9ed4c 100644
--- a/spec/javascripts/app/views/publisher_view_spec.js
+++ b/spec/javascripts/app/views/publisher_view_spec.js
@@ -1,76 +1,268 @@
+/*   Copyright (c) 2010-2012, Diaspora Inc.  This file is
+ *   licensed under the Affero General Public License version 3 or later.  See
+ *   the COPYRIGHT file.
+ */
+
 describe("app.views.Publisher", function() {
-  beforeEach(function() {
-    // should be jasmine helper
-    loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
+  describe("standalone", function() {
+    beforeEach(function() {
+      // should be jasmine helper
+      loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
 
-    spec.loadFixture("aspects_index");
-    this.view = new app.views.Publisher();
-  });
+      spec.loadFixture("aspects_index");
+      this.view = new app.views.Publisher({
+        standalone: true
+      });
+    });
 
-  describe("#open", function() {
-    it("removes the 'closed' class from the publisher element", function() {
-      expect($(this.view.el)).toHaveClass("closed");
-      this.view.open($.Event());
-      expect($(this.view.el)).not.toHaveClass("closed");
+    it("hides the close button in standalone mode", function() {
+      expect(this.view.$('#hide_publisher').is(':visible')).toBeFalsy();
     });
   });
 
-  describe("#close", function() {
+  context("plain publisher", function() {
     beforeEach(function() {
-      this.view.open($.Event());
+      // should be jasmine helper
+      loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}});
+
+      spec.loadFixture("aspects_index");
+      this.view = new app.views.Publisher();
+    });
+
+    describe("#open", function() {
+      it("removes the 'closed' class from the publisher element", function() {
+        expect($(this.view.el)).toHaveClass("closed");
+        this.view.open($.Event());
+        expect($(this.view.el)).not.toHaveClass("closed");
+      });
+    });
+
+    describe("#close", function() {
+      beforeEach(function() {
+        this.view.open($.Event());
+      });
+
+      it("removes the 'active' class from the publisher element", function(){
+        this.view.close($.Event());
+        expect($(this.view.el)).toHaveClass("closed");
+      })
+
+      it("resets the element's height", function() {
+        $(this.view.el).find("#status_message_fake_text").height(100);
+        this.view.close($.Event());
+        expect($(this.view.el).find("#status_message_fake_text").attr("style")).not.toContain("height");
+      });
+    });
+
+    describe("#clear", function() {
+      it("calls close", function(){
+        spyOn(this.view, "close");
+
+        this.view.clear($.Event());
+        expect(this.view.close).toHaveBeenCalled();
+      })
+
+      it("clears all textareas", function(){
+        _.each(this.view.$("textarea"), function(element){
+          $(element).val('this is some stuff');
+          expect($(element).val()).not.toBe("");
+        });
+
+        this.view.clear($.Event());
+
+        _.each(this.view.$("textarea"), function(element){
+          expect($(element).val()).toBe("");
+        });
+      })
+
+      it("removes all photos from the dropzone area", function(){
+        var self = this;
+        _.times(3, function(){
+          self.view.el_photozone.append($("<li>"))
+        });
+
+        expect(this.view.el_photozone.html()).not.toBe("");
+        this.view.clear($.Event());
+        expect(this.view.el_photozone.html()).toBe("");
+      })
+
+      it("removes all photo values appended by the photo uploader", function(){
+        $(this.view.el).prepend("<input name='photos[]' value='3'/>")
+        var photoValuesInput = this.view.$("input[name='photos[]']");
+
+        photoValuesInput.val("3")
+        this.view.clear($.Event());
+        expect(this.view.$("input[name='photos[]']").length).toBe(0);
+      })
+    });
+  });
+
+  context("#toggleService", function(){
+    beforeEach( function(){
+      spec.loadFixture('aspects_index_services');
+      this.view = new app.views.Publisher();
+    });
+
+    it("toggles the 'dim' class on a clicked item", function() {
+      var first = $(".service_icon").eq(0);
+      var second = $(".service_icon").eq(1);
+
+      expect(first.hasClass('dim')).toBeTruthy();
+      expect(second.hasClass('dim')).toBeTruthy();
+
+      first.trigger('click');
+
+      expect(first.hasClass('dim')).toBeFalsy();
+      expect(second.hasClass('dim')).toBeTruthy();
+
+      first.trigger('click');
+
+      expect(first.hasClass('dim')).toBeTruthy();
+      expect(second.hasClass('dim')).toBeTruthy();
+    });
+
+    describe("#_createCounter", function() {
+      it("gets called in when you toggle service icons", function(){
+        spyOn(this.view, '_createCounter');
+        $(".service_icon").first().trigger('click');
+        expect(this.view._createCounter).toHaveBeenCalled();
+      });
+
+      it("removes the 'old' .counter span", function(){
+        spyOn($.fn, "remove");
+        $(".service_icon").first().trigger('click');
+        expect($.fn.remove).toHaveBeenCalled();
+      });
     });
 
-    it("removes the 'active' class from the publisher element", function(){
-      this.view.close($.Event());
-      expect($(this.view.el)).toHaveClass("closed");
-    })
+    describe("#_toggleServiceField", function() {
+      it("gets called when you toggle service icons", function(){
+        spyOn(this.view, '_toggleServiceField');
+        $(".service_icon").first().trigger('click');
+        expect(this.view._toggleServiceField).toHaveBeenCalled();
+      });
+
+      it("toggles the hidden input field", function(){
+        expect($('input[name="services[]"]').length).toBe(0);
+        $(".service_icon").first().trigger('click');
+        expect($('input[name="services[]"]').length).toBe(1);
+        $(".service_icon").first().trigger('click');
+        expect($('input[name="services[]"]').length).toBe(0);
+      });
+
+      it("toggles the correct input", function() {
+        var first = $(".service_icon").eq(0);
+        var second = $(".service_icon").eq(1);
+
+        first.trigger('click');
+        second.trigger('click');
+
+        expect($('input[name="services[]"]').length).toBe(2);
+
+        first.trigger('click');
 
-    it("resets the element's height", function() {
-      $(this.view.el).find("#status_message_fake_text").height(100);
-      this.view.close($.Event());
-      expect($(this.view.el).find("#status_message_fake_text").attr("style")).not.toContain("height");
+        var prov1 = first.attr('id');
+        var prov2 = second.attr('id');
+
+        expect($('input[name="services[]"][value="'+prov1+'"]').length).toBe(0);
+        expect($('input[name="services[]"][value="'+prov2+'"]').length).toBe(1);
+      });
     });
   });
 
-  describe("#clear", function() {
-    it("calls close", function(){
-      spyOn(this.view, "close");
+  context("aspect selection", function(){
+    beforeEach( function(){
+      spec.loadFixture('status_message_new');
+
+      this.radio_els = $('#publisher .dropdown li.radio');
+      this.check_els = $('#publisher .dropdown li.aspect_selector');
+
+      this.view = new app.views.Publisher();
+      this.view.open();
+    });
 
-      this.view.clear($.Event());
-      expect(this.view.close);
-    })
+    it("initializes with 'all_aspects'", function(){
+      expect(this.radio_els.first().hasClass('selected')).toBeFalsy();
+      expect(this.radio_els.last().hasClass('selected')).toBeTruthy();
 
-    it("clears all textareas", function(){
-      _.each(this.view.$("textarea"), function(element){
-        $(element).val('this is some stuff');
-        expect($(element).val()).not.toBe("");
+      _.each(this.check_els, function(el){
+        expect($(el).hasClass('selected')).toBeFalsy();
       });
+    });
 
-      this.view.clear($.Event());
+    it("toggles the selected entry visually", function(){
+      this.check_els.last().trigger('click');
 
-      _.each(this.view.$("textarea"), function(element){
-        expect($(element).val()).toBe("");
+      _.each(this.radio_els, function(el){
+        expect($(el).hasClass('selected')).toBeFalsy();
       });
-    })
 
-    it("removes all photos from the dropzone area", function(){
-      var self = this;
-      _.times(3, function(){
-        self.view.$("#photodropzone").append($("<li>"))
+      expect(this.check_els.first().hasClass('selected')).toBeFalsy();
+      expect(this.check_els.last().hasClass('selected')).toBeTruthy();
+    });
+
+    describe("#_updateSelectedAspectIds", function(){
+      beforeEach(function(){
+        this.li = $('<li data-aspect_id="42" />');
+        this.view.$('.dropdown_list').append(this.li);
       });
 
-      expect(this.view.$("#photodropzone").html()).not.toBe("");
-      this.view.clear($.Event());
-      expect(this.view.$("#photodropzone").html()).toBe("");
-    })
+      it("gets called when aspects are selected", function(){
+        spyOn(this.view, "_updateSelectedAspectIds");
+        this.check_els.last().trigger('click');
+        expect(this.view._updateSelectedAspectIds).toHaveBeenCalled();
+      });
 
-    it("removes all photo values appended by the photo uploader", function(){
-      $(this.view.el).prepend("<input name='photos[]' value='3'/>")
-      var photoValuesInput = this.view.$("input[name='photos[]']");
+      it("removes a previous selection and inserts the current one", function() {
+        var selected = this.view.$('input[name="aspect_ids[]"]');
+        expect(selected.length).toBe(1);
+        expect(selected.first().val()).toBe('all_aspects');
 
-      photoValuesInput.val("3")
-      this.view.clear($.Event());
-      expect(this.view.$("input[name='photos[]']").length).toBe(0);
-    })
+        this.li.trigger('click');
+
+        selected = this.view.$('input[name="aspect_ids[]"]');
+        expect(selected.length).toBe(1);
+        expect(selected.first().val()).toBe('42');
+      });
+
+      it("toggles the same item", function() {
+        expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1);
+
+        this.li.trigger('click');
+        expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1);
+
+        this.li.trigger('click');
+        expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(0);
+      });
+
+      it("keeps other fields with different values", function() {
+        var li2 = $("<li data-aspect_id=99></li>");
+        this.view.$('.dropdown_list').append(li2);
+
+        this.li.trigger('click');
+        expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(1);
+
+        li2.trigger('click');
+        expect(this.view.$('input[name="aspect_ids[]"]').length).toBe(2);
+      });
+    });
+
+    describe("#_addHiddenAspectInput", function(){
+      it("gets called when aspects are selected", function(){
+        spyOn(this.view, "_addHiddenAspectInput");
+        this.check_els.last().trigger('click');
+        expect(this.view._addHiddenAspectInput).toHaveBeenCalled();
+      });
+
+      it("adds a hidden input to the form", function(){
+        var id = 42;
+
+        this.view._addHiddenAspectInput(id);
+        var input = this.view.$('input[name="aspect_ids[]"][value="'+id+'"]');
+
+        expect(input.length).toBe(1);
+        expect(input.val()).toBe('42');
+      });
+    });
   });
 });
diff --git a/spec/javascripts/bookmarklet-spec.js b/spec/javascripts/bookmarklet-spec.js
index c776b9e1ab75aa0becec61924b6f21fed6d86b4b..fee57d0e7480426b66d8a6886305020f3e1d35b6 100644
--- a/spec/javascripts/bookmarklet-spec.js
+++ b/spec/javascripts/bookmarklet-spec.js
@@ -11,11 +11,12 @@ describe("bookmarklet", function() {
     });
 
     it('verifies the publisher is loaded', function(){
-      expect(typeof Publisher === "object").toBeTruthy();
+      expect(typeof app.publisher === "object").toBeTruthy();
     });
 
     it('verifies we are using the bookmarklet', function(){
-      expect(Publisher.bookmarklet).toBeTruthy();
+      expect(app.publisher.options.standalone).toBeTruthy();
+      expect(app.publisher.$('#hide_publisher').is(':visible')).toBeFalsy();
     });
   });
 
diff --git a/spec/javascripts/publisher-spec.js b/spec/javascripts/publisher-spec.js
deleted file mode 100644
index af84eeb39692a4427873003d87c8eb2a3200610d..0000000000000000000000000000000000000000
--- a/spec/javascripts/publisher-spec.js
+++ /dev/null
@@ -1,177 +0,0 @@
-/*   Copyright (c) 2010-2011, Diaspora Inc.  This file is
-*   licensed under the Affero General Public License version 3 or later.  See
-*   the COPYRIGHT file.
-*/
-
-describe("Publisher", function() {
-
-  Publisher.open = function(){ this.form().removeClass("closed"); }
-
-  describe("toggleCounter", function(){
-    beforeEach( function(){
-      spec.loadFixture('aspects_index_services');
-    });
-
-    it("gets called in when you toggle service icons", function(){
-      spyOn(Publisher, 'createCounter');
-      Publisher.toggleServiceField($(".service_icon").first());
-      expect(Publisher.createCounter).toHaveBeenCalled();
-    });
-
-    it("removes the .counter span", function(){
-      spyOn($.fn, "remove");
-      Publisher.createCounter($(".service_icon").first());
-      expect($.fn.remove).toHaveBeenCalled();
-    });
-  });
-
-  describe("bindAspectToggles", function() {
-    beforeEach( function(){
-      spec.loadFixture('status_message_new');
-      Publisher.open();
-    });
-
-    it('gets called on initialize', function(){
-      spyOn(Publisher, 'bindAspectToggles');
-      Publisher.initialize();
-      expect(Publisher.bindAspectToggles).toHaveBeenCalled();
-    });
-
-    it('correctly initializes an all_aspects state', function(){
-      Publisher.initialize();
-
-      expect($("#publisher .dropdown .dropdown_list li.radio").first().hasClass("selected")).toBeFalsy();
-      expect($("#publisher .dropdown .dropdown_list li.radio").last().hasClass("selected")).toBeTruthy();
-
-      $.each($("#publihser .dropdown .dropdown_list li.aspect_selector"), function(index, element){
-        expect($(element).hasClass("selected")).toBeFalsy();
-      });
-    });
-
-    it('toggles selected only on the clicked icon', function(){
-      Publisher.initialize();
-
-      $("#publisher .dropdown .dropdown_list li.aspect_selector").last().click();
-
-      $.each($("#publisher .dropdown .dropdown_list li.radio"), function(index, element){
-        expect($(element).hasClass("selected")).toBeFalsy();
-      });
-
-      expect($("#publisher .dropdown .dropdown_list li.aspect_selector").first().hasClass("selected")).toBeFalsy();
-      expect($("#publisher .dropdown .dropdown_list li.aspect_selector").last().hasClass("selected")).toBeTruthy();
-    });
-
-    it('calls toggleAspectIds with the clicked element', function(){
-      spyOn(Publisher, 'toggleAspectIds');
-      Publisher.bindAspectToggles();
-      var aspectBadge = $("#publisher .dropdown .dropdown_list li").last();
-      aspectBadge.click();
-      expect(Publisher.toggleAspectIds.mostRecentCall.args[0].get(0)).toEqual(aspectBadge.get(0));
-    });
-  });
-
-  describe('toggleAspectIds', function(){
-    beforeEach( function(){
-      spec.loadFixture('status_message_new');
-      li = $("<li data-aspect_id=42></li>");
-    });
-
-    it('adds a hidden field to the form if there is not one already', function(){
-      expect($('#publisher [name="aspect_ids[]"]').length).toBe(1);
-      expect($('#publisher [name="aspect_ids[]"]').last().attr('value')).toBe('all_aspects');
-      Publisher.toggleAspectIds(li);
-      expect($('#publisher [name="aspect_ids[]"]').length).toBe(1);
-      expect($('#publisher [name="aspect_ids[]"]').last().attr('value')).toBe('42');
-    });
-
-    it('removes the hidden field if its already there', function() {
-      expect($('#publisher [name="aspect_ids[]"]').length).toBe(1);
-
-      Publisher.toggleAspectIds(li);
-      expect($('#publisher [name="aspect_ids[]"]').length).toBe(1);
-
-      Publisher.toggleAspectIds(li);
-      expect($('#publisher [name="aspect_ids[]"]').length).toBe(0);
-    });
-
-    it('does not remove a hidden field with a different value', function() {
-      var li2 = $("<li data-aspect_id=99></li>");
-
-      Publisher.toggleAspectIds(li);
-      expect($('#publisher [name="aspect_ids[]"]').length).toBe(1);
-
-      Publisher.toggleAspectIds(li2);
-      expect($('#publisher [name="aspect_ids[]"]').length).toBe(2);
-    });
-  });
-
-  describe("bindServiceIcons", function() {
-    beforeEach( function(){
-      spec.loadFixture('aspects_index_services');
-    });
-
-    it('gets called on initialize', function(){
-      spyOn(Publisher, 'bindServiceIcons');
-      Publisher.initialize();
-      expect(Publisher.bindServiceIcons).toHaveBeenCalled();
-    });
-
-    it('toggles dim only on the clicked icon', function(){
-      expect($(".service_icon#facebook").hasClass("dim")).toBeTruthy();
-      expect($(".service_icon#twitter").hasClass("dim")).toBeTruthy();
-
-      Publisher.bindServiceIcons();
-      $(".service_icon#facebook").click();
-
-      expect($(".service_icon#facebook").hasClass("dim")).toBeFalsy();
-      expect($(".service_icon#twitter").hasClass("dim")).toBeTruthy();
-    });
-
-    it('binds to the services icons and toggles the hidden field', function(){
-      spyOn(Publisher, 'toggleServiceField');
-      Publisher.bindServiceIcons();
-      $(".service_icon#facebook").click();
-
-      expect(Publisher.toggleServiceField).toHaveBeenCalled();
-    });
-  });
-
-  describe('toggleServiceField', function(){
-    beforeEach( function(){
-      spec.loadFixture('aspects_index_services');
-    });
-
-    it('adds a hidden field to the form if there is not one already', function(){
-      expect($('#publisher [name="services[]"]').length).toBe(0);
-      Publisher.toggleServiceField($(".service_icon#facebook").first());
-      expect($('#publisher [name="services[]"]').length).toBe(1);
-      expect($('#publisher [name="services[]"]').attr('value')).toBe("facebook");
-    });
-
-    it('removes the hidden field if its already there', function() {
-      Publisher.toggleServiceField($(".service_icon#facebook").first());
-      expect($('#publisher [name="services[]"]').length).toBe(1);
-
-      Publisher.toggleServiceField($(".service_icon#facebook").first());
-      expect($('#publisher [name="services[]"]').length).toBe(0);
-    });
-
-    it('does not remove a hidden field with a different value', function() {
-      Publisher.toggleServiceField($(".service_icon#facebook").first());
-      expect($('#publisher [name="services[]"]').length).toBe(1);
-
-      Publisher.toggleServiceField($(".service_icon#twitter").first());
-      expect($('#publisher [name="services[]"]').length).toBe(2);
-    });
-  });
-
-  describe("input", function(){
-    beforeEach(function(){
-      spec.loadFixture('aspects_index_prefill');
-    });
-    it("returns the status_message_fake_text textarea", function(){
-      expect(Publisher.input()[0].id).toBe('status_message_fake_text');
-      expect(Publisher.input().length).toBe(1);
-    });
-  });
-});