diff -urpN admin_menu/admin_menu.css admin_menu/admin_menu.css --- admin_menu/admin_menu.css 2008-11-03 08:36:22.000000000 +1100 +++ admin_menu/admin_menu.css 2008-12-16 12:17:46.000000000 +1100 @@ -101,3 +101,12 @@ html.js fieldset.collapsible div.fieldse #admin-menu { display: none; } body.admin-menu { margin-top: 0 !important; } } + +/** + * Admin Menu settings page + */ +#admin-menu-theme-settings .inline-element, +#admin-menu-theme-settings .inline-element .form-item +{ + display: inline; +} diff -urpN admin_menu/admin_menu.inc admin_menu/admin_menu.inc --- admin_menu/admin_menu.inc 2008-12-04 09:53:31.000000000 +1100 +++ admin_menu/admin_menu.inc 2008-12-10 13:47:19.000000000 +1100 @@ -372,6 +372,7 @@ function admin_menu_theme_settings() { '#default_value' => variable_get('admin_menu_tweak_tabs', 0), '#description' => t('If enabled, the tabs on the current page are moved into the administration menu. This feature is only available in themes that use the CSS classes tabs primary and tabs secondary for tabs.'), ); + $form['tweaks'] += admin_menu_tweak('settings'); $form = system_settings_form($form); $form['wipe description'] = array( '#type' => 'item', diff -urpN admin_menu/admin_menu.js admin_menu/admin_menu.js --- admin_menu/admin_menu.js 2008-11-03 08:36:22.000000000 +1100 +++ admin_menu/admin_menu.js 2008-12-16 13:28:34.000000000 +1100 @@ -1,31 +1,73 @@ /* $Id: admin_menu.js,v 1.7.2.6 2008/11/02 21:36:22 sun Exp $ */ -$(document).ready(function() { - // Apply margin-top if enabled; directly applying marginTop doesn't work in IE. - if ($('#admin-menu').size()) { - if (Drupal.settings.admin_menu.margin_top) { - $('body').addClass('admin-menu'); - } - if (Drupal.settings.admin_menu.position_fixed) { - $('#admin-menu').css('position', 'fixed'); - } - // Move page tabs into administration menu. - if (Drupal.settings.admin_menu.tweak_tabs) { - $('ul.tabs.primary li').each(function() { - $(this).addClass('admin-menu-tab').appendTo('#admin-menu > ul'); - }); - $('ul.tabs.secondary').appendTo('#admin-menu > ul > li.admin-menu-tab.active'); - } +Drupal.adminMenu = Drupal.adminMenu || {}; + +/** + * Core behavior for Administration menu. + * + * This tests whether there is an administration menu is in the output and + * executes all registered behaviors. + */ +Drupal.behaviors.adminMenu = function (context) { + var $adminMenu = $('#admin-menu'); + if ($adminMenu.size()) { + jQuery.each(Drupal.adminMenu, function() { + this(context, $adminMenu); + }); } +}; - // Collapse fieldsets on Modules page. For why multiple selectors see #111719. +/** + * Collapse fieldsets on Modules page. + * + * For why multiple selectors see #111719. + */ +Drupal.behaviors.adminMenuCollapseModules = function (context) { if (Drupal.settings.admin_menu.tweak_modules) { - $('#system-modules fieldset:not(.collapsed), #system-modules-1 fieldset:not(.collapsed)').addClass('collapsed'); + $('#system-modules fieldset:not(.collapsed), #system-modules-1 fieldset:not(.collapsed)', context).addClass('collapsed'); + } +}; + +/** + * Apply 'margin-top'; directly applying marginTop does not work in IE. + */ +Drupal.adminMenu.marginTop = function (context, $adminMenu) { + if (Drupal.settings.admin_menu.margin_top) { + $('body', context).addClass('admin-menu'); + } +}; + +/** + * Apply 'position: fixed'. + */ +Drupal.adminMenu.positionFixed = function (context, $adminMenu) { + if (Drupal.settings.admin_menu.position_fixed) { + $adminMenu.css('position', 'fixed'); } +}; +/** + * Move page tabs into administration menu. + */ +Drupal.adminMenu.pageTabs = function (context, $adminMenu) { + if (Drupal.settings.admin_menu.tweak_tabs) { + $('ul.tabs.primary li', context).each(function() { + $(this).addClass('admin-menu-tab').appendTo('#admin-menu > ul'); + }); + $('ul.tabs.secondary').appendTo('#admin-menu > ul > li.admin-menu-tab.active'); + } +}; + +/** + * Apply JavaScript-based hovering behaviors. + * + * @todo This has to run last. If another script registers additional behaviors + * it will not run last. + */ +Drupal.adminMenu.hover = function (context, $adminMenu) { // Hover emulation for IE 6. if ($.browser.msie && parseInt(jQuery.browser.version) == 6) { - $('#admin-menu li').hover(function() { + $('li', $adminMenu).hover(function() { $(this).addClass('iehover'); }, function() { $(this).removeClass('iehover'); @@ -33,7 +75,7 @@ $(document).ready(function() { } // Delayed mouseout. - $('#admin-menu li').hover(function() { + $('li', $adminMenu).hover(function() { // Stop the timer. clearTimeout(this.sfTimer); // Display child lists. @@ -47,4 +89,65 @@ $(document).ready(function() { uls.css({left: '-999em', display: 'none'}); }, 400); }); -}); +}; + +/** + * Modify Admin Menu settings page for visbility tweak settings. + */ +Drupal.adminMenu.visbility_settings = function() { + if ($('#edit-admin-menu-tweak-visibility-mode').length) { + // Get active visiibility tweak + active = $('#edit-admin-menu-tweak-visibility-mode option:selected').get(0).value; + + // Hide inactive visbility tweak settings + $('.admin_menu_visibility_settings').each(function() { + if (this.id.substr(31) != active) { + $(this).hide(); + } + }); + + // Toggle visbility tweak settings on change + $('#edit-admin-menu-tweak-visibility-mode').change(function() { + old = active; + active = $('#edit-admin-menu-tweak-visibility-mode option:selected').get(0).value; + + if ($('#admin_menu_visibility_settings-'+ old).length) { + $('#admin_menu_visibility_settings-'+ old).hide(); + } + + if ($('#admin_menu_visibility_settings-'+ active).length) { + $('#admin_menu_visibility_settings-'+ active).show(); + } + }); + } +} + +/** + * Apply visibility tweak. + */ +Drupal.adminMenu.visibility = function() { + // Initialize defined visibility tweak + eval(Drupal.settings.admin_menu.tweak_visibility.mode +'.init()'); + + // Set initial visibility status + if (Drupal.settings.admin_menu.tweak_visibility.hidden == 1) { + Drupal.adminMenu_visibility.hide(); + } +} + +/** + * Visibility tweak JS API. + */ +Drupal.adminMenu_visibility = { + hide: function() { + $.getJSON("/admin_menu/js/visibility/hide"); + Drupal.settings.admin_menu.tweak_visibility.hidden = 1; + eval(Drupal.settings.admin_menu.tweak_visibility.mode +'.hide()'); + }, + + show: function() { + $.getJSON("/admin_menu/js/visibility/show"); + Drupal.settings.admin_menu.tweak_visibility.hidden = 0; + eval(Drupal.settings.admin_menu.tweak_visibility.mode +'.show()'); + } +} diff -urpN admin_menu/admin_menu.module admin_menu/admin_menu.module --- admin_menu/admin_menu.module 2008-12-04 10:03:34.000000000 +1100 +++ admin_menu/admin_menu.module 2008-12-10 14:10:00.000000000 +1100 @@ -70,6 +70,11 @@ function admin_menu_menu() { 'type' => MENU_CALLBACK, 'file' => 'admin_menu.inc', ); + $items['admin_menu/js'] = array( + 'page callback' => 'admin_menu_js', + 'access arguments' => array('access administration menu'), + 'type' => MENU_CALLBACK, + ); return $items; } @@ -87,6 +92,7 @@ function admin_menu_init() { drupal_add_css($path .'/admin_menu.css', 'module', 'all', FALSE); // Performance: Defer execution. drupal_add_js($path .'/admin_menu.js', 'module', 'header', TRUE); + module_load_include('inc', 'admin_menu', 'admin_menu_tweaks'); if ($setting = variable_get('admin_menu_margin_top', 1)) { drupal_add_js(array('admin_menu' => array('margin_top' => $setting)), 'setting'); @@ -100,6 +106,7 @@ function admin_menu_init() { if ($_GET['q'] == 'admin/build/modules') { drupal_add_js(array('admin_menu' => array('tweak_modules' => variable_get('admin_menu_tweak_modules', 0))), 'setting'); } + admin_menu_tweak('init'); } } diff -urpN admin_menu/admin_menu_tweaks.inc admin_menu/admin_menu_tweaks.inc --- admin_menu/admin_menu_tweaks.inc 1970-01-01 10:00:00.000000000 +1000 +++ admin_menu/admin_menu_tweaks.inc 2008-12-16 13:25:21.000000000 +1100 @@ -0,0 +1,213 @@ + $tweak) { + // Standalone tweaks + if ($tweak['#type'] != 'visibility') { + $form['admin_menu_tweak_'. $name] = array( + '#type' => 'checkbox', + '#title' => $tweak['#title'], + '#description' => $tweak['#description'], + '#default_value' => variable_get('admin_menu_tweak_'. $name, 0), + ); + } + + // Visibility tweaks + else { + $behavior[$name] = $tweak['#title']; + + if (isset($tweak['#form'])) { + $visibility['admin_menu_tweak_visibility_'. $name] = array( + '#type' => 'markup', + '#prefix' => '
', + '#suffix' => '
', + ); + $visibility['admin_menu_tweak_visibility_'. $name] += call_user_func($tweak['#form']); + } + } + } + + if (isset($behavior)) { + $form['admin_menu_tweak_visibility'] = array( + '#type' => 'fieldset', + '#title' => 'Visibility settings', + ); + $form['admin_menu_tweak_visibility']['admin_menu_tweak_visibility'] = array( + '#type' => 'checkbox', + '#title' => t('Hidden by default'), + '#default_value' => variable_get('admin_menu_tweak_visibility', 1), + '#description' => t('If checked the Administration menu is hidden by default'), + ); + $form['admin_menu_tweak_visibility']['admin_menu_tweak_visibility_mode'] = array( + '#type' => 'select', + '#title' => t('Admin Menu visibility behavior'), + '#options' => $behavior, + '#default_value' => variable_get('admin_menu_tweak_visibility_mode', 'key_combo'), + ); + + if (isset($visibility)) { + $form['admin_menu_tweak_visibility'] += $visibility; + } + } + + return $form; + break; + + case 'init': + foreach ($tweaks as $name => $tweak) { + // Standalone tweaks + if ($tweak['#type'] != 'visibility' && $setting = variable_get('admin_menu_tweak_'. $name, 0) && isset($tweak['#file'])) { + drupal_add_js($tweak['#file'], 'module'); + } + } + + // Load visibility tweaks custom settings + if (isset($tweaks[variable_get('admin_menu_tweak_visibility_mode', 'key_combo')]['#settings'])) { + $settings = call_user_func($tweaks[variable_get('admin_menu_tweak_visibility_mode', 'key_combo')]['#settings']); + drupal_add_js(array('admin_menu' => array('tweak_visibility' => array('settings' => $settings))), 'setting'); + } + + drupal_add_js(array('admin_menu' => array('tweak_visibility' => array( + 'hidden' => isset($_SESSION['admin_menu_visibility']) + ? $_SESSION['admin_menu_visibility'] + : variable_get('admin_menu_tweak_visibility', 1) + , + 'mode' => variable_get('admin_menu_tweak_visibility_mode', 'key_combo'), + ))), 'setting'); + drupal_add_js($tweaks[variable_get('admin_menu_tweak_visibility_mode', 'key_combo')]['#file'], 'module'); + + break; + } +} + +/** + * AJAX handler for Admin Menu JS API + */ +function admin_menu_js() { + $args = func_get_args(); + + switch ($args[0]) { + case 'visibility': + $_SESSION['admin_menu_visibility'] = $args[1] == 'hide' ? 1 : 0; + print drupal_json($_SESSION['admin_menu_visibility']); + break; + } + + exit; +} + +/** + * Implementation of hook_admin_menu_tweak(). + */ +function admin_menu_admin_menu_tweak() { + $tweak['navigation'] = array( + '#type' => NULL, + '#title' => t('Keyboard navigation'), + '#description' => t('If enabled, the administration menu can be navigated using the keyboard. To toggle keyboard controls, hit the ESC button.'), + '#file' => drupal_get_path('module', 'admin_menu') .'/js/navigation.js', + ); + + $tweak['key_combo'] = array( + '#type' => 'visibility', + '#title' => t('Key combo'), + '#description' => t('description.'), + '#file' => drupal_get_path('module', 'admin_menu') .'/js/visibility.js', + '#form' => 'admin_menu_tweak_key_combo_form', + '#settings' => 'admin_menu_tweak_key_combo_settings', + ); + + $tweak['icon_hotspot'] = array( + '#type' => 'visibility', + '#title' => t('Icon hotspot'), + '#description' => t('description.'), + '#file' => drupal_get_path('module', 'admin_menu') .'/js/visibility.js', + ); + + return $tweak; +} + +function admin_menu_tweak_key_combo_form() { + $form['admin_menu_tweak_visibility_key_combo_modifier'] = array( + '#type' => 'select', + '#title' => t('Display modifier + key'), + '#options' => admin_menu_tweak_key_combos('modifier'), + '#default_value' => variable_get('admin_menu_tweak_visibility_key_combo_modifier', 'ctrl + alt'), + '#prefix' => '
', + '#suffix' => '
', + ); + $form['admin_menu_tweak_visibility_key_combo_key'] = array( + '#type' => 'select', + '#options' => admin_menu_tweak_key_combos('key'), + '#default_value' => variable_get('admin_menu_tweak_visibility_key_combo_key', ''), + '#description' => t('Key combination to show/hide Administration Menu.'), + '#prefix' => '
+ ', + '#suffix' => '
', + ); + + return $form; +} + +function admin_menu_tweak_key_combo_settings() { + return array( + 'combo' => variable_get('admin_menu_tweak_visibility_key_combo_modifier', 'ctrl + alt') . + (variable_get('admin_menu_tweak_visibility_key_combo_key', '') ? " + ". variable_get('admin_menu_tweak_visibility_key_combo_key', '') : '') ."; " + ); +} + +function admin_menu_tweak_key_combos($type) { + switch ($type) { + case 'key': + $options = array( + '' => 'None', + '65' => 'A', + '66' => 'B', + '67' => 'C', + '68' => 'D', + '69' => 'E', + '70' => 'F', + '71' => 'G', + '72' => 'H', + '73' => 'I', + '74' => 'J', + '75' => 'K', + '76' => 'L', + '77' => 'M', + '78' => 'N', + '79' => 'O', + '80' => 'P', + '81' => 'Q', + '82' => 'R', + '83' => 'S', + '84' => 'T', + '85' => 'U', + '86' => 'V', + '87' => 'W', + '88' => 'X', + '89' => 'Y', + '90' => 'Z', + ); + break; + + case 'modifier': + $options = array( + 'ctrl + alt' => 'ctrl + alt', + 'ctrl + shift' => 'ctrl + shift', + 'ctrl + alt + shift' => 'ctrl + alt + shift' + ); + break; + } + + return $options; +} diff -urpN admin_menu/js/navigation.js admin_menu/js/navigation.js --- admin_menu/js/navigation.js 1970-01-01 10:00:00.000000000 +1000 +++ admin_menu/js/navigation.js 2008-12-10 12:30:38.000000000 +1100 @@ -0,0 +1,252 @@ +// Keyboard navigation. +var tree, active = false, current; + +/** + * Initialize function, caches the DOM tree in an JavaScript object. + */ + +var createTree = function() { + + /** + * Cache menu's DOM as an JavaScript Object. This improves dramaticaly the + * navigation performance, cause we are not required to travers the DOM while + * each keypress instead we just travers the js object later. + * + * @param HTMLListElement element + * An HTMLListElement object containing the current LI Element + * @param integer index + * An number inidicating serial index + * @return + * This is the current structure of the return object + * { + * li: current li element + * a: current element's first A element + * children: recursively collected array of current element's children + * index: serial number of current element + * uls: immediate descendant ul + * } + */ + + var collectElements = function (element, index) { + // Cache creation of jQuery object + var _this = $(element); + + // Set default tree object + var tree = { li: _this, a: _this.find('> a'), index: index }; + + // Check whether this element has children + var children; + if ((children = _this.find('> ul > li')).length) { + // Yes, we have children, let us collect all children elements recursively + tree.children = $.map(children, collectElements); + } + return tree; + }; + + tree = $.map($('#admin-menu > ul > li.expandable'), collectElements); + + /** + * Walks through the tree object and creates parents references on the fly. + * No return is required because we are working with object references. + * + * @param array array + * Array with elements for looping through + * @param object parent + * Refrence to the parent object + * @param interger parentIndex + * Specifies the position of parent in array + * @param interger level + * Current element depth + */ + var setParents = function (array, parent, parentIndex, level) { + // fallback level on initialization + var level = level || 1; + + for (var i = 0; i < array.length; i++) { + // Chache object. + var object = array[i]; + + // Set current depth. + object.level = level; + + // Create reference to the parent object. + if (parent) object.parent = parent; + + // Set parent's position in one's array. + object.parentIndex = parentIndex || i; + + // Do we have children? If yes go lets apply the same on our children. + if (object.children) { + arguments.callee(object.children, object, i, level + 1); + } + } + }; + + // Trigger the setParents function + setParents(tree); +}; + +// Attach the keydown trigger function on the document. +// This is on place where we could pick off the keydown event in all browsers. +$(document).bind('keydown', function(e) { + event = (e) ? e : window.event; + + // Workaround for altKey bug in jQuery >= 1.2.5 + event = event.originalEvent ? event.originalEvent : event; + + // Cache keyCode + var key = event.keyCode; + + // If not left, top, right, down or escape is pressed. Break here. + if (!(key == 37 || key == 38 || key == 39 || key == 40 || key == 27)) { + return true; + } + + // Does somebody pressed the escape key? + if (key == 27) { + + // Toggle the active state + active = !active; + + // If the keyboard navigation is not active anymore. + if (!active) { + // Find all active tabs and close them + $('#admin-menu li.expandable').trigger('mouseleave'); + + // Color mark should be erased, + current.li.removeClass('focused'); + + // Blur the current A element. + current.a.blur(); + + // Set current to undefined, so on next initialize + // the current element would be picked again. + current = undefined; + + // Cancel event's propaganation. + return false; + } + } + + // If the menu is not active, break here. + if (!active) { + return false; + } + + // If the tree is not initializated, create. + if(!tree) { + createTree(); + } + + // Reset first element on initialization. + if (!current) { + current = tree[1] || tree[0]; + } + + var next; + + switch (key) { + case 37: // Left + // Do we have first level here and can we go to the previous element? + if (current.level == 1 && tree[current.parentIndex - 1]) { + // Yes, our next selection is current's previous element. + next = tree[current.parentIndex - 1]; + } + // Handle third and deeper levels. + else if (current.level > 2) { + // Our next element is our current parent. + next = current.parent; + } + // If somebody presed left in the second level. + else if(current.parent) { + // Go to the parents previous element. + next = tree[current.parent.parentIndex - 1]; + } + break; + + case 38: // Up + // We have first level. + if (current.level == 1) { + // Next is our last child. + next = current.children[current.children.length - 1]; + } + // We are at the second level and we are not on the top. + else if (!(current.level == 2 && current.index == 0)) { + // Select previous element. + next = current.parent.children[current.index - 1]; + } + // Second level an we are at the top + else if (current.level == 2 && current.index == 0) { + // Select our parent, cause parent is visualy more at the top. + next = current.parent; + } + break; + + case 39: // Right + // First level? + if (current.level == 1) { + // Next element is set? + if(tree[current.parentIndex + 1]) { + next = tree[current.parentIndex + 1]; + } + } + // Handle third level and deeper. Do we have chidlren? + else if (current.children) { + // Select our first child. + next = current.children[0]; + } + // Are we at the second level? + else if (current.level == 2) { + // Select visualy next element. + next = tree[current.parent.parentIndex + 1]; + } + break; + + case 40: // Down + // Do we are at the first level and we have children? + if (current.level == 1 && tree[current.parentIndex].children) { + // Get first level element's first child. + next = tree[current.parentIndex].children[0]; + } + else { + // Just select our previous element. + next = current.parent.children[current.index + 1]; + } + break; + } + + // Fallback to current if next is not set. + if (!next) { + next = current; + } + + // Remove previous mark. + current.li.removeClass('focused'); + + // Do we go up or just have children and are at the same level? + if ((next.level < current.level) || (!next.children && next.level == current.level)) { + // Hid not required elements. + current.li.trigger('mouseleave'); + } + + // Set current element mark. + next.li.addClass('focused'); + + // Activate the enter key + next.a.focus(); + + // Show descendant ul + next.li.trigger('mouseenter'); + + // Save next object, so we could fall back on this next time + current = next; + + // Cancel event propagation, in all browsers. + event.preventDefault(); + event.cancelBubble = true; + if (event.stopPropagation) { + event.stopPropagation(); + } + + return false; +}); diff -urpN admin_menu/js/visibility.js admin_menu/js/visibility.js --- admin_menu/js/visibility.js 1970-01-01 10:00:00.000000000 +1000 +++ admin_menu/js/visibility.js 2008-12-16 11:56:43.000000000 +1100 @@ -0,0 +1,63 @@ +/** + * Key Combo visibility tweak. + */ +key_combo = { + init: function() { + $(document).keydown(function(e) { + evt = (e) ? e : window.event; + // Workaround for altKey bug in jQuery >= 1.2.5 + evt = evt.originalEvent ? evt.originalEvent : evt; + + combo = eval(Drupal.settings.admin_menu.tweak_visibility.settings.combo.replace(/\+/g, '&&').replace(/([a-z]+)/g, "evt.$1Key").replace(/([0-9]+)/g, "evt.keyCode == $1")); + + if (combo && Drupal.settings.admin_menu.tweak_visibility.hidden) { + Drupal.adminMenu_visibility.show(); + } + else if (combo) { + Drupal.adminMenu_visibility.hide(); + } + }); + }, + + hide: function() { + $('#admin-menu').hide(); + }, + + show: function() { + $('#admin-menu').show(); + } +} + +/** + * Icon Hotspot visibility tweak. + */ +icon_hotspot = { + init: function() { + $('#admin-menu').css({height: '21px'}); + $('#admin-menu ul:first li.admin-menu-icon:first a:first') + .css({padding: '1px 4px 4px'}) + .attr({href: '#', title: 'Click here to toggle administration menu visibility!'}) + .click(function() { + if (Drupal.settings.admin_menu.tweak_visibility.hidden) { + Drupal.adminMenu_visibility.show(); + } + else { + Drupal.adminMenu_visibility.hide(); + } + + return false; + }); + }, + + hide: function() { + $('#admin-menu') + .width('24px').css({overflow: 'hidden'}) + .children('ul:first').children('li:gt(0)').hide(); + }, + + show: function() { + $('#admin-menu') + .width('100%').css({overflow: 'visible'}) + .children('ul:first').children('li:gt(0)').show(); + } +}