Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.171
diff -u -F^f -r1.171 form.inc
--- includes/form.inc	5 Jan 2007 19:08:30 -0000	1.171
+++ includes/form.inc	6 Jan 2007 00:45:55 -0000
@@ -1410,6 +1410,17 @@ function theme_form($element) {
  */
 function theme_textarea($element) {
   $class = array('form-textarea');
+
+  // Add teaser behaviour (must come before resizable)
+  if ($element['#teaser'] && variable_get('teaser_button', 1)) {
+    drupal_add_js('misc/teaser.js');
+    // Note: arrays are merged in drupal_get_js().
+    drupal_add_js(array('teaserButton' => array(t('Join teaser'), t('Split at cursor'))), 'setting');
+    drupal_add_js(array('teaser' => array($element['#id'] => $element['#teaser'])), 'setting');
+    $class[] = 'teaser';
+  }
+
+  // Add resizable behaviour
   if ($element['#resizable'] !== FALSE) {
     drupal_add_js('misc/textarea.js');
     $class[] = 'resizable';
Index: misc/drupal.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.js,v
retrieving revision 1.29
diff -u -F^f -r1.29 drupal.js
--- misc/drupal.js	14 Oct 2006 02:39:48 -0000	1.29
+++ misc/drupal.js	6 Jan 2007 00:45:55 -0000
@@ -200,6 +200,26 @@
   return uri.indexOf('?q=') ? item : item.replace('%26', '%2526').replace('%23', '%2523');
 };
 
+/**
+ * Get the text selection in a textarea.
+ */
+Drupal.getSelection = function (element) {
+  if (document.selection) {
+    // The current selection
+    var range1 = document.selection.createRange();
+    var range2 = range1.duplicate();
+    // Select all text.
+    range2.moveToElementText(element);
+    // Now move 'dummy' end point to end point of original range.
+    range2.setEndPoint('EndToEnd', range1);
+    // Now we can calculate start and end points.
+    var start = range2.text.length - range1.text.length;
+    var end = start + range1.text.length;
+    return { 'start': start, 'end': end };
+  }
+  return { 'start': element.selectionStart, 'end': element.selectionEnd };
+}
+
 // Global Killswitch on the <html> element
 if (Drupal.jsEnabled) {
   document.documentElement.className = 'js';
Index: misc/textarea.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/textarea.js,v
retrieving revision 1.11
diff -u -F^f -r1.11 textarea.js
--- misc/textarea.js	7 Sep 2006 08:05:31 -0000	1.11
+++ misc/textarea.js	6 Jan 2007 00:45:55 -0000
@@ -7,6 +7,12 @@
     $(this).wrap('<div class="resizable-textarea"></div>')
       .parent().append($('<div class="grippie"></div>').mousedown(startDrag));
 
+    // Inherit visibility
+    if ($(this).is(':hidden')) {
+      $(this).parent().hide();
+      $(this).show();
+    }
+
     var grippie = $('div.grippie', $(this).parent())[0];
     grippie.style.marginRight = (grippie.offsetWidth - $(this)[0].offsetWidth) +'px';
 
Index: modules/block/block.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/block/block.module,v
retrieving revision 1.245
diff -u -F^f -r1.245 block.module
--- modules/block/block.module	18 Dec 2006 21:49:39 -0000	1.245
+++ modules/block/block.module	6 Jan 2007 00:45:55 -0000
@@ -551,8 +551,8 @@ function block_box_form($edit = array())
     '#required' => TRUE,
     '#weight' => -19,
   );
-  $form['body_filter']['#weight'] = -17;
-  $form['body_filter']['body'] = array(
+  $form['body_field']['#weight'] = -17;
+  $form['body_field']['body'] = array(
     '#type' => 'textarea',
     '#title' => t('Block body'),
     '#default_value' => $edit['body'],
@@ -563,7 +563,7 @@ function block_box_form($edit = array())
   if (!isset($edit['format'])) {
     $edit['format'] = FILTER_FORMAT_DEFAULT;
   }
-  $form['body_filter']['format'] = filter_form($edit['format'], -16);
+  $form['body_field']['format'] = filter_form($edit['format'], -16);
   $form['submit'] = array('#type' => 'submit', '#value' => t('Save block'));
 
   return $form;
Index: modules/blog/blog.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/blog/blog.module,v
retrieving revision 1.271
diff -u -F^f -r1.271 blog.module
--- modules/blog/blog.module	10 Dec 2006 20:34:02 -0000	1.271
+++ modules/blog/blog.module	6 Jan 2007 00:45:56 -0000
@@ -208,8 +208,20 @@ function blog_form(&$node) {
   }
 
   $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => $node->title, '#weight' => -5);
-  $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE);
-  $form['body_filter']['filter'] = filter_form($node->format);
+
+  $form['body_field'] = array(
+    '#after_build' => array('node_teaser_js'));
+
+  $form['body_field']['teaser_js'] = array(
+    '#type' => 'textarea',
+    '#rows' => 10,
+    '#teaser' => 'edit-body',
+    '#disabled' => TRUE);
+
+  $form['body_field']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE);
+
+  $form['body_field']['filter'] = filter_form($node->format);
+
   return $form;
 }
 
Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.404
diff -u -F^f -r1.404 book.module
--- modules/book/book.module	29 Dec 2006 07:41:44 -0000	1.404
+++ modules/book/book.module	6 Jan 2007 00:45:56 -0000
@@ -223,13 +223,23 @@ function book_form(&$node) {
     '#default_value' => $node->title,
     '#weight' => -5,
   );
