Index: mollom.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.admin.inc,v
retrieving revision 1.39
diff -u -p -r1.39 mollom.admin.inc
--- mollom.admin.inc	20 Sep 2010 03:39:10 -0000	1.39
+++ mollom.admin.inc	22 Sep 2010 09:44:05 -0000
@@ -327,6 +327,10 @@ function mollom_admin_blacklist_form($fo
     'links' => t('Links'),
     'author' => t('Author name'),
   );
+  $matches = array(
+    'contains' => t('contains'),
+    'exact' => t('exact'),
+  );
 
   $form['blacklist'] = array();
   // Do not retrieve the current blacklist when submitting the form.
@@ -336,9 +340,21 @@ function mollom_admin_blacklist_form($fo
       if ($entry['reason'] != $type) {
         continue;
       }
+      // #class property is internally used by
+      // theme_mollom_admin_blacklist_form().
       $row = array(
-        'context' => array('#markup' => check_plain($contexts[$entry['context']])),
-        'text' => array('#markup' => check_plain($entry['text'])),
+        'context' => array(
+          '#markup' => check_plain($contexts[$entry['context']]),
+          '#class' => 'mollom-blacklist-context value-' . check_plain($entry['context']),
+        ),
+        'match' => array(
+          '#markup' => check_plain($matches[$entry['match']]),
+          '#class' => 'mollom-blacklist-match value-' . check_plain($entry['match']),
+        ),
+        'text' => array(
+          '#markup' => check_plain($entry['text']),
+          '#class' => 'mollom-blacklist-text',
+        ),
       );
       $row['actions']['delete'] = array(
         '#type' => 'link',
@@ -356,17 +372,36 @@ function mollom_admin_blacklist_form($fo
     }
   }
 
+  // The value NULL in the following select lists will intentionally trigger a
+  // form validation error, since the elements are #required.
+  // @see http://drupal.org/node/140783
   $form['entry']['context'] = array(
     '#type' => 'select',
-    '#options' => $contexts,
-    '#default_value' => 'everything',
+    '#title' => t('Context'),
+    '#title_display' => 'invisible',
+    '#options' => array(NULL => t('- Any -')) + $contexts,
     '#required' => TRUE,
+    '#id' => 'mollom-blacklist-filter-context',
+  );
+  $form['entry']['match'] = array(
+    '#type' => 'select',
+    '#title' => t('Match'),
+    '#title_display' => 'invisible',
+    '#options' => array(NULL => t('- Any -')) + $matches,
+    '#required' => TRUE,
+    '#id' => 'mollom-blacklist-filter-match',
   );
   $form['entry']['text'] = array(
     '#type' => 'textfield',
+    '#title' => t('Text'),
+    '#title_display' => 'invisible',
     '#size' => 40,
     '#required' => TRUE,
     '#maxlength' => 64,
+    '#id' => 'mollom-blacklist-filter-text',
+    '#attributes' => array(
+      'autocomplete' => 'off',
+    ),
   );
   $form['entry']['reason'] = array(
     '#type' => 'value',
@@ -391,6 +426,7 @@ function mollom_admin_blacklist_form_sub
   $data = array(
     'text' => $form_state['values']['entry']['text'],
     'context' => $form_state['values']['entry']['context'],
+    'match' => $form_state['values']['entry']['match'],
     'reason' => $form_state['values']['entry']['reason'],
   );
   $result = mollom('mollom.addBlacklistText', $data);
@@ -398,12 +434,13 @@ function mollom_admin_blacklist_form_sub
   $args = array(
     '@text' => $data['text'],
     '@context' => $data['context'],
+    '@match' => $data['match'],
     '@reason' => $data['reason'],
   );
   if ($result === TRUE) {
     drupal_set_message(t('The entry was added to the blacklist.'));
     _mollom_watchdog(array(
-      'Added @text (@context) to @reason blacklist.' => $args,
+      'Added @text (@context, @match) to @reason blacklist.' => $args,
       'Data:<pre>@data</pre>' => array('@data' => $data),
       'Result:<pre>@result</pre>' => array('@result' => $result),
     ));
@@ -411,7 +448,7 @@ function mollom_admin_blacklist_form_sub
   else {
     drupal_set_message(t('An error occurred upon trying to add the text to the blacklist.'), 'error');
     _mollom_watchdog(array(
-      'Failed to add @text (%context) to @reason blacklist.' => $args,
+      'Failed to add @text (@context, @match) to @reason blacklist.' => $args,
       'Data:<pre>@data</pre>' => array('@data' => $data),
       'Result:<pre>@result</pre>' => array('@result' => $result),
     ), WATCHDOG_ERROR);
@@ -425,6 +462,7 @@ function theme_mollom_admin_blacklist_fo
   $form = $variables['form'];
   $header = array(
     t('Context'),
+    t('Matches'),
     t('Text'),
     '',
   );
@@ -432,22 +470,35 @@ function theme_mollom_admin_blacklist_fo
 
   $rows[] = array(
     drupal_render($form['entry']['context']),
+    drupal_render($form['entry']['match']),
     drupal_render($form['entry']['text']),
     drupal_render($form['entry']['actions']),
   );
 
   foreach (element_children($form['blacklist']) as $id) {
     $rows[] = array(
-      drupal_render($form['blacklist'][$id]['context']),
-      drupal_render($form['blacklist'][$id]['text']),
+      array(
+        'data' => drupal_render($form['blacklist'][$id]['context']),
+        'class' => $form['blacklist'][$id]['context']['#class'],
+      ),
+      array(
+        'data' => drupal_render($form['blacklist'][$id]['match']),
+        'class' => $form['blacklist'][$id]['match']['#class'],
+      ),
+      array(
+        'data' => drupal_render($form['blacklist'][$id]['text']),
+        'class' => $form['blacklist'][$id]['text']['#class'],
+      ),
       drupal_render($form['blacklist'][$id]['actions']),
     );
   }
 
   // This table is never empty due to the form.
-  $output  = theme('table', array('header' => $header, 'rows' => $rows));
+  $output  = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'mollom-blacklist')));
   $output .= drupal_render_children($form);
 
+  drupal_add_js(drupal_get_path('module', 'mollom') . '/mollom.admin.js');
+
   return $output;
 }
 
