diff --git a/css/ctools.css b/css/ctools.css
index 5cf9c5b4b2d8d9be05e7faadc7ae7ca944895611..4a2d875d53620c10c95ac663f41590b8b903af91 100644
--- a/css/ctools.css
+++ b/css/ctools.css
@@ -10,3 +10,13 @@
   border: 1px solid #F0C020;
   padding: 1em;
 }
+
+a.ctools-ajaxing {
+  padding-right: 18px !important;
+  background: url(../images/status-active.gif) right center no-repeat;
+}
+
+input.ctools-ajaxing {
+  padding-right: 18px;
+  background: url(../images/status-active.gif) right center no-repeat;
+}
diff --git a/css/modal.css b/css/modal.css
index 40a752589ac40a241540576d26830748fdb225ee..61cabacbc46bce86f6988f40ef8828c794112d0d 100644
--- a/css/modal.css
+++ b/css/modal.css
@@ -28,7 +28,7 @@ div.ctools-modal-content .modal-header a {
 }
 
 div.ctools-modal-content .modal-content {
-  padding: 0 1em;
+  padding: 1em 1em 0 1em;
   overflow: auto;
   width: 575px;
   height: 400px;
diff --git a/ctools.module b/ctools.module
index fa41de94416ee8f8093c242d0f6ccd88bb8ece1f..cc5b13d14e0a7859da19e4c1c8c5ff32a6301c73 100644
--- a/ctools.module
+++ b/ctools.module
@@ -43,6 +43,13 @@ function ctools_add_js($file) {
   drupal_add_js(drupal_get_path('module', 'ctools') . "/js/$file.js");
 }
 
+/**
+ * Implement hook_init to keep our global CSS at the ready.
+ */
+function ctools_init() {
+  ctools_add_css('ctools');
+}
+
 /**
  * Provide a hook passthrough to included files.
  *
diff --git a/images/no-icon.png b/images/no-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa78ec179a83428e5b8e247890278cdf91a8cb3e
Binary files /dev/null and b/images/no-icon.png differ
diff --git a/images/status-active.gif b/images/status-active.gif
new file mode 100644
index 0000000000000000000000000000000000000000..207e95c3fa8cc31a89af150ad74059b1667922b6
Binary files /dev/null and b/images/status-active.gif differ
diff --git a/includes/content.inc b/includes/content.inc
index a7a2a857ebf3b3365966d9ea3f69578cc78198a4..be810d71d338496dee70e7ee8500e644fc2cc78c 100644
--- a/includes/content.inc
+++ b/includes/content.inc
@@ -75,6 +75,9 @@ function ctools_content_defaults($info, &$plugin) {
       if (isset($plugin['required contexts'])) {
         $type['required contexts'] = $plugin['required contexts'];
       }
+      if (isset($plugin['top level'])) {
+        $type['top level'] = $plugin['top level'];
+      }
       $plugin['content types'] = array($plugin['name'] => $type);
     }
     // Otherwise, auto discover the function based upon pattern naming.
@@ -503,7 +506,7 @@ function _ctools_content_create_add_form_info(&$form_info, $info, $plugin, $subt
  * @param $default_types
  *   A default allowed/denied status for content that isn't known about
  */
-function ctools_get_available_content_types($contexts = NULL, $has_content = FALSE, $allowed_types = NULL, $default_types = NULL) {
+function ctools_content_get_available_types($contexts = NULL, $has_content = FALSE, $allowed_types = NULL, $default_types = NULL) {
   $plugins = ctools_get_content_types();
   $available = array();
 
@@ -546,7 +549,7 @@ function ctools_get_available_content_types($contexts = NULL, $has_content = FAL
  * availability.
  *
  */
-function ctools_get_all_content_types() {
+function ctools_content_get_all_types() {
   $plugins = ctools_get_content_types();
   $available = array();
 
diff --git a/js/ajax-responder.js b/js/ajax-responder.js
index 4a09865b7125608aac50b3fe6467e8bb611a3898..a1db193e3aaa4af5b5096651476f52a7326e9cd2 100644
--- a/js/ajax-responder.js
+++ b/js/ajax-responder.js
@@ -29,31 +29,14 @@ Drupal.CTools.AJAX.respond = function(data) {
  * specified by the href of the link.
  */
 Drupal.CTools.AJAX.clickAJAXLink = function() {
-  var url = $(this).attr('href');
-  url.replace('/nojs/', '/ajax/');
-  $.ajax({
-    type: "POST",
-    url: url,
-    data: '',
-    global: true,
-    success: Drupal.CTools.AJAX.respond,
-    error: function() { 
-      alert("An error occurred while attempting to process " + url); 
-    },
-    dataType: 'json'
-  });
-  return false;
-};
+  if ($(this).hasClass('ctools-ajaxing')) {
+    return false;
+  }
 
-/**
- * Generic replacement click handler to open the modal with the destination
- * specified by the href of the link.
- */
-Drupal.CTools.AJAX.clickAJAXButton = function() {
-  // @todo -- if no url we should take the form action and submit the
-  // form.
-  var url = Drupal.CTools.AJAX.findURL(this);
-  if (url) {
+  var url = $(this).attr('href');
+  var object = $(this);
+  $(this).addClass('ctools-ajaxing');
+  try {
     url.replace('/nojs/', '/ajax/');
     $.ajax({
       type: "POST",
@@ -64,25 +47,76 @@ Drupal.CTools.AJAX.clickAJAXButton = function() {
       error: function() { 
         alert("An error occurred while attempting to process " + url); 
       },
-      dataType: 'json'
-    });
-  }
-  else {
-    var form = $(this).parents('form');
-    url = $(form).attr('action');
-    url.replace('/nojs/', '/ajax/');
-    $(form).ajaxSubmit({
-      type: "POST",
-      url: url,
-      data: '',
-      global: true,
-      success: Drupal.CTools.AJAX.respond,
-      error: function() { 
-        alert("An error occurred while attempting to process " + url); 
+      complete: function() {
+        object.removeClass('ctools-ajaxing');
       },
       dataType: 'json'
     });
   }
+  catch (err) {
+    alert("An error occurred while attempting to process " + url); 
+    $(this).removeClass('ctools-ajaxing');
+    return false;
+  }
+
+  return false;
+};
+
+/**
+ * Generic replacement click handler to open the modal with the destination
+ * specified by the href of the link.
+ */
+Drupal.CTools.AJAX.clickAJAXButton = function() {
+  if ($(this).hasClass('ctools-ajaxing')) {
+    return false;
+  }
+
+  var url = Drupal.CTools.AJAX.findURL(this);
+  $(this).addClass('ctools-ajaxing');
+  var object = $(this);
+  try {
+    if (url) {
+      url.replace('/nojs/', '/ajax/');
+      $.ajax({
+        type: "POST",
+        url: url,
+        data: '',
+        global: true,
+        success: Drupal.CTools.AJAX.respond,
+        error: function() { 
+          alert("An error occurred while attempting to process " + url); 
+        },
+        complete: function() {
+          object.removeClass('ctools-ajaxing');
+        },
+        dataType: 'json'
+      });
+    }
+    else {
+      var form = $(this).parents('form');
+      url = $(form).attr('action');
+      url.replace('/nojs/', '/ajax/');
+      $(form).ajaxSubmit({
+        type: "POST",
+        url: url,
+        data: '',
+        global: true,
+        success: Drupal.CTools.AJAX.respond,
+        error: function() { 
+          alert("An error occurred while attempting to process " + url); 
+        },
+        complete: function() {
+          object.removeClass('ctools-ajaxing');
+        },
+        dataType: 'json'
+      });
+    }
+  }
+  catch (err) {
+    alert("An error occurred while attempting to process " + url); 
+    $(this).removeClass('ctools-ajaxing');
+    return false;
+  }
   return false;
 };
 
diff --git a/js/modal.js b/js/modal.js
index 5f7179e1f20da54de18a506e290904d586f355d7..195156e493b3099b0521b4038a096b917c2d29c1 100644
--- a/js/modal.js
+++ b/js/modal.js
@@ -27,7 +27,7 @@ Drupal.CTools.Modal.show = function() {
     });
     $('div.ctools-modal-content .modal-content', context).css({
       'width': ($(window).width() * .8 - 25) + 'px', 
-      'height': ($(window).height() * .8 - 22) + 'px'
+      'height': ($(window).height() * .8 - 35) + 'px'
     });
   }
 
@@ -97,7 +97,12 @@ Drupal.theme.prototype.CToolsModalThrobber = function () {
 Drupal.CTools.Modal.clickAjaxLink = function() {
   // show the empty dialog right away.
   Drupal.CTools.Modal.show();
-  return Drupal.CTools.AJAX.clickAJAXLink.apply(this);
+  Drupal.CTools.AJAX.clickAJAXLink.apply(this);
+  if (!$(this).hasClass('ctools-ajaxing')) {
+    Drupal.CTools.Modal.dismiss();
+  }
+
+  return false;
 };
 
 /**
@@ -105,27 +110,52 @@ Drupal.CTools.Modal.clickAjaxLink = function() {
  * specified by the href of the link.
  */
 Drupal.CTools.Modal.clickAjaxButton = function() {
+  if ($(this).hasClass('ctools-ajaxing')) {
+    return false;
+  }
+
   Drupal.CTools.Modal.show();
-  return Drupal.CTools.AJAX.clickAJAXButton.apply(this);
+  Drupal.CTools.AJAX.clickAJAXButton.apply(this);
+  if (!$(this).hasClass('ctools-ajaxing')) {
+    Drupal.CTools.Modal.dismiss();
+  }
+
+  return false;
 };
 
 /**
  * Submit responder to do an AJAX submit on all modal forms.
  */
 Drupal.CTools.Modal.submitAjaxForm = function() {
+  if ($(this).hasClass('ctools-ajaxing')) {
+    return false;
+  }
+
   url = $(this).attr('action');
-  url.replace('/nojs/', '/ajax/');
-  $(this).ajaxSubmit({
-    type: "POST",
-    url: url,
-    data: '',
-    global: true,
-    success: Drupal.CTools.AJAX.respond,
-    error: function() { 
-      alert("An error occurred while attempting to process " + url); 
-    },
-    dataType: 'json'
-  });
+  $(this).addClass('ctools-ajaxing');
+  var object = $(this);
+  try {
+    url.replace('/nojs/', '/ajax/');
+    $(this).ajaxSubmit({
+      type: "POST",
+      url: url,
+      data: '',
+      global: true,
+      success: Drupal.CTools.AJAX.respond,
+      error: function() { 
+        alert("An error occurred while attempting to process " + url); 
+      },
+      complete: function() {
+        object.removeClass('ctools-ajaxing');
+      },
+      dataType: 'json'
+    });
+  }
+  catch (err) {
+    alert("An error occurred while attempting to process " + url); 
+    $(this).removeClass('ctools-ajaxing');
+    return false;
+  }
   return false;
 }
 
diff --git a/plugins/content_types/block/block.inc b/plugins/content_types/block/block.inc
index be244625d665653772befda56d27e07e6b59aef5..595fe957c5749ee34a66e79fa8ba66e611dda9bd 100644
--- a/plugins/content_types/block/block.inc
+++ b/plugins/content_types/block/block.inc
@@ -276,17 +276,13 @@ function forum_ctools_block_info($module, $delta, &$info) {
 function profile_ctools_block_info($module, $delta, &$info) {
   // Hide the author information block which isn't as rich as what we can
   // do with context.
-  return NULL;
-  $info['icon'] = 'icon_core_authorinformation.png';
-  $info['category'] = t('Core blocks');
+  $info = NULL;
 }
 
 function book_ctools_block_info($module, $delta, &$info) {
   // Hide the book navigation block which isn't as rich as what we can
   // do with context.
-  return NULL;
-  $info['icon'] = 'icon_core_booknavigation.png';
-  $info['category'] = t('Core blocks');
+  $info = NULL;
 }
 
 function blog_ctools_block_info($module, $delta, &$info) {
diff --git a/plugins/content_types/custom/custom.inc b/plugins/content_types/custom/custom.inc
index 26eb6bafc19ee785cdcdfa4b89ee4654bf470ea1..6fe17eed9948a1d93a2f233cdaf8c59d6da88cb2 100644
--- a/plugins/content_types/custom/custom.inc
+++ b/plugins/content_types/custom/custom.inc
@@ -20,6 +20,9 @@ function ctools_custom_ctools_content_types() {
     'title' => t('New custom content'),
     'icon' => 'icon_block_custom.png',
     'description' => t('Create a completely custom piece of HTML content.'),
+    // Make this a top level category so it appears higher in UIs that support
+    // that.
+    'top level' => TRUE,
     'category' => t('Custom'),
     'defaults' => array('title' => '', 'body' => '', 'format' => FILTER_FORMAT_DEFAULT),
     // render callback is automatically deduced: