Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1012 diff -u -p -r1.1012 common.inc --- includes/common.inc 11 Oct 2009 02:14:43 -0000 1.1012 +++ includes/common.inc 11 Oct 2009 05:10:01 -0000 @@ -3138,7 +3138,7 @@ function drupal_clear_css_cache() { * @return * The cleaned identifier. */ -function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '[' => '-', ']' => '')) { +function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) { // By default, we filter using Drupal's coding standards. $identifier = strtr($identifier, $filter); @@ -4939,10 +4939,10 @@ function drupal_common_theme() { 'arguments' => array('tree' => NULL), ), 'menu_local_task' => array( - 'arguments' => array('link' => NULL, 'active' => FALSE), + 'arguments' => array('element' => NULL), ), 'menu_local_action' => array( - 'arguments' => array('link' => NULL), + 'arguments' => array('element' => NULL), ), 'menu_local_tasks' => array( 'arguments' => array(), Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.350 diff -u -p -r1.350 menu.inc --- includes/menu.inc 9 Oct 2009 08:02:24 -0000 1.350 +++ includes/menu.inc 11 Oct 2009 05:11:55 -0000 @@ -1349,29 +1349,29 @@ function theme_menu_link(array $variable * * @param $variables * An associative array containing: - * - link: A menu link array with 'title', 'href', and 'localized_options' + * - #link: A menu link array with 'title', 'href', and 'localized_options' * keys. - * - active: A boolean indicating whether the local task is active. + * - #active: A boolean indicating whether the local task is active. * * @ingroup themeable */ function theme_menu_local_task($variables) { - $link = $variables['link']; - return '
  • ' . l($link['title'], $link['href'], $link['localized_options']) . "
  • \n"; + $link = $variables['element']['#link']; + return '' . l($link['title'], $link['href'], $link['localized_options']) . "\n"; } /** * Generate the HTML output for a single local action link. * - * @param $variables + * @param $element * An associative array containing: - * - link: A menu link array with 'title', 'href', and 'localized_options' + * - #link: A menu link array with 'title', 'href', and 'localized_options' * keys. * * @ingroup themeable */ function theme_menu_local_action($variables) { - $link = $variables['link']; + $link = $variables['element']['#link']; return '
  • ' . l($link['title'], $link['href'], $link['localized_options']) . "
  • \n"; } @@ -1549,6 +1549,9 @@ function menu_navigation_links($menu_nam * * @param $level * The level of tasks you ask for. Primary tasks are 0, secondary are 1. + * @param $path + * (optional) A menu link path to collect tabs and actions for. + * * @return * An array containing * - tabs: Local tasks for the requested level: @@ -1560,25 +1563,28 @@ function menu_navigation_links($menu_nam * - root_path: The router path for the current page. If the current page is * a default local task, then this corresponds to the parent tab. */ -function menu_local_tasks($level = 0) { +function menu_local_tasks($level = 0, $path = NULL) { $data = &drupal_static(__FUNCTION__); - $root_path = &drupal_static(__FUNCTION__ . ':root_path', ''); + $empty = array( - 'tabs' => array('count' => 0, 'output' => ''), - 'actions' => array('count' => 0, 'output' => ''), - 'root_path' => &$root_path, + 'tabs' => array('count' => 0, 'output' => array()), + 'actions' => array('count' => 0, 'output' => array()), + 'root_path' => '', ); - if (!isset($data)) { - $data = array(); + $router_item = menu_get_item($path); + if (!$router_item || !$router_item['access']) { + return $empty; + } + $root_path = $router_item['path']; + $empty['root_path'] = $root_path; + + if (!isset($data[$root_path])) { + $data[$root_path] = $empty; // Set defaults in case there are no actions or tabs. $actions = $empty['actions']; $tabs = array(); - $router_item = menu_get_item(); - if (!$router_item || !$router_item['access']) { - return $empty; - } // Get all tabs and the root page. $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC)) ->fields('menu_router') @@ -1586,10 +1592,9 @@ function menu_local_tasks($level = 0) { ->orderBy('weight') ->orderBy('title') ->execute(); - $map = arg(); + $map = $router_item['original_map']; $children = array(); $tasks = array(); - $root_path = $router_item['path']; foreach ($result as $item) { _menu_translate($item, $map, TRUE); @@ -1606,8 +1611,8 @@ function menu_local_tasks($level = 0) { // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort). $depth = 1001; while (isset($children[$path])) { - $tabs_current = ''; - $actions_current = ''; + $tabs_current = array(); + $actions_current = array(); $next_path = ''; $tab_count = 0; $action_count = 0; @@ -1620,17 +1625,27 @@ function menu_local_tasks($level = 0) { for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']); // Use the path of the parent instead. $link['href'] = $tasks[$p]['href']; - $tabs_current .= theme('menu_local_task', array('link' => $link, 'active' => TRUE)); + $tabs_current[] = array( + '#theme' => 'menu_local_task', + '#link' => $link, + '#active' => TRUE, + ); $next_path = $item['path']; $tab_count++; } else { if ($item['type'] == MENU_LOCAL_TASK) { - $tabs_current .= theme('menu_local_task', array('link' => $link)); + $tabs_current[] = array( + '#theme' => 'menu_local_task', + '#link' => $link, + ); $tab_count++; } else { - $actions_current .= theme('menu_local_action', array('link' => $link)); + $actions_current[] = array( + '#theme' => 'menu_local_action', + '#link' => $link, + ); $action_count++; } } @@ -1643,14 +1658,14 @@ function menu_local_tasks($level = 0) { $actions['output'] = $actions_current; $depth++; } - $data['actions'] = $actions; + $data[$root_path]['actions'] = $actions; // Find all tabs at the same level or above the current one. $parent = $router_item['tab_parent']; $path = $router_item['path']; $current = $router_item; $depth = 1000; while (isset($children[$parent])) { - $tabs_current = ''; + $tabs_current = array(); $next_path = ''; $next_parent = ''; $count = 0; @@ -1667,19 +1682,26 @@ function menu_local_tasks($level = 0) { // Use the path of the parent instead. $link['href'] = $tasks[$p]['href']; if ($item['path'] == $router_item['path']) { - $root_path = $tasks[$p]['path']; + $data[$root_path]['root_path'] = $tasks[$p]['path']; } } // We check for the active tab. if ($item['path'] == $path) { - $tabs_current .= theme('menu_local_task', array('link' => $link, 'active' => TRUE)); + $tabs_current[] = array( + '#theme' => 'menu_local_task', + '#link' => $link, + '#active' => TRUE, + ); $next_path = $item['tab_parent']; if (isset($tasks[$next_path])) { $next_parent = $tasks[$next_path]['tab_parent']; } } else { - $tabs_current .= theme('menu_local_task', array('link' => $link)); + $tabs_current[] = array( + '#theme' => 'menu_local_task', + '#link' => $link, + ); } } } @@ -1693,14 +1715,17 @@ function menu_local_tasks($level = 0) { ksort($tabs); // Remove the depth, we are interested only in their relative placement. $tabs = array_values($tabs); - $data['tabs'] = $tabs; + $data[$root_path]['tabs'] = $tabs; + + // Allow modules to alter local tasks or dynamically append further tasks. + drupal_alter('menu_local_tasks', $data, $router_item, $root_path); } - if (isset($data['tabs'][$level])) { + if (isset($data[$root_path]['tabs'][$level])) { return array( - 'tabs' => $data['tabs'][$level], - 'actions' => $data['actions'], - 'root_path' => $root_path, + 'tabs' => $data[$root_path]['tabs'][$level], + 'actions' => $data[$root_path]['actions'], + 'root_path' => $data[$root_path]['root_path'], ); } return $empty; @@ -1741,18 +1766,22 @@ function menu_tab_root_path() { } /** - * Returns the rendered local tasks. The default implementation renders them as tabs. + * Returns renderable local tasks. * * @ingroup themeable */ function theme_menu_local_tasks() { - $output = ''; + $output = array(); if ($primary = menu_primary_local_tasks()) { - $output .= "\n"; + $primary['#prefix'] = ''; + $output[] = $primary; } if ($secondary = menu_secondary_local_tasks()) { - $output .= "\n"; + $secondary['#prefix'] = ''; + $output[] = $secondary; } return $output; Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.533 diff -u -p -r1.533 theme.inc --- includes/theme.inc 9 Oct 2009 16:33:13 -0000 1.533 +++ includes/theme.inc 10 Oct 2009 17:39:51 -0000 @@ -2081,6 +2081,29 @@ function template_preprocess(&$variables $variables['attributes_array'] = array(); $variables['title_attributes_array'] = array(); + // Initialize a variable to hold any attached links. This is populated when + // the first item passed to the theme function is a page element that has + // links attached to it. + $variables['attached_links'] = array(); + $element = reset($variables); + if (is_array($element) && !empty($element['#attached_links'])) { + $variables['attached_links'] = $element['#attached_links']; + // Add appropriate CSS classes that associate the element with the links + // that are attached to it. + $attached_link_classes = template_generate_attached_link_classes($element['#attached_links']); + if (!empty($attached_link_classes)) { + $variables['classes_array'][] = 'attached-links-enabled-region'; + $variables['classes_array'] = array_merge($variables['classes_array'], $attached_link_classes); + } + // Wrap the attached links associated with this element in a theme function + // that will group them together for display. Templates which render + // different sets of attached links separately - for example, by using + // render($attached_links['block']) rather than render($attached_links) - + // will automatically avoid this wrapper function and therefore will have + // complete freedom to display the links anywhere within the page element. + $variables['attached_links']['#theme_wrappers'][] = 'attached_links_group'; + } + // Set default variables that depend on the database. $variables['is_admin'] = FALSE; $variables['is_front'] = FALSE; @@ -2112,6 +2135,40 @@ function template_process(&$variables, $ } /** + * Generate a list of relevant CSS classes from an array of attached links. + * + * The array of attached links is searched recursively for all items of type + * 'attached_links', and the 'href' parameter of each link is used to generate + * an appropriate CSS class. + * + * @param $attached_links + * An associative array containing the links that will be searched. Usually, + * this will be the '#attached_links' property of a page element. + * @return + * An array of CSS classes generated from the attached links. + * + * @see system_pre_render_attached_links() + */ +function template_generate_attached_link_classes($attached_links) { + $classes = array(); + // If the array itself contains attached links, generate classes from them. + if (isset($attached_links['#type']) && $attached_links['#type'] == 'attached_links') { + if (isset($attached_links['#links'])) { + foreach ($attached_links['#links'] as $link) { + $classes[] = drupal_html_class('attached-links-region-for-' . $link['href']); + } + } + } + // Otherwise, recursively search the element's children for links. + else { + foreach (element_children($attached_links) as $group) { + $classes = array_merge($classes, template_generate_attached_link_classes($attached_links[$group])); + } + } + return $classes; +} + +/** * Preprocess variables for html.tpl.php * * @see system_elements() Index: misc/attached_links.css =================================================================== RCS file: misc/attached_links.css diff -N misc/attached_links.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/attached_links.css 10 Oct 2009 17:44:23 -0000 @@ -0,0 +1,62 @@ +/* $Id$ */ + +/** + * Highlighted regions for attached_links.js. + */ +.attached-links-link-icon { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + background: #444 none no-repeat scroll 2px 2px; + height: 19px; + width: 19px; + float: left; + display: block; + margin-right: 2px; +} + +.attached-links-link-icon-configure { + background-image: url(configure.png); +} + +.attached-links-link-icon-delete { + background-image: url(delete.png); +} + +.attached-links-link-icon-edit { + background-image: url(edit.png); +} + +.attached-links-enabled-region { + outline: none; + position: relative; +} + +.active-attached-links-region { + outline: #000 dashed 2px; +} + +.attached-links-group { + position: absolute; + right: 0; + top: 0; + padding: 0; + margin: 0; +} + +ul.attached-links { + float: right; + padding: 0; + margin: 0; +} + +ul.attached-links li { + padding: 0; + margin: 0; + list-style: none; + display: inline; + line-height: 100%; +} + +ul.attached-links li a { + text-decoration: none; +} Index: misc/attached_links.js =================================================================== RCS file: misc/attached_links.js diff -N misc/attached_links.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/attached_links.js 10 Oct 2009 17:45:59 -0000 @@ -0,0 +1,31 @@ +// $Id$ +(function ($) { + +/** + * Highlights the region of the page that an attached link is associated with. + */ +Drupal.behaviors.attachedLinks = { + attach: function (context) { + var addHighlight = function () { + // If the attached link has a CSS class containing an encoded version of + // the URL that the link points to, find the region of the page whose CSS + // class contains the same encoded URL. + var matches = $(this).attr('class').match(/\battached-links-link-to-(\S+)/); + if (matches) { + var className = '.attached-links-region-for-' + matches[1]; + $(className).addClass('active-attached-links-region'); + $(className).addClass('active-attached-links-link'); + } + }; + + var removeHighlight = function () { + $('.active-attached-links-region').removeClass('active-attached-links-region'); + $('.active-attached-links-link').removeClass('active-attached-links-link'); + }; + + // Trigger the behavior when hovering over the link. + $('.attached-links-link').hover(addHighlight, removeHighlight); + } +}; + +})(jQuery); Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.386 diff -u -p -r1.386 block.module --- modules/block/block.module 10 Oct 2009 21:39:01 -0000 1.386 +++ modules/block/block.module 11 Oct 2009 05:05:48 -0000 @@ -249,6 +249,27 @@ function block_get_blocks_by_region($reg foreach ($list as $key => $block) { $build[$key] = $block->content; unset($block->content); + + // Attach links for this block; skip this for the system main block. + // @todo Actually, this is nonsense. The local tasks we render elsewhere + // are the system main block's edit links. Right? ;) + if ($key != 'system_main') { + $build[$key] += array( + '#attached_links' => array( + '#type' => 'attached_links', + '#links' => array(), + ), + ); + // Add local tasks for blocks; not overwriting any existing links. + // @todo Implement proper path/subject/verb URL pattern for blocks. + $build[$key]['#attached_links']['#links'] += array( + 'configure' => array( + 'href' => "admin/structure/block/configure/{$block->module}/{$block->delta}", + 'title' => t('Configure'), + ), + ); + } + $build[$key] += array( '#block' => $block, '#weight' => ++$weight, Index: modules/block/block.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.tpl.php,v retrieving revision 1.4 diff -u -p -r1.4 block.tpl.php --- modules/block/block.tpl.php 11 Sep 2009 06:48:02 -0000 1.4 +++ modules/block/block.tpl.php 10 Oct 2009 17:39:51 -0000 @@ -11,6 +11,12 @@ * - $block->module: Module that generated the block. * - $block->delta: An ID for the block, unique within each module. * - $block->region: The block region embedding the current block. + * - $attached_links: An array of links that are attached to the block on the + * page. Use render($attached_links) to print them all, or print a subset + * using render($attached_links['block']), which prints the configuration + * links for the block itself, or render($attached_links['block content']), + * which prints the links associated with the content that is displayed in + * the block. * - $classes: String of classes that can be used to style contextually through * CSS. It can be manipulated through the variable $classes_array from * preprocess functions. The default values can be one or more of the following: @@ -36,6 +42,11 @@ */ ?>
    > + + + + + subject): ?> >subject ?> Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.43 diff -u -p -r1.43 locale.test --- modules/locale/locale.test 11 Oct 2009 03:07:18 -0000 1.43 +++ modules/locale/locale.test 11 Oct 2009 05:05:48 -0000 @@ -1087,7 +1087,7 @@ class LanguageSwitchingFunctionalTest ex $this->assertText(t('Languages'), t('Language switcher block found.')); // Assert that only the current language is marked as active. - list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]'); + list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]/div[@class="content"]'); $links = array( 'active' => array(), 'inactive' => array(), @@ -1096,7 +1096,7 @@ class LanguageSwitchingFunctionalTest ex 'active' => array(), 'inactive' => array(), ); - foreach ($language_switcher->div->ul->li as $link) { + foreach ($language_switcher->ul->li as $link) { $classes = explode(" ", (string) $link['class']); list($language) = array_intersect($classes, array('en', 'fr')); if (in_array('active', $classes)) { Index: modules/menu/menu.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.api.php,v retrieving revision 1.15 diff -u -p -r1.15 menu.api.php --- modules/menu/menu.api.php 9 Oct 2009 08:02:24 -0000 1.15 +++ modules/menu/menu.api.php 11 Oct 2009 05:12:43 -0000 @@ -430,5 +430,68 @@ function hook_menu_delete($menu) { } /** + * Alter tabs and actions displayed on the page before they are rendered. + * + * This hook is invoked by menu_local_tasks(). The system-determined tabs and + * actions are passed in by reference. Additional tabs or actions may be added, + * or existing items altered. + * + * Each tab or action is an associative array containing: + * - #theme: The theme function to use to render. + * - #link: An associative array containing: + * - title: The localized title of the link. + * - href: The system path to link to. + * - localized_options: An array of options to pass to url(). + * - #active: Whether the link should be marked as 'active'. + * + * @param $data + * An associative array containing: + * - actions: An associative array containing: + * - count: The amount of actions determined by the menu system, which can + * be ignored. + * - output: A list of of actions, each one being an associative array + * as described above. + * - tabs: An indexed array (list) of tab levels (up to 2 levels), each + * containing an associative array: + * - count: The amount of tabs determined by the menu system. This value + * does not need to be altered if there is more than one tab. + * - output: A list of of tabs, each one being an associative array as + * described above. + */ +function hook_menu_local_tasks_alter(&$data, $router_item, $root_path) { + // Add an action linking to node/add to all pages. + $data['actions']['output'][] = array( + '#theme' => 'menu_local_task', + '#link' => array( + 'title' => t('Add new content'), + 'href' => 'node/add', + 'localized_options' => array( + 'attributes' => array( + 'title' => t('Add new content'), + ), + ), + ), + ); + + // Add a tab linking to node/add to all pages. + $data['tabs'][0]['output'][] = array( + '#theme' => 'menu_local_task', + '#link' => array( + 'title' => t('Example tab'), + 'href' => 'node/add', + 'localized_options' => array( + 'attributes' => array( + 'title' => t('Add new content'), + ), + ), + ), + // Define whether this link is active. This can be omitted for + // implementations that add links to pages outside of the current page + // context. + '#active' => ($router_item['path'] == $root_path), + ); +} + +/** * @} End of "addtogroup hooks". */ Index: modules/menu/menu.module =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v retrieving revision 1.208 diff -u -p -r1.208 menu.module --- modules/menu/menu.module 11 Oct 2009 03:07:18 -0000 1.208 +++ modules/menu/menu.module 11 Oct 2009 05:05:49 -0000 @@ -383,6 +383,19 @@ function menu_block_view($delta = '') { $menus = menu_get_menus(FALSE); $data['subject'] = check_plain($menus[$delta]); $data['content'] = menu_tree($delta); + if (!empty($data['content'])) { + // @todo This is how we will do it. 11/10/2009 sun + $tasks = menu_local_tasks(0, 'admin/structure/menu/manage/' . $delta); + $data['content']['#attached_links'] = array( + '#type' => 'attached_links', + '#links' => array( + 'edit' => array( + 'href' => 'admin/structure/menu/manage/' . $delta, + 'title' => t('Edit the !menu menu', array('!menu' => drupal_strtolower($menus[$delta]))), + ), + ), + ); + } return $data; } Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1145 diff -u -p -r1.1145 node.module --- modules/node/node.module 11 Oct 2009 03:07:18 -0000 1.1145 +++ modules/node/node.module 11 Oct 2009 05:05:49 -0000 @@ -1102,10 +1102,26 @@ function node_build($node, $build_mode = $build = $node->content; // We don't need duplicate rendering info in node->content. unset($node->content); - + + // @todo This is how we will do it. 11/10/2009 sun + $tasks = menu_local_tasks(0, 'node/' . $node->nid); + $build += array( '#theme' => 'node', '#node' => $node, + '#attached_links' => array( + '#type' => 'attached_links', + '#links' => array( + 'edit' => array( + 'href' => "node/{$node->nid}/edit", + 'title' => t('Edit !type !node', array('!type' => drupal_strtolower(node_type_get_name($node)), '!node' => drupal_strtolower($node->title))), + ), + 'delete' => array( + 'href' => "node/{$node->nid}/delete", + 'title' => t('Delete !type !node', array('!type' => drupal_strtolower(node_type_get_name($node)), '!node' => drupal_strtolower($node->title))), + ), + ), + ), '#build_mode' => $build_mode, ); return $build; Index: modules/node/node.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.tpl.php,v retrieving revision 1.23 diff -u -p -r1.23 node.tpl.php --- modules/node/node.tpl.php 11 Oct 2009 03:07:18 -0000 1.23 +++ modules/node/node.tpl.php 11 Oct 2009 05:05:49 -0000 @@ -18,6 +18,8 @@ * - $node_url: Direct url of the current node. * - $terms: the themed list of taxonomy term links output from theme_links(). * - $display_submitted: whether submission information should be displayed. + * - $attached_links: An array of links that are attached to the node on the + * page. Use render($attached_links) to print them all. * - $classes: String of classes that can be used to style contextually through * CSS. It can be manipulated through the variable $classes_array from * preprocess functions. The default values can be one or more of the following: @@ -74,6 +76,10 @@ + + + + >a href=""> Index: modules/system/page.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/system/page.tpl.php,v retrieving revision 1.36 diff -u -p -r1.36 page.tpl.php --- modules/system/page.tpl.php 9 Oct 2009 01:00:05 -0000 1.36 +++ modules/system/page.tpl.php 11 Oct 2009 05:13:05 -0000 @@ -31,14 +31,14 @@ * - $secondary_menu (array): An array containing the Secondary menu links for * the site, if they have been configured. * - $breadcrumb: The breadcrumb trail for the current page. - * - $action_links: Actions local to the page, such as 'Add menu' on the menu - * administration interface. * * Page content (in order of occurrence in the default page.tpl.php): * - $title: The page title, for use in the actual HTML content. * - $messages: HTML for status and error messages. Should be displayed prominently. - * - $tabs: Tabs linking to any sub-pages beneath the current page (e.g., the view - * and edit tabs when displaying a node). + * - $tabs (array): Tabs linking to any sub-pages beneath the current page + * (e.g., the view and edit tabs when displaying a node). + * - $action_links (array): Actions local to the page, such as 'Add menu' on the + * menu administration interface. * - $feed_icons: A string of all feed icons for the current page. * * Regions: @@ -107,9 +107,9 @@

    -
    +
    - +
    Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.804 diff -u -p -r1.804 system.module --- modules/system/system.module 10 Oct 2009 16:48:38 -0000 1.804 +++ modules/system/system.module 11 Oct 2009 00:05:33 -0000 @@ -204,6 +204,9 @@ function system_theme() { 'system_run_cron_image' => array( 'arguments' => array('image_path' => NULL), ), + 'attached_links_group' => array( + 'arguments' => array('elements' => NULL), + ), )); } @@ -303,6 +306,16 @@ function system_element_info() { '#attributes' => array(), '#items' => array(), ); + $types['attached_links'] = array( + '#theme' => 'links', + '#pre_render' => array('system_pre_render_attached_links'), + '#attributes' => array('class' => array('attached-links')), + '#links' => array(), + '#attached' => array( + 'js' => array('misc/attached_links.js'), + 'css' => array('misc/attached_links.css'), + ), + ); // Input elements. $types['submit'] = array( @@ -507,6 +520,7 @@ function system_menu() { 'type' => MENU_CALLBACK, 'file' => 'system.admin.inc', ); + $items['admin'] = array( 'title' => 'Administer', 'access arguments' => array('access administration pages'), @@ -1641,6 +1655,19 @@ function system_block_view($delta = '') if (isset($system_menus[$delta])) { $block['subject'] = t($system_menus[$delta]); $block['content'] = menu_tree($delta); + // @todo: Should we move this to the menu module? The code is much + // cleaner and easier to read when we put it here. + if (!empty($block['content']) && module_exists('menu')) { + $block['content']['#attached_links'] = array( + '#type' => 'attached_links', + '#links' => array( + 'edit' => array( + 'href' => 'admin/structure/menu/manage/' . $delta, + 'title' => t('Edit the !menu menu', array('!menu' => drupal_strtolower($block['subject']))), + ), + ), + ); + } return $block; } break; @@ -2901,3 +2928,76 @@ function theme_system_run_cron_image($va return ''; } +/** + * Checks access, adds classes, and performs other actions on attached links. + * + * This is used as a pre-render function for the #attached_links type. + */ +function system_pre_render_attached_links($element) { + $transformed_links = array(); + foreach ($element['#links'] as $type => $link_data) { + // Use the menu item to determine access to the link, if an access property + // was not already set. + if (!isset($link_data['#access'])) { + $item = menu_get_item($link_data['href']); + $link_data['#access'] = !empty($item['access']); + } + + // Do not display this link if the current user does not have access to it, + // or if the user is already on the page that is being linked to. + if (!$link_data['#access'] || $link_data['href'] == $_GET['q']) { + continue; + } + + // Get the link title from the menu item if none was set. + if (!isset($link_data['title'])) { + $item = menu_get_item($link_data['href']); + $link_data['title'] = $item['title']; + } + + // Define a class that will associate this link with the region of the page + // that it is attached to. See template_generate_attached_link_classes(). + $link_class = implode(' ', array('attached-links-link', drupal_html_class("attached-links-$type-link"), drupal_html_class('attached-links-link-to-' . $link_data['href']))); + + // Add wrapper classes for the icon display. + $existing_wrapper_class = !empty($link_data['attributes']['class']) ? $link_data['attributes']['class'] : array(); + $wrapper_class = array_merge($existing_wrapper_class, array("attached-links-link-icon", "attached-links-link-icon-$type")); + + // Transform and add to the link. + $transformed_links[$link_class] = array( + 'href' => $link_data['href'], + // Refer users back to the current page after they have completed any + // action (for example, a form submission) associated with the attached + // link. + 'query' => drupal_get_destination(), + // Prepare the link to be displayed as an icon with the original link + // title appearing on hover. + 'title' => '', + 'attributes' => array( + 'class' => $wrapper_class, + 'title' => $link_data['title'], + ), + ); + } + + // Replace the links data with the recomputed items. + $element['#links'] = $transformed_links; + return $element; +} + +/** + * Theme a group of attached links. + * + * @param $variables + * An associative array containing: + * - elements: An associative array containing the properties of the element. + * Properties used: #children + * + * @return + * A themed HTML string representing the group of attached links. + * + * @ingroup themeable + */ +function theme_attached_links_group($variables) { + return ''; +} Index: themes/garland/block.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/block.tpl.php,v retrieving revision 1.9 diff -u -p -r1.9 block.tpl.php --- themes/garland/block.tpl.php 11 Sep 2009 06:48:03 -0000 1.9 +++ themes/garland/block.tpl.php 10 Oct 2009 17:39:51 -0000 @@ -3,6 +3,10 @@ ?>
    > + + + + subject)): ?>

    >subject ?>

    Index: themes/garland/node.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/node.tpl.php,v retrieving revision 1.17 diff -u -p -r1.17 node.tpl.php --- themes/garland/node.tpl.php 11 Oct 2009 03:07:21 -0000 1.17 +++ themes/garland/node.tpl.php 11 Oct 2009 05:05:50 -0000 @@ -3,6 +3,10 @@ ?>
    > + + + + Index: themes/garland/page.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/page.tpl.php,v retrieving revision 1.35 diff -u -p -r1.35 page.tpl.php --- themes/garland/page.tpl.php 5 Oct 2009 02:43:01 -0000 1.35 +++ themes/garland/page.tpl.php 11 Oct 2009 05:13:26 -0000 @@ -33,11 +33,11 @@
    > -
    -
    +
    +
    - +
    Index: themes/garland/style.css =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/style.css,v retrieving revision 1.65 diff -u -p -r1.65 style.css --- themes/garland/style.css 5 Oct 2009 02:43:01 -0000 1.65 +++ themes/garland/style.css 10 Oct 2009 17:39:51 -0000 @@ -650,8 +650,8 @@ ul.secondary li.active a { */ .node { border-bottom: 1px solid #e9eff3; - margin: 0 -26px 1.5em; - padding: 1.5em 26px; + margin: 0 -16px 1.5em; + padding: 1.5em 16px; } ul.links li, ul.inline li { @@ -809,6 +809,32 @@ tr.even td.menu-disabled { } /** + * Icons for attached links. + */ +.attached-links-enabled-region { + outline: none; +} + +.active-attached-links-region { + outline: dashed #027AC6 2px; +} + +ul.attached-links li a { + text-decoration: none; + background-color: #027AC6; +} + +ul.attached-links li a:hover { + background-color: #0062A0; +} + +/* Override the styling applied to normal block lists. */ +.block ul.attached-links { + margin: 0; + padding: 0; +} + +/** * Collapsible fieldsets */ fieldset { Index: themes/seven/page.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/seven/page.tpl.php,v retrieving revision 1.4 diff -u -p -r1.4 page.tpl.php --- themes/seven/page.tpl.php 15 Sep 2009 17:10:39 -0000 1.4 +++ themes/seven/page.tpl.php 11 Oct 2009 05:13:44 -0000 @@ -4,11 +4,11 @@

    -
    +
    -
    +
    @@ -19,7 +19,7 @@
    - +