Index: modules/node/content_types.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/content_types.inc,v
retrieving revision 1.85
diff -u -r1.85 content_types.inc
--- modules/node/content_types.inc	9 Aug 2009 01:15:33 -0000	1.85
+++ modules/node/content_types.inc	10 Aug 2009 07:15:28 -0000
@@ -16,7 +16,7 @@
   $rows = array();
 
   foreach ($names as $key => $name) {
-    $type = $types[$key]; 
+    $type = $types[$key];
     if (node_hook($type->type, 'form')) {
       $type_url_str = str_replace('_', '-', $type->type);
       $row = array(theme('node_admin_overview', $name, $type));
@@ -37,7 +37,7 @@
   if (empty($rows)) {
     $rows[] = array(array('data' => t('No content types available. <a href="@link">Add content type</a>.', array('@link' => url('admin/structure/types/add'))), 'colspan' => '5', 'class' => 'message'));
   }
-  
+
   $build['node_table'] = array(
     '#theme' => 'table',
     '#header' => $header,
@@ -180,12 +180,23 @@
     '#description' => t('Enable the <em>submitted by Username on date</em> text.'),
   );
   $form['display']['teaser_length'] = array(
-    '#type' => 'select', 
+    '#type' => 'select',
     '#title' => t('Length of trimmed posts'),
     '#default_value' => variable_get('teaser_length_' . $type->type, 600),
     '#options' => drupal_map_assoc(array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000), '_node_characters'),
     '#description' => t("The maximum number of characters used in the trimmed version of content.")
   );
+  $form['display']['teaser_read_more_location'] = array(
+    '#type' => 'radios',
+    '#title' => t('Read more link'),
+    '#default_value' => variable_get('teaser_read_more_location_' . $type->type, 'inline'),
+    '#options' => array(
+      'hidden' => t('Disabled'),
+      'inline' => t('Append to node summary'),
+      'link' => t('Display in links section'),
+    ),
+    '#description' => t('Determine the location of "read more" links which allow a user to click through to the full post from the trimmed post.'),
+  );
   $form['old_type'] = array(
     '#type' => 'value',
     '#value' => $type->type,
@@ -304,8 +315,6 @@
     return;
   }
 
-  $status = node_type_save($type);
-
   $variables = $form_state['values'];
 
   // Remove everything that's been saved already - whatever's left is assumed
@@ -333,6 +342,8 @@
     }
   }
 
+  $status = node_type_save($type);
+
   node_types_rebuild();
   $t_args = array('%name' => $type->name);
 
@@ -409,6 +420,7 @@
   node_type_delete($form_state['values']['type']);
 
   variable_del('teaser_length_' . $form_state['values']['type']);
+  variable_del('teaser_read_more_location_' . $form_state['values']['type']);
   variable_del('node_preview_' . $form_state['values']['type']);
   $t_args = array('%name' => $form_state['values']['name']);
   drupal_set_message(t('The content type %name has been deleted.', $t_args));
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1095
diff -u -r1.1095 node.module
--- modules/node/node.module	8 Aug 2009 22:52:59 -0000	1.1095
+++ modules/node/node.module	10 Aug 2009 07:15:30 -0000
@@ -469,6 +469,7 @@
       );
       $field = field_create_field($field);
     }
+
     if (empty($instance)) {
       $instance = array(
         'field_name' => 'body',
@@ -492,11 +493,17 @@
           ),
         ),
       );
+
+      $summary_link = variable_get('teaser_read_more_location_' . $type->type, 'inline') == 'inline';
+      $instance['settings']['read_more'] = ($summary_link ? t('Read more') : '');
+
       field_create_instance($instance);
     }
     else {
       $instance['label'] = $type->body_label;
       $instance['settings']['display_summary'] = TRUE;
+      $summary_link = variable_get('teaser_read_more_location_' . $type->type, 'inline') == 'inline';
+      $instance['settings']['read_more'] = ($summary_link ? t('Read more') : '');
       field_update_instance($instance);
     }
   }