-  $form['body_filter']['body'] = array('#type' => 'textarea',
+
+  $form['body_field'] = array(
+    '#after_build' => array('node_teaser_js'));
+
+  $form['body_field']['teaser_js'] = array(
+    '#type' => 'textarea',
+    '#rows' => 10,
+    '#teaser' => 'edit-body',
+    '#disabled' => TRUE);
+
+  $form['body_field']['body'] = array('#type' => 'textarea',
     '#title' => check_plain($type->body_label),
     '#default_value' => $node->body,
     '#rows' => 20,
     '#required' => TRUE,
   );
-  $form['body_filter']['format'] = filter_form($node->format);
+  $form['body_field']['format'] = filter_form($node->format);
 
   if (user_access('administer nodes')) {
     $form['weight'] = array('#type' => 'weight',
Index: modules/forum/forum.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.module,v
retrieving revision 1.374
diff -u -F^f -r1.374 forum.module
--- modules/forum/forum.module	5 Jan 2007 05:32:23 -0000	1.374
+++ modules/forum/forum.module	6 Jan 2007 00:45:57 -0000
@@ -398,8 +398,18 @@ function forum_form(&$node) {
     $form['shadow'] = array('#type' => 'checkbox', '#title' => t('Leave shadow copy'), '#default_value' => $shadow, '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.'));
   }
 
-  $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE);
-  $form['body_filter']['format'] = filter_form($node->format);
+  $form['body_field'] = array(
+    '#after_build' => array('node_teaser_js'));
+
+  $form['body_field']['teaser_js'] = array(
+    '#type' => 'textarea',
+    '#rows' => 10,
+    '#teaser' => 'edit-body',
+    '#disabled' => TRUE);
+
+  $form['body_field']['body'] = array('#type' => 'textarea', '#title' => check_plain($type->body_label), '#default_value' => $node->body, '#rows' => 20, '#required' => TRUE);
+
+  $form['body_field']['format'] = filter_form($node->format);
 
   return $form;
 }
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.767
diff -u -F^f -r1.767 node.module
--- modules/node/node.module	5 Jan 2007 22:03:06 -0000	1.767
+++ modules/node/node.module	6 Jan 2007 00:45:59 -0000
@@ -139,6 +139,28 @@ function node_mark($nid, $timestamp) {
 }
 
 /**
+ * See if the user used JS to submit a teaser.
+ */
+function node_teaser_js(&$form, $form_values) {
+  // Glue the teaser to the body.
+  if (isset($form['#post']['teaser_js'])) {
+    if (trim($form_values['teaser_js'])) {
+      // Space the teaser from the body
+      $body = trim($form_values['teaser_js']) ."\r\n<!--break-->\r\n". trim($form_values['body']);
+    }
+    else {
+      // Empty teaser, no spaces.
+      $body = '<!--break-->'. $form_values['body'];
+    }
+    // Pass value onto preview/submit
+    form_set_value($form['body'], $body);
+    // Pass value back onto form
+    $form['body']['#value'] = $body;
+  }
+  return $form;
+}
+
+/**
  * Automatically generate a teaser for a node body in a given format.
  */
 function node_teaser($body, $format = NULL) {
@@ -995,6 +1017,11 @@ function node_configure() {
     '#options' => array(t('Optional'), t('Required')), '#description' => t('Must users preview posts before submitting?')
   );
 
+  $form['teaser_button'] = array(
+    '#type' => 'radios', '#title' => t('Teaser split button'), '#default_value' => variable_get('teaser_button', 1),
+    '#options' => array(t('Disabled'), t('Enabled')), '#description' => t('Display a teaser split button for node text areas.')
+  );
+
   return system_settings_form($form);
 }
 
