Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.346 diff -u -p -u -p -r1.346 menu.inc --- includes/menu.inc 18 Sep 2009 10:54:20 -0000 1.346 +++ includes/menu.inc 25 Sep 2009 11:38:59 -0000 @@ -789,7 +789,7 @@ function menu_get_object($type = 'node', } /** - * Render a menu tree based on the current path. + * Returns a menu tree based on the current path. * * The tree is expanded based on the current path and dynamic paths are also * changed according to the defined to_arg functions (for example the 'My account' @@ -798,20 +798,21 @@ function menu_get_object($type = 'node', * @param $menu_name * The name of the menu. * @return - * The rendered HTML of that menu on the current page. + * An array representing the menu on the current page, suitable for + * rendering with drupal_render(). */ function menu_tree($menu_name) { - $menu_output = &drupal_static(__FUNCTION__, array()); + $menu_tree = &drupal_static(__FUNCTION__, array()); - if (!isset($menu_output[$menu_name])) { + if (!isset($menu_tree[$menu_name])) { $tree = menu_tree_page_data($menu_name); - $menu_output[$menu_name] = menu_tree_output($tree); + $menu_tree[$menu_name] = menu_tree_output($tree); } - return $menu_output[$menu_name]; + return $menu_tree[$menu_name]; } /** - * Returns a rendered menu tree. + * Returns an array representing a menu, suitable for rendering. * * The menu item's LI element is given one of the following classes: * - expanded: The menu item is showing its submenu. @@ -1305,7 +1306,7 @@ function template_preprocess_menu_tree(& * @ingroup themeable */ function theme_menu_tree($tree) { - return ''; + return !empty($tree) ? '' : ''; } /** Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.526 diff -u -p -u -p -r1.526 theme.inc --- includes/theme.inc 21 Sep 2009 06:36:54 -0000 1.526 +++ includes/theme.inc 25 Sep 2009 11:39:00 -0000 @@ -847,10 +847,12 @@ function theme() { } else { // The theme call is a template. - $variables = array( - 'template_files' => array() - ); + $variables = array(); if (!empty($info['arguments'])) { + // Populate the variables with arguments passed to the theme function. + // Note that the first argument may be treated specially by template + // preprocess functions, so it must go into the variables array before + // anything else does. $count = 0; foreach ($info['arguments'] as $name => $default) { $variables[$name] = isset($args[$count]) ? $args[$count] : $default; @@ -858,6 +860,11 @@ function theme() { } } + // Add an empty array of template files as a default; preprocess functions + // will be able to modify this. We include it last, as per the comment + // above. + $variables += array('template_files' => array()); + // default render function and extension. $render_function = 'theme_render_template'; $extension = '.tpl.php'; @@ -885,7 +892,12 @@ function theme() { if (!empty($info[$phase])) { foreach ($info[$phase] as $processor_function) { if (function_exists($processor_function)) { - call_user_func_array($processor_function, $args); + $result = call_user_func_array($processor_function, $args); + // Allow process functions to abort rendering the item by returning + // FALSE. + if ($result === FALSE) { + return ''; + } } } } 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 25 Sep 2009 11:39:00 -0000 @@ -0,0 +1,58 @@ +/* $Id$ */ + +/* +** Highlighted regions for attached_links.js. +*/ +.attached-links-link-icon { + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-bottomright: 3px; + -moz-border-radius-topleft: 3px; + -moz-border-radius-topright: 3px; + background: #444444 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: dashed #000000 2px; +} + +ul.attached-links { + position: absolute; + right: 0; + top: 0; + 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 25 Sep 2009 11:39:00 -0000 @@ -0,0 +1,27 @@ +// $Id$ +(function ($) { + +/** + * Highlights the admin links region when hovering over an edit link. + */ +Drupal.behaviors.highlightEditableRegion = { + attach: function (context) { + var addHighlight = function () { + var matches = $(this).attr('class').match(/[^ ]*-at-(\S+)/); + if (matches) { + var class = '.attached-links-enabled-at-' + matches[1]; + $(class).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'); + }; + + $('.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.379 diff -u -p -u -p -r1.379 block.module --- modules/block/block.module 22 Sep 2009 07:50:15 -0000 1.379 +++ modules/block/block.module 25 Sep 2009 11:39:00 -0000 @@ -233,8 +233,23 @@ function block_get_blocks_by_region($reg unset($block->content); $build[$key] += array( '#block' => $block, + 'links' => array( + '#type' => 'attached_links', + '#links' => array( + 'configure' => array( + 'href' => 'admin/structure/block/configure/' . $block->module . '/' . $block->delta, + 'title' => !empty($block->subject) ? t('Configure the @block block', array('@block' => drupal_strtolower($block->subject))) : t('Configure this block'), + ), + ), + ), '#weight' => ++$weight, ); + // Special exception for the main system content block: it's weird and + // confusing to show admin links for it - what exactly is the user + // configuring? + if ($block->module == 'system' && $block->delta == 'main') { + unset($build[$key]['#attached_links']['configure']); + } $build[$key]['#theme_wrappers'][] ='block'; } $build['#sorted'] = TRUE; @@ -768,6 +783,22 @@ function block_flush_caches() { function template_preprocess_block(&$variables) { $block_counter = &drupal_static(__FUNCTION__, array()); $variables['block'] = $variables['elements']['#block']; + + // Create the $content variable that templates expect. If the block is + // empty, do not show it. + $variables['content'] = $variables['elements']['#children']; + if (empty($variables['content'])) { + // TODO: This is necessary to match the behavior in _block_render_blocks() + // when $block->content contains HTML output rather than a structured + // array. The inconsistency is ugly, though. It also means that the array + // of blocks per region inserted into the page via block_page_alter() can + // contain a larger number of blocks than actually will display on the + // page. (This part actually might not be bad, though; it means that other + // modules have a chance to insert content into these "empty" blocks and + // therefore make them appear after all, if they want to.) + return FALSE; + } + // All blocks get an independent counter for each region. if (!isset($block_counter[$variables['block']->region])) { $block_counter[$variables['block']->region] = 1; @@ -776,9 +807,6 @@ function template_preprocess_block(&$var $variables['block_zebra'] = ($block_counter[$variables['block']->region] % 2) ? 'odd' : 'even'; $variables['block_id'] = $block_counter[$variables['block']->region]++; - // Create the $content variable that templates expect. - $variables['content'] = $variables['elements']['#children']; - $variables['classes_array'][] = 'block-' . $variables['block']->module; $variables['template_files'][] = 'block-' . $variables['block']->region; Index: modules/block/block.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.tpl.php,v retrieving revision 1.4 diff -u -p -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 25 Sep 2009 11:39:00 -0000 @@ -30,12 +30,22 @@ * - $logged_in: Flags true when the current user is a logged-in member. * - $is_admin: Flags true when the current user is an administrator. * + * Admin links variables: + * - $has_admin_lins: TRUE when the block is editable by the current user. + * - $attached_links: Already-themed link(s) for admin links that may be taken on the + * block; may be empty. + * * @see template_preprocess() * @see template_preprocess_block() * @see template_process() */ ?>
> + + + + + subject): ?> >subject ?> Index: modules/menu/menu.module =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v retrieving revision 1.204 diff -u -p -u -p -r1.204 menu.module --- modules/menu/menu.module 18 Sep 2009 00:04:22 -0000 1.204 +++ modules/menu/menu.module 25 Sep 2009 11:39:00 -0000 @@ -157,6 +157,10 @@ function menu_theme() { 'file' => 'menu.admin.inc', 'arguments' => array('title' => NULL, 'name' => NULL, 'description' => NULL), ), + 'menu_editable' => array( + 'arguments' => array('element' => NULL), + 'template' => 'menu-editable', + ), ); } @@ -301,6 +305,32 @@ function menu_block_view($delta = '') { } /** + * Implement hook_page_alter(). + */ +function menu_page_alter(&$page) { + // Search for every non-empty menu block on the page. + // TODO: This works, but cries out for something like hook_block_alter()... + foreach (element_children($page) as $region) { + $blocks = &$page[$region]; + foreach (element_children($blocks) as $id) { + if (isset($blocks[$id]['#theme_wrappers']) && isset($blocks[$id]['#block'])) { + $block = $blocks[$id]['#block']; + $content = &$blocks[$id]; + if (isset($content['#theme_wrappers']) && in_array('menu_tree', $content['#theme_wrappers'])) { + // If we have a non-empty menu tree, wrap it in a theme function + // that will display an edit link. + $content['#theme_wrappers'] += array('menu_editable'); + $content['links']['#links']['edit'] = array( + 'href' => 'admin/structure/menu-customize/' . $block->delta, + 'title' => t('Edit the @menu menu', array('@menu' => drupal_strtolower($content['#block']->subject))), + ); + } + } + } + } +} + +/** * Implement hook_node_insert(). */ function menu_node_insert($node) { @@ -496,3 +526,12 @@ function menu_get_menus($all = TRUE) { return $query->execute()->fetchAllKeyed(); } + + +/** + * Process variables for menu-editable.tpl.php. + */ +function template_preprocess_menu_editable(&$variables) { + // Create the $content variable that templates expect. + $variables['content'] = $variables['element']['#children']; +} Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1131 diff -u -p -u -p -r1.1131 node.module --- modules/node/node.module 23 Sep 2009 10:41:55 -0000 1.1131 +++ modules/node/node.module 25 Sep 2009 11:39:00 -0000 @@ -1034,6 +1034,19 @@ function node_build($node, $build_mode = $build += array( '#theme' => 'node', '#node' => $node, + '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.22 diff -u -p -u -p -r1.22 node.tpl.php --- modules/node/node.tpl.php 11 Sep 2009 06:48:03 -0000 1.22 +++ modules/node/node.tpl.php 25 Sep 2009 11:39:00 -0000 @@ -44,6 +44,15 @@ * teaser listings. * - $id: Position of the node. Increments each time it's output. * + * Admin links variables: + * - $has_attached_links: TRUE when the node is editable by the current user. + * - $attached_links: Already-themed links to the admin links pages for the node; may + * be empty. + * - $attached_links_text: An array of caption for the admin links of the node; + * may be empty. + * - $attached_links_info: An array of information describing the links to take + * action on the node; may be empty. + * * Node status variables: * - $build_mode: Build mode, e.g. 'full', 'teaser'... * - $teaser: Flag for the teaser state (shortcut for $build_mode == 'teaser'). @@ -74,6 +83,10 @@ + + + + > Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.795 diff -u -p -u -p -r1.795 system.module --- modules/system/system.module 22 Sep 2009 15:26:46 -0000 1.795 +++ modules/system/system.module 25 Sep 2009 11:39:00 -0000 @@ -460,6 +460,17 @@ function system_element_info() { '#theme' => array('hidden'), ); + // Attached links + $types['attached_links'] = array( + '#theme' => 'links', + '#pre_render' => array('edit_mode_pre_render_links'), + '#attributes' => array('class' => 'attached-links'), + '#attached' => array( + 'js' => 'misc/attached_links.js', + 'css' => 'misc/attached_links.css', + ), + ); + return $types; } @@ -503,6 +514,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_page', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['admin'] = array( 'title' => 'Administer', 'access arguments' => array('access administration pages'), @@ -1306,6 +1325,13 @@ function blocked_ip_load($iid) { } /** + * Menu item title callback. + */ +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) { @@ -2686,6 +2712,20 @@ function system_timezone($abbreviation = } /** + * Menu callback; toggle the global edit mode and redirect. + */ +function edit_mode_toggle_page() { + 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 @@ -2885,3 +2925,68 @@ function theme_system_run_cron_image($im return ''; } +/** + * Pre-render function for attached_links. + */ +function edit_mode_pre_render_links(&$element) { + if (empty($_SESSION['edit_mode']) || empty($element['#links'])) { + // Remove this element when not in edit mode. + $element = array(); + return; + } + + $has_links = FALSE; + foreach ($element['#links'] as $key => $data) { + if (!isset($data['access']) && function_exists('menu_get_item')) { + $item = menu_get_item($data['href']); + $element['#links'][$key]['access'] = !empty($item['access']); + } + $has_links = $has_links || !empty($element['#links'][$key]['access']); + } + + // Do not show edit links when the user is already on the page that is + // being linked to. + $show_links = FALSE; + if ($has_links && isset($_GET['q'])) { + foreach ($element['#links'] as $key => $data) { + $show_links = $show_links || ($data['href'] != $_GET['q']); + } + } + + /*// Add appropriate CSS classes that define this element as editable and + // associate it with its edit link. + // @todo expose $classes_array as actual variables. + if ($has_links) { + $variables['classes_array'][] = 'attached-links-enabled-region'; + foreach ($links as $key => $data) { + $variables['classes_array'][] = 'attached-links-enabled-at-' . str_replace('/', '-', $data['href']); + } + }*/ + + if ($show_links) { + $text_items = array(); + $info_items = array(); + foreach ($element['#links'] as $key => $data) { + $check_plain = isset($data['attributes']['html']) ? !$data['attributes']['html'] : TRUE; + $text_items[$key] = $check_plain ? check_plain($data['title']) : $data['title']; + $info_items["attached-links-link attached-links-$key-link attached-links-at-" . str_replace('/', '-', $data['href'])] = array( + 'title' => ' ', + 'href' => $data['href'], + 'html' => TRUE, + // Refer users back to the current page after they have completed + // their edits. + 'query' => drupal_get_destination(), + 'attributes' => array( + 'class' => 'attached-links-link attached-links-link-' . $key, + 'title' => $text_items[$key], + ), + ); + } + // Replace the links data with the recomputed items. + $element['#links'] = $info_items; + } + else { + $element['#links'] = array(); + } + +} Index: modules/toolbar/toolbar.install =================================================================== RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.install,v retrieving revision 1.4 diff -u -p -u -p -r1.4 toolbar.install --- modules/toolbar/toolbar.install 31 Aug 2009 17:09:01 -0000 1.4 +++ modules/toolbar/toolbar.install 25 Sep 2009 11:39:00 -0000 @@ -28,7 +28,10 @@ function toolbar_install() { 'node/add' => 'Add content', 'admin/content' => 'Find content', 'admin' => 'Dashboard', + // No title, so the title callback prevails. + 'edit-mode/toggle' => '', ); + $weight = -20; foreach ($items as $path => $title) { $link = array( @@ -37,7 +40,11 @@ function toolbar_install() { 'link_path' => $path, 'router_path' => $path, 'menu_name' => 'admin_shortcuts', - 'module' => 'menu', + // Saved as links with the toolbar module which makes them impossible + // to delete on the user interface. This is important for items like + // the edit toggle which cannot be figured out easily from elsewhere. + // The items can be disabled still of course. + 'module' => 'toolbar', 'weight' => $weight, ); Index: modules/toolbar/toolbar.module =================================================================== RCS file: /cvs/drupal/drupal/modules/toolbar/toolbar.module,v retrieving revision 1.13 diff -u -p -u -p -r1.13 toolbar.module --- modules/toolbar/toolbar.module 15 Sep 2009 20:50:48 -0000 1.13 +++ modules/toolbar/toolbar.module 25 Sep 2009 11:39:00 -0000 @@ -151,8 +151,12 @@ function toolbar_menu_navigation_links($ $link['href'] = $item['link']['href']; // Add icon placeholder. $link['title'] = '' . $item['link']['title']; - // Add admin link ID and to-overlay class for the overlay. - $link['attributes'] = array('id' => 'toolbar-link-' . $id, 'class' => array('to-overlay')); + $classes = array(); + if ($link['href'] != 'edit-mode/toggle') { + $classes = array('class' => array('to-overlay')); + } + // Add admin link ID and optionaly the to-overlay class for the overlay. + $link['attributes'] = array('id' => 'toolbar-link-' . $id) + $classes; $link['html'] = TRUE; $class = ' path-' . $id; Index: themes/garland/block.tpl.php =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/block.tpl.php,v retrieving revision 1.9 diff -u -p -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 25 Sep 2009 11:39:00 -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.16 diff -u -p -u -p -r1.16 node.tpl.php --- themes/garland/node.tpl.php 11 Sep 2009 06:48:03 -0000 1.16 +++ themes/garland/node.tpl.php 25 Sep 2009 11:39:00 -0000 @@ -3,6 +3,10 @@ ?>
> + + + + Index: themes/garland/style.css =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/style.css,v retrieving revision 1.64 diff -u -p -u -p -r1.64 style.css --- themes/garland/style.css 31 Aug 2009 17:40:03 -0000 1.64 +++ themes/garland/style.css 25 Sep 2009 11:39:00 -0000 @@ -88,7 +88,7 @@ hr { background: #5294c1; } -ul, .block ul, ol { +ul, .block ul.menu, ol { margin: 0.5em 0 1em; padding: 0 0 0 1.5em; /* LTR */ } @@ -290,6 +290,10 @@ table .form-button, table .form-submit { margin: 0; } +.edit-link { + font-size: 0.8em; +} + /** * Layout */ @@ -650,8 +654,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 +813,29 @@ tr.even td.menu-disabled { } /** + * Edit icons. + */ +.attached-links-enabled-region { + outline: none; +} + +.active-attached-links-region { + outline: dashed #027AC6 2px; +} + +ul.attached-links li a { + text-decoration: none; +} + +ul.attached-links li a span { + background-color: #027AC6; +} + +ul.attached-links li a:hover span { + background-color: #0062A0; +} + +/** * Collapsible fieldsets */ fieldset {