@@ -1145,11 +1152,13 @@
   // to know when a teaser view is different than a full view.
   $links = array();
   if ($build_mode == 'teaser') {
-    $links['node_readmore'] = array(
-      'title' => t('Read more'),
-      'href' => 'node/' . $node->nid,
-      'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title))
-    );
+    if (variable_get('teaser_read_more_location_' . $node->type, 'inline') == 'link') {
+      $links['node_readmore'] = array(
+        'title' => t('Read more'),
+        'href' => 'node/' . $node->nid,
+        'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title))
+      );
+    }
   }
   $node->content['links']['node'] = array(
     '#theme' => 'links',
Index: modules/field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.17
diff -u -r1.17 text.module
--- modules/field/modules/text/text.module	4 Aug 2009 06:38:56 -0000	1.17
+++ modules/field/modules/text/text.module	10 Aug 2009 07:15:28 -0000
@@ -32,6 +32,9 @@
     'field_formatter_text_summary_or_trimmed' => array(
       'arguments' => array('element' => NULL),
     ),
+    'read_more' => array(
+      'arguments' => array('link' => NULL, 'inline' => FALSE),
+    ),
   );
 }
 
@@ -68,7 +71,7 @@
       'label' => t('Long text and summary'),
       'description' => t('This field stores long text in the database along with optional summary text.'),
       'settings' => array('max_length' => ''),
-      'instance_settings' => array('text_processing' => 1, 'display_summary' => 0),
+      'instance_settings' => array('text_processing' => 1, 'display_summary' => 0, 'read_more' => ''),
       'default_widget' => 'text_textarea_with_summary',
       'default_formatter' => 'text_summary_or_trimmed',
     ),
@@ -302,12 +305,61 @@
   $instance = field_info_instance($element['#field_name'], $element['#bundle']);
 
   if (!empty($element['#item']['safe_summary'])) {
-    return $element['#item']['safe_summary'];
+    $summary =  $element['#item']['safe_summary'];
   }
   else {
     $size = variable_get('teaser_length_' . $element['#bundle'], 600);
-    return text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL, $size);
+    $summary = text_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL, $size);
+  }
+
+  // If enabled, add a "Read more" link to the end of the summary.
+  if (!empty($instance['settings']['read_more'])) {
+    if ($summary) {
+      // Define the tags that allow the read more link to be inserted right before their
+      // closing tag. If the summary ends with any other tag, the read more link will be
+      // appended at the end of the summary instead.
+      $inline_tags = array('p');
+
+      $htmlDom = DOMDocument::loadHTML($summary);
+
+      // The result of DOMDocument::saveXML($bodyNode) is a partial (X)HTML document.
+      // We only need what is inside the body tag.
+      $bodyNode = $htmlDom->getElementsByTagName('body')->item(0);
+
+      // Fetch the last element in the summary.
+      $node = $bodyNode->lastChild;
+
+      // Determine how to insert the read more link into the summary. Inline means the
+      // the link will be inserted before the ending tag of the last element in the summary,
+      // otherwise the link will be appended to the summary as a stand-alone element.
+      $inline = in_array($node->tagName, $inline_tags);
+
+      // Create and theme the read more link.
+      // TODO: find a type-agnostic way to build the link.
+      $link = $htmlDom->createDocumentFragment();
+      $link->appendXml(theme('read_more', l($instance['settings']['read_more'], "{$element['#object_type']}/{$element['#object']->nid}"), $inline));
+
+      // Add the read more link to the summary.
+      if ($inline && $node) {
+        $node->appendChild($link);
+      }
+      else {
+        $bodyNode->appendChild($link);
+      }
+
+      // DOM manipulation treats our fragment as a full document. Extract the contents of
+      // the <body> element, as we are only interested in that.
+      if (preg_match("|^<body[^>]*>(.*)</body>$|s", $htmlDom->saveXML($bodyNode), $matches)) {
+        $summary = $matches[1];
+      }
+    }
+    else {
+      // The summary is empty, return the read more link.
+      return theme('read_more', l($instance['settings']['read_more'], "{$element['#object_type']}/{$element['#object']->nid}"), FALSE);
+    }
   }
+
+  return $summary;
 }
 
 /**
@@ -744,3 +796,7 @@
   }
 }
 
+function theme_read_more($link, $inline = FALSE) {
+  $element = ($inline ? 'span' : 'div');
+  return ' <'. $element .' class="read-more">' . $link . '</'. $element .'>';
+}
\ No newline at end of file
