Index: link.module
===================================================================
--- link.module	(revision 1876)
+++ link.module	(working copy)
@@ -45,7 +45,7 @@
   return array(
     'link' => array(
       'label' => t('Link'),
-      'description' => t('Store a title, href, and attributes in the database to assemble a link.'),
+      'description' => t('Store a title, href, description (alt) and attributes in the database to assemble a link.'),
     ),
   );
 }
@@ -89,6 +89,27 @@
         '#size' => '46',
       );
 
+      $description_options = array(
+        'optional' => t('Optional Description'),
+        'required' => t('Required Description'),
+        'value' => t('Static Description: '),
+        'none' => t('No Description'),
+      );
+
+      $form['description'] = array(
+        '#type' => 'radios',
+        '#title' => t('Link Description'),
+        '#default_value' => isset($field['description']) ? $field['description'] : 'optional',
+        '#options' => $description_options,
+        '#description' => t('If the link description is optional or required, a field will be displayed to the end user. If the link description is static, the link will always use the same description. If <a href="http://drupal.org/project/token">token module</a> is installed, the static description value may use any other node field as its value. Static and token-based descriptions may include most inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, etc.'),
+      );
+
+      $form['description_value'] = array(
+        '#type' => 'textfield',
+        '#default_value' => $field['description_value'],
+        '#size' => '46',
+      );
+
       // Add token module replacements if available
       if (module_exists('token')) {
         $form['tokens'] = array(
@@ -175,6 +196,7 @@
       return array(
         'url' => array('type' => 'varchar', 'length' => LINK_URL_MAX_LENGTH, 'not null' => FALSE, 'sortable' => TRUE),
         'title' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE),
+        'description' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE),
         'attributes' => array('type' => 'text', 'size' => 'medium', 'not null' => FALSE),
       );
 
@@ -205,7 +227,6 @@
   if (empty($item['title']) && empty($item['url'])) {
     return TRUE;
   }
-  return FALSE;
 }
 
 /**
@@ -317,6 +338,12 @@
   if ($field['url'] == 'optional' && $field['title'] == 'optional' && (strlen(trim($item['url'])) != 0 || strlen(trim($item['title'])) != 0)) {
     $optional_field_found = TRUE;
   }
+  
+    // Require a description for the link if necessary.
+  if ($field['description'] == 'required' && strlen(trim($item['alt'])) == 0) {
+     form_set_error($field['field_name'] .']['. $delta .'][alt', t('Descriptions are required for all links.'));
+  }
+  return FALSE;
 }
 
 /**
@@ -451,6 +478,15 @@
     'link_formatter_default' => array(
       'arguments' => array('element' => NULL),
     ),
+    'link_formatter_descbefore' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'link_formatter_descafter' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'link_formatter_descalt' => array(
+      'arguments' => array('element' => NULL),
+    ),
     'link_formatter_plain' => array(
       'arguments' => array('element' => NULL),
     ),
@@ -483,18 +519,25 @@
     if (isset($element['url']) && isset($element['title'])) {
       $element['url']['#title'] = $element['#title'] .' '. $element['url']['#title'];
       $element['title']['#title'] = $element['#title'] .' '. $element['title']['#title'];
+      $element['description']['#title'] = $element['#title'] .' '. $element['description']['#title'];
     }
     elseif ($element['url']) {
       $element['url']['#title'] = $element['#title'];
     }
   }
+  
+  $classes = array('link-field-subrow','clear-block');
+  if(isset($element['description'])) {$classes[] = 'link-field-compact';}
 
   $output = '';
-  $output .= '<div class="link-field-subrow clear-block">';
+  $output .= '<div class="' . implode(' ',$classes) . '">';
   if (isset($element['title'])) {
     $output .= '<div class="link-field-title link-field-column">'. theme('textfield', $element['title']) .'</div>';
   }
   $output .= '<div class="link-field-url'. (isset($element['title']) ? ' link-field-column' : '') .'">'. theme('textfield', $element['url']) .'</div>';
+  if (isset($element['description'])) {
+    $output .= '<div class="link-field-description link-field-column">'. theme('textfield', $element['description']) .'</div>';
+  }
   $output .= '</div>';
   if (!empty($element['attributes']['target'])) {
     $output .= '<div class="link-attributes">'. theme('checkbox', $element['attributes']['target']) .'</div>';
@@ -551,6 +594,15 @@
        '#default_value' => isset($element['#value']['title']) ? $element['#value']['title'] : NULL,
      );
    }
+   if ($field['description'] != 'none') {
+     $element['description'] = array(
+       '#type' => 'textfield',
+       '#maxlength' => '255',
+       '#title' => t('Description'),
+       '#required' => FALSE,
+       '#default_value' => isset($element['#value']['description']) ? $element['#value']['description'] : NULL,
+     );
+   }
 
    // Initialize field attributes as an array if it is not an array yet.
    if (!is_array($field['attributes'])) {
@@ -580,6 +632,21 @@
       'field types' => array('link'),
       'multiple values' => CONTENT_HANDLE_CORE,
     ),
+    'descbefore' => array(
+      'label' => t('Description before link, as label'),
+      'field types' => array('link'),
+      'multiple values' => CONTENT_HANDLE_CORE,
+    ),
+    'descafter' => array(
+      'label' => t('Description after link'),
+      'field types' => array('link'),
+      'multiple values' => CONTENT_HANDLE_CORE,
+    ),
+    'descalt' => array(
+      'label' => t('Description as alternate link text'),
+      'field types' => array('link'),
+      'multiple values' => CONTENT_HANDLE_CORE,
+    ),
     'url' => array(
       'label' => t('URL, as link'),
       'field types' => array('link'),
@@ -623,6 +690,54 @@
 }
 
 /**
+ * Theme function for 'description before' text field formatter.
+ */
+function theme_link_formatter_descbefore($element) {
+  // Display a normal link if both title and URL are available.
+  if (!empty($element['#item']['display_title']) && !empty($element['#item']['url'])) {
+    $description = $element['#item']['description'] ? ' <span class="field-type-link-description">' . $element['#item']['description'] . '</span>' . ": " : '';
+    return $description . l($element['#item']['display_title'], $element['#item']['url'], $element['#item']);
+  }
+  // If only a title, display the title.
+  elseif (!empty($element['#item']['display_title'])) {
+    $description = $element['#item']['description']  ? $element['#item']['description'] . ": " : '';
+    return $description . check_plain($element['#item']['display_title']);
+  }
+}
+
+/**
+ * Theme function for 'description after' text field formatter.
+ */
+function theme_link_formatter_descafter($element) {
+  // Display a normal link if both title and URL are available.
+  if (!empty($element['#item']['display_title']) && !empty($element['#item']['url'])) {
+    $description = $element['#item']['description'] ? ' ' . $element['#item']['description'] : '';
+    return l($element['#item']['display_title'], $element['#item']['url'], $element['#item']) . $description;
+  }
+  // If only a title, display the title.
+  elseif (!empty($element['#item']['display_title'])) {
+    $description = $element['#item']['description']  ? ' <span class="field-type-link-description">' . $element['#item']['description'] . '</span>' : '';
+    return check_plain($element['#item']['display_title']) . $description;
+  }
+}
+
+/**
+ * Theme function for 'description as alternate text' text field formatter.
+ */
+function theme_link_formatter_descalt($element) {
+  // Display a normal link if both title and URL are available.
+  if (!empty($element['#item']['display_title']) && !empty($element['#item']['url'])) {
+    $description = $element['#item']['description'] ? ' ' . $element['#item']['description'] : '';
+    return l($element['#item']['display_title'], $element['#item']['url'], $element['#item'], array('attributes' => array('alt' => $description)));
+  }
+  // If only a title, display the title.
+  elseif (!empty($element['#item']['display_title'])) {
+    $description = $element['#item']['description']  ? ' <span class="field-type-link-description" title="' . $description. '">' . $element['#item']['description'] . '</span>' : '';
+    return check_plain($element['#item']['display_title']);
+  }
+}
+
+/**
  * Theme function for 'plain' text field formatter.
  */
 function theme_link_formatter_plain($element) {
@@ -674,7 +789,8 @@
 
     $tokens['link']['url'] = t("Link URL");
     $tokens['link']['title'] = t("Link title");
-    $tokens['link']['view'] = t("Formatted html link");
+    $tokens['link']['description'] = t("Link description");
+    $tokens['link']['view'] = t("Formatted html link w/description");
 
     return $tokens;
   }
