Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1021 diff -u -p -r1.1021 common.inc --- includes/common.inc 15 Oct 2009 21:19:30 -0000 1.1021 +++ includes/common.inc 15 Oct 2009 22:19:40 -0000 @@ -3292,7 +3292,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); Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.536 diff -u -p -r1.536 theme.inc --- includes/theme.inc 15 Oct 2009 12:27:34 -0000 1.536 +++ includes/theme.inc 15 Oct 2009 22:19:41 -0000 @@ -2088,6 +2088,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; @@ -2119,6 +2142,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 15 Oct 2009 22:19:41 -0000 @@ -0,0 +1,62 @@ +/* $Id$ */ + +/** + * Styling for attached links and the page regions they are associated with. + */ +.attached-links-link { + -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-configure { + background-image: url(configure.png); +} + +.attached-links-link-delete { + background-image: url(delete.png); +} + +.attached-links-link-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 15 Oct 2009 22:19:41 -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'); + $(this).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.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.api.php,v retrieving revision 1.8 diff -u -p -r1.8 block.api.php --- modules/block/block.api.php 31 Aug 2009 17:06:08 -0000 1.8 +++ modules/block/block.api.php 15 Oct 2009 22:19:41 -0000 @@ -151,6 +151,70 @@ function hook_block_view($delta = '') { } /** + * Perform alterations to the content of a block. + * + * This hook allows you to modify any data returned by hook_block_view(). + * + * Note that instead of hook_block_view_alter(), which is called for all + * blocks, you can also use hook_block_view_MODULE_DELTA_alter() to alter a + * specific block. + * + * @param $data + * An array of data containing the keys 'subject' and 'content', as returned + * from the hook_block_view() implementation of the module that defined the + * block. Note that $data['content'] may be either a string or a renderable + * array, so you should check that the content is actually an array before + * trying to modify part of the renderable structure. + * @param $module + * The name of the module that defined the block. + * @param $delta + * The identifier for the block within that module, as defined within + * hook_block_info(). + */ +function hook_block_view_alter(&$data, $module, $delta) { + // Remove the contextual links attached to all blocks that provide them. + if (is_array($data['content']) && isset($data['content']['#attached_links'])) { + unset($data['content']['#attached_links']); + } + // Add a theme wrapper function defined by the current module to all blocks + // provided by the "somemodule" module. + if (is_array($data['content']) && $module == 'somemodule') { + $data['content']['#theme_wrappers'][] = 'mymodule_special_block'; + } +} + +/** + * Perform alterations to a specific block. + * + * Modules can implement hook_block_view_MODULE_DELTA_alter() to modify a + * specific block, rather than implementing hook_block_view_alter() and either + * checking for specific blocks via an if statement or using a long switch + * statement to alter multiple blocks in the same function. + * + * Note that this hook fires before hook_block_view_alter(). Therefore, all + * implementations of hook_block_view_MODULE_DELTA_alter() will run before all + * implementations of hook_block_view_alter(), regardless of the module order. + * + * @param $data + * An array of data containing the keys 'subject' and 'content', as returned + * from the hook_block_view() implementation of the module that defined the + * block. Note that $data['content'] may be either a string or a renderable + * array, so you should check that the content is actually an array before + * trying to modify part of the renderable structure. + * + * @see hook_block_view_alter(). + */ +function hook_block_view_MODULE_DELTA_alter(&$data) { + // This code will only run for a specific block. For example, if MODULE_DELTA + // in the function definition above is set to "mymodule_somedelta", the code + // will only run on the "somedelta" block provided by the "mymodule" module. + + // Change the title of the "somedelta" block provided by the "mymodule" + // module. + $data['subject'] = t('New title of the block'); +} + +/** * Act on blocks prior to rendering. * * This hook allows you to add, remove or modify blocks in the block list. The Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.389 diff -u -p -r1.389 block.module --- modules/block/block.module 14 Oct 2009 02:13:14 -0000 1.389 +++ modules/block/block.module 15 Oct 2009 22:19:41 -0000 @@ -280,6 +280,29 @@ function _block_get_renderable_array($li foreach ($list as $key => $block) { $build[$key] = $block->content; unset($block->content); + // Attach links to the block. Skip this for empty blocks and also for the + // main system content block and help block. These blocks are somewhat + // unusual, and putting links on them would distract from the main content + // of the page; in addition, they do not have many useful configuration + // options. + if (!empty($build[$key]) && ($block->module != 'system' || ($block->delta != 'main' && $block->delta != 'help'))) { + $build[$key]['#attached_links'] = array( + // First add the configuration links for the block itself. + 'block' => array( + '#type' => 'attached_links', + '#links' => array( + 'configure' => array( + 'href' => "admin/structure/block/configure/{$block->module}/{$block->delta}", + 'title' => t('Configure'), + 'description' => !empty($block->subject) ? t('Configure the !block block', array('!block' => drupal_strtolower($block->subject))) : t('Configure this block'), + ), + ), + ), + // Now attach any links that were previously attached to the block + // content, so we can group them together with the links above. + 'block content' => !empty($build[$key]['#attached_links']) ? $build[$key]['#attached_links'] : array(), + ); + } $build[$key] += array( '#block' => $block, '#weight' => ++$weight, @@ -752,6 +775,10 @@ function _block_render_blocks($region_bl } else { $array = module_invoke($block->module, 'block_view', $block->delta); + // Allow modules to modify the block before it is viewed, via either + // hook_block_view_MODULE_DELTA_alter() or hook_block_view_alter(). + drupal_alter("block_view_{$block->module}_{$block->delta}", $array); + drupal_alter('block_view', $array, $block->module, $block->delta); if (isset($cid)) { cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY); } 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 15 Oct 2009 22:19:41 -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.45 diff -u -p -r1.45 locale.test --- modules/locale/locale.test 15 Oct 2009 17:53:34 -0000 1.45 +++ modules/locale/locale.test 15 Oct 2009 22:19:42 -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.module =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v retrieving revision 1.209 diff -u -p -r1.209 menu.module --- modules/menu/menu.module 13 Oct 2009 01:24:07 -0000 1.209 +++ modules/menu/menu.module 15 Oct 2009 22:19:42 -0000 @@ -431,10 +431,49 @@ 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'])) { + $data['content']['#attached_links'] = menu_attached_links($delta, $menus[$delta]); + } return $data; } /** + * Implement hook_block_view_alter(). + */ +function menu_block_view_alter(&$data, $module, $delta) { + // Attach links to the system menus. + if ($module == 'system' && !empty($data['content'])) { + $system_menus = menu_list_system_menus(); + if (isset($system_menus[$delta])) { + $data['content']['#attached_links'] = menu_attached_links($delta, $system_menus[$delta]); + } + } +} + +/** + * Prepare a set of attached links for a menu. + * + * @param $menu_name + * The machine-readable name of the menu. + * @param $title + * The title that is displayed for the menu. + * @return + * An array representing the attached links. + */ +function menu_attached_links($menu_name, $title) { + return array( + '#type' => 'attached_links', + '#links' => array( + 'edit' => array( + 'href' => 'admin/structure/menu/manage/' . $menu_name, + 'title' => t('Edit'), + 'description' => t('Edit the !menu menu', array('!menu' => drupal_strtolower($title))), + ), + ), + ); +} + +/** * Implement hook_node_insert(). */ function menu_node_insert($node) { Index: modules/node/node.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v retrieving revision 1.39 diff -u -p -r1.39 node.api.php --- modules/node/node.api.php 10 Oct 2009 21:39:03 -0000 1.39 +++ modules/node/node.api.php 15 Oct 2009 22:19:42 -0000 @@ -511,7 +511,7 @@ function hook_node_validate($node, $form } /** - * The node content is being assembled before rendering. + * The node is being assembled before rendering. * * TODO D7 This needs work to clearly explain the different build modes. * @@ -519,6 +519,12 @@ function hook_node_validate($node, $form * will be called after hook_view(). The structure of $node->content is a * renderable array as expected by drupal_render(). * + * The module may also add elements to $node->build_data. This is used for + * additional contextual data (such as contextual links) that are not part of + * the node content but that may still be rendered along with the node. The + * structure of $node->build_data is a renderable array as expected by + * drupal_render(). + * * When $build_mode is 'rss', modules can also add extra RSS elements and * namespaces to $node->rss_elements and $node->rss_namespaces respectively for * the RSS item generated for this node. @@ -542,10 +548,11 @@ function hook_node_view($node, $build_mo } /** - * The node content was built, the module may modify the structured content. + * The node was built; the module may modify the structured content. * * This hook is called after the content has been assembled in $node->content - * and may be used for doing processing which requires that the complete node + * and any additional build data has been assembled in $node->build_data. It + * may be used for doing processing which requires that the complete node * content structure has been built. * * If the module wishes to act on the rendered HTML of the node rather than the @@ -565,6 +572,9 @@ function hook_node_build_alter($node, $b $node->content['an_additional_field']['#weight'] = -10; } + // Do not show a delete link for this node. + unset($node->build_data['#attached_links']['#links']['delete']); + // Add a #post_render callback to act on the rendered HTML of the node. $node->content['#post_render'][] = 'my_module_node_post_render'; } Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1146 diff -u -p -r1.1146 node.module --- modules/node/node.module 15 Oct 2009 12:44:36 -0000 1.1146 +++ modules/node/node.module 15 Oct 2009 22:19:42 -0000 @@ -1098,12 +1098,13 @@ function node_revision_delete($revision_ * An array as expected by drupal_render(). */ function node_build($node, $build_mode = 'full') { - // Populate $node->content with a render() array. + // Populate $node->content and $node->build_data with renderable arrays. node_build_content($node, $build_mode); - $build = $node->content; - // We don't need duplicate rendering info in node->content. + $build = $node->content + $node->build_data; + // We don't need duplicate rendering info. unset($node->content); + unset($node->build_data); $build += array( '#theme' => 'node', @@ -1114,7 +1115,7 @@ function node_build($node, $build_mode = } /** - * Builds a structured array representing the node's content. + * Builds a structured array representing the node and its content. * * The content built for the node (field values, comments, file attachments or * other node components) will vary depending on the $build_mode parameter. @@ -1168,6 +1169,29 @@ function node_build_content($node, $buil '#attributes' => array('class' => array('links', 'inline')), ); + // Attach contextual links to the node when it is being viewed outside of + // its primary context. + if (empty($node->build_data)) { + $node->build_data = array(); + } + $node->build_data += array( + '#attached_links' => array( + '#type' => 'attached_links', + '#links' => array( + 'edit' => array( + 'href' => "node/{$node->nid}/edit", + 'title' => t('Edit'), + 'description' => t('Edit !type !node', array('!type' => drupal_strtolower(node_type_get_name($node)), '!node' => drupal_strtolower($node->title[FIELD_LANGUAGE_NONE][0]['value']))), + ), + 'delete' => array( + 'href' => "node/{$node->nid}/delete", + 'title' => t('Delete'), + 'description' => t('Delete !type !node', array('!type' => drupal_strtolower(node_type_get_name($node)), '!node' => drupal_strtolower($node->title[FIELD_LANGUAGE_NONE][0]['value']))), + ), + ), + ), + ); + // Allow modules to make their own additions to the node. module_invoke_all('node_view', $node, $build_mode); Index: modules/node/node.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.tpl.php,v retrieving revision 1.24 diff -u -p -r1.24 node.tpl.php --- modules/node/node.tpl.php 11 Oct 2009 06:43:33 -0000 1.24 +++ modules/node/node.tpl.php 15 Oct 2009 22:19:42 -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 @@ + + + + > Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.816 diff -u -p -r1.816 system.module --- modules/system/system.module 15 Oct 2009 21:19:31 -0000 1.816 +++ modules/system/system.module 15 Oct 2009 22:19:44 -0000 @@ -215,6 +215,12 @@ function system_theme() { 'arguments' => array('form' => NULL), 'file' => 'system.admin.inc', ), + 'attached_links' => array( + 'arguments' => array('links' => NULL, 'attributes' => array(), 'heading' => array()), + ), + 'attached_links_group' => array( + 'arguments' => array('elements' => NULL), + ), )); } @@ -255,6 +261,10 @@ function system_permission() { 'title' => t('Block IP addresses'), 'description' => t('Block IP addresses from accessing your site.'), ), + 'view contextual links' => array( + 'title' => t('View contextual links'), + 'description' => t('Access a global "edit mode" which displays contextual links associated with items on the page.'), + ), ); } @@ -314,6 +324,16 @@ function system_element_info() { '#attributes' => array(), '#items' => array(), ); + $types['attached_links'] = array( + '#theme' => 'attached_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'), + ), + ); // By default, we don't want AJAX commands being rendered in the context of an // HTML page, so we don't provide defaults for #theme or #theme_wrappers. // However, modules can set these properties (for example, to provide an HTML @@ -526,6 +546,14 @@ function system_menu() { 'type' => MENU_CALLBACK, 'file' => 'system.admin.inc', ); + + $items['edit-mode/toggle'] = array( + 'title callback' => 'edit_mode_toggle_title', + 'page callback' => 'edit_mode_toggle', + 'access arguments' => array('view contextual links'), + 'type' => MENU_CALLBACK, + ); + $items['admin'] = array( 'title' => 'Administer', 'access arguments' => array('access administration pages'), @@ -1409,6 +1437,13 @@ function blocked_ip_load($iid) { } /** + * Title callback for the page edit mode toggle. + */ +function edit_mode_toggle_title() { + return empty($_SESSION['edit_mode']) ? t('Enable edit mode') : t('Disable edit mode'); +} + +/** * Menu item access callback - only admin or enabled themes can be accessed. */ function _system_themes_access($theme) { @@ -2883,6 +2918,20 @@ function system_timezone($abbreviation = } /** + * Menu callback; Toggle the global edit mode and redirect. + */ +function edit_mode_toggle() { + if (!empty($_SESSION['edit_mode'])) { + unset($_SESSION['edit_mode']); + } + else { + $_SESSION['edit_mode'] = 1; + } + $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''; + drupal_goto($referer); +} + +/** * Format the Powered by Drupal text. * * @ingroup themeable @@ -3444,6 +3493,133 @@ function system_date_format_delete($dfid } /** + * 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) { + if (empty($_SESSION['edit_mode']) || !user_access('view contextual links')) { + // Do not show attached links when the edit mode is disabled or when the + // user does not have permission to view them. + return array(); + } + + $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 and description from the menu item if none were set. + if (!isset($link_data['title']) || !isset($link_data['description'])) { + $item = menu_get_item($link_data['href']); + $link_data += array( + 'title' => $item['title'], + 'description' => $item['description'], + ); + } + + // Define classes for the link item. + $link_item_classes = array( + 'attached-links-item', + drupal_html_class("attached-links-item-$type"), + ); + $link_item_classes = implode(' ', $link_item_classes); + + // Define classes for the link itself. + $existing_link_classes = !empty($link_data['attributes']['class']) ? $link_data['attributes']['class'] : array(); + $link_classes = array_merge($existing_link_classes, array( + "attached-links-link", + "attached-links-link-$type", + // Associate the link with the region of the page it is attached to. See + // template_generate_attached_link_classes() for the matching class + // attached to the page region. + drupal_html_class('attached-links-link-to-' . $link_data['href']), + )); + + // Transform and add to the link. + $transformed_links[$link_item_classes] = 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(), + // Make sure that the link description will appear on hover. + 'title' => $link_data['title'], + 'attributes' => array( + 'class' => $link_classes, + 'title' => $link_data['description'], + ), + ); + } + + // Replace the links data with the recomputed items. + $element['#links'] = $transformed_links; + return $element; +} + +/** + * Theme a set of attached links. + * + * @param $variables + * An associative array containing the same properties that are passed in to + * theme_links(). + * + * @return + * A themed HTML string representing the set of attached links. + * + * @ingroup themeable + * @see theme_links() + */ +function theme_attached_links($variables) { + foreach ($variables['links'] as &$link) { + // Wrap the link title in a class that will allow it to be displayed as an + // icon instead. We therefore need to mark this link as HTML and make sure + // to sanitize it ourselves if necessary, since theme_links() will not. + $sanitized_title = !empty($link['html']) ? $link['title'] : check_plain($link['title']); + $link['title'] = '' . $sanitized_title . ''; + $link['html'] = TRUE; + } + return theme('links', $variables); +} + +/** + * Theme a group of attached links. + * + * A group of attached links can contain multiple "sets" of attached links, + * where each set is themed via theme_attached_links(). This function is + * intended to be a simple wrapper that allows all sets associated with an + * element to be grouped together, while still allowing templates to pull out + * sets individually and render them in different places (for example, the + * set of attached links associated with configuring a block may need to be + * displayed in a different place as the set of attached links associated with + * the content of the block). + * + * @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 + * @see theme_attached_links() + */ +function theme_attached_links_group($variables) { + return ''; +} + +/** * Implement hook_archiver_info(). */ function system_archiver_info() { Index: modules/toolbar/toolbar.install =================================================================== RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.install,v retrieving revision 1.6 diff -u -p -r1.6 toolbar.install --- modules/toolbar/toolbar.install 13 Oct 2009 13:54:55 -0000 1.6 +++ modules/toolbar/toolbar.install 15 Oct 2009 22:19:44 -0000 @@ -27,7 +27,11 @@ function toolbar_install() { 'node/add' => 'Add content', 'admin/content' => 'Find content', 'admin/dashboard' => 'Dashboard', + // No title, so the title callback associated with the menu item will be + // used instead. + 'edit-mode/toggle' => '', ); + $weight = -20; foreach ($items as $path => $title) { $link = array( 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 15 Oct 2009 22:19:44 -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 15 Oct 2009 22:19:44 -0000 @@ -3,6 +3,10 @@ ?>
> + + + + 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 15 Oct 2009 22:19:44 -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 { } /** + * Styling 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 {