Index: edge.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/edge/edge.module,v
retrieving revision 1.2
diff -u -p -r1.2 edge.module
--- edge.module	5 Oct 2010 00:34:03 -0000	1.2
+++ edge.module	5 Oct 2010 00:51:15 -0000
@@ -14,7 +14,7 @@ function edge_theme_registry_alter(&$cal
 
   // Apply replacement theme callbacks, but only if they have not been
   // overridden.
-  $edge_theme = array('item_list');
+  $edge_theme = array('item_list', 'links');
   foreach ($edge_theme as $callback) {
     if (isset($callbacks[$callback]) && $callbacks[$callback]['function'] == 'theme_' . $callback) {
       $callbacks[$callback]['function'] = 'theme_edge_' . $callback;
Index: edge.theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/edge/edge.theme.inc,v
retrieving revision 1.2
diff -u -p -r1.2 edge.theme.inc
--- edge.theme.inc	5 Oct 2010 00:34:03 -0000	1.2
+++ edge.theme.inc	5 Oct 2010 00:50:56 -0000
@@ -93,3 +93,118 @@ function theme_edge_item_list($variables
   return $output;
 }
 
+/**
+ * Returns HTML for a set of links.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - links: A keyed array of links to be themed. The key for each link is used
+ *     as its css class. Each link should be itself an array, with the following
+ *     keys:
+ *     - title: the link text
+ *     - href: the link URL. If omitted, the 'title' is shown as a plain text
+ *       item in the links list.
+ *     - html: (optional) set this to TRUE if 'title' is HTML so it will be
+ *       escaped.
+ *     Array items are passed on to the l() function's $options parameter when
+ *     creating the link.
+ *   - attributes: A keyed array of attributes.
+ *   - heading: An optional keyed array or a string for a heading to precede the
+ *     links. When using an array the following keys can be used:
+ *     - text: the heading text
+ *     - level: the heading level (e.g. 'h2', 'h3')
+ *     - class: (optional) an array of the CSS classes for the heading
+ *     When using a string it will be used as the text of the heading and the
+ *     level will default to 'h2'.
+ *
+ *     Headings should be used on navigation menus and any list of links that
+ *     consistently appears on multiple pages. To make the heading invisible
+ *     use the 'element-invisible' CSS class. Do not use 'display:none', which
+ *     removes it from screen-readers and assistive technology. Headings allow
+ *     screen-reader and keyboard only users to navigate to or skip the links.
+ *     See http://juicystudio.com/article/screen-readers-display-none.php
+ *     and http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
+ */
+function theme_edge_links($variables) {
+  global $language_url;
+
+  $links = $variables['links'];
+  $attributes = $variables['attributes'];
+  $heading = $variables['heading'];
+  $output = '';
+
+  if ($links) {
+    // Prepend the heading to the list, if any.
+    if ($heading) {
+      // Convert a string heading into an array, using a H2 tag by default.
+      if (is_string($heading)) {
+        $heading = array('text' => $heading);
+      }
+      // Merge in default array properties into $heading.
+      $heading += array(
+        'level' => 'h2',
+        'attributes' => array(),
+      );
+      // @todo D8: Remove backwards compatibility for $heading['class'].
+      if (isset($heading['class'])) {
+        $heading['attributes']['class'] = $heading['class'];
+      }
+
+      $output .= '<' . $heading['level'] . drupal_attributes($heading['attributes']) . '>';
+      $output .= check_plain($heading['text']);
+      $output .= '</' . $heading['level'] . '>';
+    }
+
+    $output .= '<ul' . drupal_attributes($attributes) . '>';
+
+    $num_links = count($links);
+    $i = 0;
+    foreach ($links as $key => $link) {
+      $i++;
+
+      $class = array();
+      // Use the array key as class name.
+      $class[] = drupal_html_class($key);
+      // Add odd/even, first, and last classes.
+      $class[] = ($i % 2 ? 'odd' : 'even');
+      if ($i == 1) {
+        $class[] = 'first';
+      }
+      if ($i == $num_links) {
+        $class[] = 'last';
+      }
+      $item = '';
+
+      // Handle links.
+      if (isset($link['href'])) {
+        $is_current_path = ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()));
+        $is_current_language = (empty($link['language']) || $link['language']->language == $language_url->language);
+        if ($is_current_path && $is_current_language) {
+          $class[] = 'active';
+        }
+        // Pass in $link as $options, they share the same keys.
+        $item = l($link['title'], $link['href'], $link);
+      }
+      // Handle title-only text items.
+      else {
+        // Merge in default array properties into $link.
+        $link += array(
+          'html' => FALSE,
+          'attributes' => array(),
+        );
+        $item .= '<span' . drupal_attributes($link['attributes']) . '>';
+        $item .= ($link['html'] ? $link['title'] : check_plain($link['title']));
+        $item .= '</span>';
+      }
+
+      $output .= '<li' . drupal_attributes(array('class' => $class)) . '>';
+      $output .= $item;
+      $output .= '</li>';
+    }
+
+    $output .= '</ul>';
+  }
+
+  return $output;
+}
+
Index: tests/edge.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/edge/tests/edge.test,v
retrieving revision 1.2
diff -u -p -r1.2 edge.test
--- tests/edge.test	5 Oct 2010 00:34:03 -0000	1.2
+++ tests/edge.test	5 Oct 2010 00:54:50 -0000
@@ -77,6 +77,62 @@ class EdgeThemeUnitTest extends DrupalUn
   }
 
   /**
+   * Tests theme_edge_links().
+   */
+  function testLinks() {
+    // Verify that empty variables produce no output.
+    $variables = array();
+    $expected = '';
+    $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback generates no output.');
+
+    $variables = array();
+    $variables['heading'] = 'Some title';
+    $expected = '';
+    $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback with heading generates no output.');
+
+    $variables = array();
+    $variables['attributes'] = array('id' => 'somelinks');
+    $variables['links'] = array(
+      'a link' => array(
+        'title' => 'A <link>',
+        'href' => 'a/link',
+      ),
+      'plain text' => array(
+        'title' => 'Plain "text"',
+      ),
+      'front page' => array(
+        'title' => 'Front page',
+        'href' => '<front>',
+      ),
+    );
+
+    $expected_links = '';
+    $expected_links .= '<ul id="somelinks">';
+    $expected_links .= '<li class="a-link odd first"><a href="' . url('a/link') . '">' . check_plain('A <link>') . '</a></li>';
+    $expected_links .= '<li class="plain-text even"><span>' . check_plain('Plain "text"') . '</span></li>';
+    $expected_links .= '<li class="front-page odd last"><a href="' . url('<front>') . '">' . check_plain('Front page') . '</a></li>';
+    $expected_links .= '</ul>';
+
+    // Verify that passing a string as heading works.
+    $variables['heading'] = 'Links heading';
+    $expected_heading = '<h2>Links heading</h2>';
+    $expected = $expected_heading . $expected_links;
+    $this->assertThemeOutput('links', $variables, $expected);
+
+    // Verify that passing an array as heading works (core support).
+    $variables['heading'] = array('text' => 'Links heading', 'level' => 'h3', 'class' => 'heading');
+    $expected_heading = '<h3 class="heading">Links heading</h3>';
+    $expected = $expected_heading . $expected_links;
+    $this->assertThemeOutput('links', $variables, $expected);
+
+    // Verify that passing attributes for the heading works.
+    $variables['heading'] = array('text' => 'Links heading', 'level' => 'h3', 'attributes' => array('id' => 'heading'));
+    $expected_heading = '<h3 id="heading">Links heading</h3>';
+    $expected = $expected_heading . $expected_links;
+    $this->assertThemeOutput('links', $variables, $expected);
+  }
+
+  /**
    * Asserts themed output.
    *
    * @param $callback