@@ -686,6 +802,7 @@
 
     $tokens['url'] = $item['url'];
     $tokens['title'] = $item['title'];
+    $tokens['description'] = $item['description'];
     $tokens['view'] = isset($item['view']) ? $item['view'] : '';
 
     return $tokens;
Index: views/link.views.inc
===================================================================
--- views/link.views.inc	(revision 1876)
+++ views/link.views.inc	(working copy)
@@ -73,6 +73,37 @@
       'allow_empty' => TRUE,
     ),
   );
+  // Build out additional views data for the link "description" field.
+  $data[$table_alias][$field['field_name'] .'_description'] = array(
+    'group' => t('Content'),
+    'title' => t('@label description', array('@label' => t($field_types[$field['type']]['label']))) .': '. t($field['widget']['label']) .' ('. $field['field_name'] .')',
+    'help' =>  $data[$table_alias][$field['field_name'] .'_url']['help'],
+    'argument' => array(
+      'field' => $db_info['columns']['description']['column'],
+      'tablename' => $db_info['table'],
+      'handler' => 'content_handler_argument_string',
+      'click sortable' => TRUE,
+      'name field' => '', // TODO, mimic content.views.inc :)
+      'content_field_name' => $field['field_name'],
+      'allow_empty' => TRUE,
+    ),
+    'filter' => array(
+      'field' => $db_info['columns']['description']['column'],
+      'title' => t('@label description', array('@label' => t($field_types[$field['type']]['label']))),
+      'tablename' => $db_info['table'],
+      'handler' => 'content_handler_filter_string',
+      'additional fields' => array(),
+      'content_field_name' => $field['field_name'],
+      'allow_empty' => TRUE,
+    ),
+    'sort' => array(
+      'field' => $db_info['columns']['description']['column'],
+      'tablename' => $db_info['table'],
+      'handler' => 'content_handler_sort',
+      'content_field_name' => $field['field_name'],
+      'allow_empty' => TRUE,
+    ),
+  );
 
   // Build out additional Views filter for the link "protocol" pseudo field.
   // TODO: Add a protocol argument.
Index: link.css
===================================================================
--- link.css	(revision 1876)
+++ link.css	(working copy)
@@ -3,6 +3,11 @@
   width: 48%;
 }
 
+div.link-field-compact div.link-field-column {
+  width: 33%;
+}
+
+
 div.link-field-column .form-text {
   width: 95%;
 }