@@ -2199,7 +2226,7 @@ function node_preview($node) {
 function theme_node_preview($node) {
   $output = '<div class="preview">';
   if ($node->teaser && $node->teaser != $node->body) {
-    drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication. You can insert the delimiter "&lt;!--break--&gt;" (without the quotes) to fine-tune where your post gets split.'));
+    drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication.<span class="no-js"> You can insert the delimiter "&lt;!--break--&gt;" (without the quotes) to fine-tune where your post gets split.</span>'));
     $output .= '<h3>'. t('Preview trimmed version') .'</h3>';
     $output .= node_view(drupal_clone($node), 1, FALSE, 0);
     $output .= '<h3>'. t('Preview full version') .'</h3>';
@@ -2931,13 +2958,25 @@ function node_content_form($node) {
   }
 
   if ($type->has_body) {
-    $form['body_filter']['body'] = array(
+    $form['body_field'] = array(
+      '#after_build' => array('node_teaser_js'));
+
+    if (variable_get('teaser_button', 1)) {
+      $form['body_field']['teaser_js'] = array(
+        '#type' => 'textarea',
+        '#rows' => 10,
+        '#teaser' => 'edit-body',
+        '#disabled' => TRUE);
+    }
+
+    $form['body_field']['body'] = array(
       '#type' => 'textarea',
       '#title' => check_plain($type->body_label),
       '#default_value' => $node->body,
       '#rows' => 20,
       '#required' => ($type->min_word_count > 0));
-    $form['body_filter']['format'] = filter_form($node->format);
+
+    $form['body_field']['format'] = filter_form($node->format);
   }
 
   return $form;
Index: misc/teaser.js
===================================================================
RCS file: misc/teaser.js
diff -N misc/teaser.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ misc/teaser.js	5 Jan 2007 22:03:47 -0000
@@ -0,0 +1,73 @@
+// $Id$
+
+/**
+ * Auto-attach for teaser behaviour.
+ *
+ * Note: depends on resizable textareas.
+ */
+Drupal.teaserAttach = function() {
+  $('textarea.teaser:not(.joined)').each(function() {
+    var teaser = $(this).addClass('joined');
+
+    // Move teaser textarea before body, and remove its form-item wrapper.
+    var body = $('#'+ Drupal.settings.teaser[this.id]);
+    var parent = teaser[0].parentNode;
+    $(body).before(teaser);
+    $(parent).remove();
+    
+    function trim(text) {
+      return text.replace(/^\s+/g, '').replace(/\s+$/g, '');
+    }
+
+    // Join the teaser back to the body.
+    function join_teaser() {
+      if (teaser.val()) {
+        body.val(trim(teaser.val()) +'\r\n\r\n'+ trim(body.val()));
+      }
+      // Hide and disable teaser
+      $(teaser).attr('disabled', 'disabled');
+      $(teaser).parent().slideUp('fast');
+      // Change label
+      $(this).val(Drupal.settings.teaserButton[1]);
+    }
+
+    // Split the teaser from the body.
+    function split_teaser() {
+      body[0].focus();
+      var selection = Drupal.getSelection(body[0]);
+      var split = selection.start;
+      var text = body.val();
+
+      // Note: using val() fails sometimes. jQuery bug?
+      teaser[0].value = trim(text.slice(0, split));
+      body[0].value = trim(text.slice(split));
+      // Reveal and enable teaser
+      $(teaser).attr('disabled', '');
+      $(teaser).parent().slideDown('fast');
+      // Change label
+      $(this).val(Drupal.settings.teaserButton[0]);
+    }
+
+    // Add split/join button.
+    var button = $('<input type="button" class="teaser-button" />')
+      .prependTo($(this).parent())
+
+    // Extract the teaser from the body, if set. Otherwise, stay in joined mode.
+    var text = body.val().split('<!--break-->', 2);
+    if (text.length == 2) {
+      teaser[0].value = trim(text[0]);
+      body[0].value = trim(text[1]);
+      $(teaser).attr('disabled', '');
+      $(button).val(Drupal.settings.teaserButton[0]).toggle(join_teaser, split_teaser);
+    }
+    else {
+      $(teaser).hide();
+      $(button).val(Drupal.settings.teaserButton[1]).toggle(split_teaser, join_teaser);
+    }
+    
+  });
+}
+
+if (Drupal.jsEnabled) {
+  $(document).ready(Drupal.teaserAttach);
+}