Index: mollom.admin.js
===================================================================
RCS file: mollom.admin.js
diff -N mollom.admin.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mollom.admin.js	22 Sep 2010 09:37:00 -0000
@@ -0,0 +1,74 @@
+// $Id: mollom.js,v 1.9 2010/08/04 03:58:46 dries Exp $
+(function ($) {
+
+/**
+ * Filters blacklist entries.
+ */
+Drupal.behaviors.mollomBlacklistFilter = {
+  attach: function (context) {
+    var self = this;
+    $('#mollom-blacklist', context).once('mollom-blacklist-filter', function () {
+      // Prepare a list of all entries to optimize performance. Each key is a
+      // blacklisted text and each value is an object containing the
+      // corresponding table row, context, and match.
+      self.entries = {};
+      $(this).find('tr:has(.mollom-blacklist-text)').each(function () {
+        var $row = $(this);
+        self.entries[$row.find('.mollom-blacklist-text').text()] = {
+          context: $row.children('.mollom-blacklist-context').attr('class').match(/value-(\w+)/)[1],
+          match: $row.children('.mollom-blacklist-match').attr('class').match(/value-(\w+)/)[1],
+          row: $row.get(0)
+        };
+      });
+
+      // Attach the instant text filtering behavior.
+      var $filterText = $('#mollom-blacklist-filter-text', context);
+      var $filterContext = $('#mollom-blacklist-filter-context', context);
+      var $filterMatch = $('#mollom-blacklist-filter-match', context);
+
+      self.lastSearch = {};
+      var filterRows = function () {
+        // Prepare static variables and conditions only once.
+        var text, visible, changed;
+        var search = {
+          text: $filterText.val(),
+          context: $filterContext.val(),
+          match: $filterMatch.val()
+        };
+        // Immediately cancel processing if search values did not change.
+        changed = false;
+        for (var i in search) {
+          if (search[i] != self.lastSearch[i]) {
+            changed = true;
+            break;
+          }
+        }
+        if (!changed) {
+          return;
+        }
+        self.lastSearch = search;
+        // Blacklists can contain thousands of entries, so we use a simple
+        // for...in loop instead of jQuery.each() to save many function calls.
+        // Likewise, we directly apply the 'display' style, since
+        // jQuery.fn.hide() and jQuery.fn.show() call into jQuery.fn.animate(),
+        // which is useless for this purpose.
+        for (text in self.entries) {
+          visible = (search.text.length == 0 || text.indexOf(search.text) != -1);
+          visible = visible && (search.context.length == 0 || self.entries[text].context == search.context);
+          visible = visible && (search.match.length == 0 || self.entries[text].match == search.match);
+          if (visible) {
+            self.entries[text].row.style.display = '';
+          }
+          else {
+            self.entries[text].row.style.display = 'none';
+          }
+        }
+      };
+      $filterText.keyup(filterRows);
+      $filterContext.change(filterRows);
+      $filterMatch.change(filterRows);
+    });
+  }
+};
+
+})(jQuery);
Index: tests/mollom.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom.test,v
retrieving revision 1.61
diff -u -p -r1.61 mollom.test
--- tests/mollom.test	20 Sep 2010 03:39:10 -0000	1.61
+++ tests/mollom.test	22 Sep 2010 10:05:52 -0000
@@ -1473,6 +1473,7 @@ class MollomBlacklistTestCase extends Mo
     $edit = array(
       'entry[text]' => $text,
       'entry[context]' => 'everything',
+      'entry[match]' => 'contains',
     );
     $this->drupalPost(NULL, $edit, t('Add'));
     $text = drupal_strtolower($text);
@@ -1495,6 +1496,7 @@ class MollomBlacklistTestCase extends Mo
     $edit = array(
       'entry[text]' => $text,
       'entry[context]' => 'everything',
+      'entry[match]' => 'contains',
     );
     $this->drupalPost(NULL, $edit, t('Add'));
     $this->assertText(t('The entry was added to the blacklist.'));
