Index: notify_by_views.module
===================================================================
--- notify_by_views.module	(revision 3286)
+++ notify_by_views.module	(working copy)
@@ -162,7 +162,27 @@
       '#default_value' => variable_get('notify_by_views_send_html_mail', FALSE),
     );
     
+    $form['notify_by_views_set']['notify_by_views_teaser_length'] = array(
+      '#type' => 'select',
+      '#title' => t('Length of post teasers'),
+      '#default_value' => variable_get('notify_by_views_teaser_length', 0),
+      '#options' => array(
+        0 => t('Default'),
+        200 => t('200 characters'),
+        400 => t('400 characters'),
+        600 => t('600 characters'),
+        800 => t('800 characters'),
+        1000 => t('1000 characters'),
+        1200 => t('1200 characters'),
+        1400 => t('1400 characters'),
+        1600 => t('1600 characters'),
+        1800 => t('1800 characters'),
+        2000 => t('2000 characters'),
+      ),
+    );
+
     $form['notify_send']['#description'] = $form['notify_send']['#description'] . t(" If using Notify_by_Views, you can set this to 'Never' because user selected settings will be used.");
+
   }
   
 }
@@ -345,6 +365,7 @@
     $node_body = '';
     $comment_body = '';
     $included_nodes = notify_by_views_included($user, $view_name);
+    $teaser_length = variable_get('notify_by_views_teaser_length', 0);
 
     // Get a template.
     $template = variable_get("notify_by_views_{$view_name}_template", _notify_by_views_default_text()); 
@@ -375,7 +396,17 @@
           $status = t('Unpublished');
         }
 
+        // If html format, strip carriage returns from the email, before the
+        // custom teaser is generated.
+        if ($html) {
+          $node->body = preg_replace('/[\r\n|\n]/', '', $node->body);
+        }
 
+        // Use custom teaser length.
+        if ($teaser_length && $user->teasers == 1) {
+          $node->teaser = _notify_by_views_custom_teaser($node->body, $teaser_length);
+        }
+
         $title   = t('@title', array('@title' => $node->title));
         $author  = ($node->name ? $node->name : variable_get('anonymous', 'Anonymous'));
         $info    = $status ." ". node_get_types('name', $node);
@@ -556,3 +587,100 @@
   //dvm($included_nodes);   //debug
   return $included_nodes;
 }
+
+/**
+ * Essentially duplicates node_teaser() in Drupal, as specifying a custom teaser
+ * length is only possible in Drupal 6 and later.
+ */
+function _notify_by_views_custom_teaser($body, $size) {
+
+  if (!isset($size)) {
+    $size = variable_get('teaser_length', 600);
+  }
+
+  // Find where the delimiter is in the body
+  $delimiter = strpos($body, '<!--break-->');
+
+  // If the size is zero, and there is no delimiter, the entire body is the teaser.
+  if ($size == 0 && $delimiter === FALSE) {
+    return $body;
+  }
+
+  // If a valid delimiter has been specified, use it to chop off the teaser.
+  if ($delimiter !== FALSE) {
+    return substr($body, 0, $delimiter);
+  }
+
+  // We check for the presence of the PHP evaluator filter in the current
+  // format. If the body contains PHP code, we do not split it up to prevent
+  // parse errors.
+  if (isset($format)) {
+    $filters = filter_list_format($format);
+    if (isset($filters['filter/1']) && strpos($body, '<?') !== FALSE) {
+      return $body;
+    }
+  }
+
+  // If we have a short body, the entire body is the teaser.
+  if (strlen($body) <= $size) {
+    return $body;
+  }
+
+  // If the delimiter has not been specified, try to split at paragraph or
+  // sentence boundaries.
+
+  // The teaser may not be longer than maximum length specified. Initial slice.
+  $teaser = truncate_utf8($body, $size);
+
+  // Store the actual length of the UTF8 string -- which might not be the same
+  // as $size.
+  $max_rpos = strlen($teaser);
+
+  // How much to cut off the end of the teaser so that it doesn't end in the
+  // middle of a paragraph, sentence, or word.
+  // Initialize it to maximum in order to find the minimum.
+  $min_rpos = $max_rpos;
+
+  // Store the reverse of the teaser.  We use strpos on the reversed needle and
+  // haystack for speed and convenience.
+  $reversed = strrev($teaser);
+
+  // Build an array of arrays of break points grouped by preference.
+  $break_points = array();
+
+  // A paragraph near the end of sliced teaser is most preferable.
+  $break_points[] = array('</p>' => 0);
+
+  // If no complete paragraph then treat line breaks as paragraphs.
+  $line_breaks = array('<br />' => 6, '<br>' => 4);
+  // Newline only indicates a line break if line break converter
+  // filter is present.
+  if (isset($filters['filter/2'])) {
+    $line_breaks["\n"] = 1;
+  }
+  $break_points[] = $line_breaks;
+
+  // If the first paragraph is too long, split at the end of a sentence.
+  $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
+
+  // Iterate over the groups of break points until a break point is found.
+  foreach ($break_points as $points) {
+    // Look for each break point, starting at the end of the teaser.
+    foreach ($points as $point => $offset) {
+      // The teaser is already reversed, but the break point isn't.
+      $rpos = strpos($reversed, strrev($point));
+      if ($rpos !== FALSE) {
+        $min_rpos = min($rpos + $offset, $min_rpos);
+      }
+    }
+
+    // If a break point was found in this group, slice and return the teaser.
+    if ($min_rpos !== $max_rpos) {
+      // Don't slice with length 0.  Length must be <0 to slice from RHS.
+      return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos);
+    }
+  }
+
+  // If a break point was not found, still return a teaser.
+  return $teaser;
+}
