Index: profiles/default/default.install =================================================================== --- profiles/default/default.install (revision 1266) +++ profiles/default/default.install (working copy) @@ -218,5 +218,4 @@ function default_install() { ->condition('name', 'seven') ->execute(); variable_set('admin_theme', 'seven'); - variable_set('node_admin_theme', '1'); } Index: profiles/default/default.info =================================================================== --- profiles/default/default.info (revision 1266) +++ profiles/default/default.info (working copy) @@ -14,6 +14,7 @@ dependencies[] = taxonomy dependencies[] = dblog dependencies[] = search dependencies[] = toolbar +dependencies[] = overlay dependencies[] = field_ui dependencies[] = file files[] = default.profile Index: themes/seven/style.css =================================================================== --- themes/seven/style.css (revision 1266) +++ themes/seven/style.css (working copy) @@ -55,7 +55,7 @@ legend { */ #branding { overflow: hidden; - padding: 20px 40px 0 40px; + padding: 20px 20px 0 20px; position: relative; background-color: #e0e0d8; } @@ -115,7 +115,7 @@ legend { */ #console div.messages { padding: 9px; - margin: 1em 0; + margin: 0.5em 0 0; color: #036; background: #bdf; border: 1px solid #ace; @@ -158,9 +158,8 @@ legend { /** * Console. */ -#page .console { - border-top: 1px solid #ccc; - padding: 9px 0 10px; +#console { + margin: 9px 0 10px; } /** @@ -618,9 +617,14 @@ html.js input.throbbing { ul.action-links { margin: 1em 0; + padding: 0 20px 0 20px; overflow: hidden; } +#block-system-main ul.action-links { + padding: 0; +} + ul.action-links li { float: left; margin: 0 1em 0 0; @@ -716,22 +720,32 @@ div.admin-options div.form-item { } /* Overlay theming */ -body.overlay { - background: #fff; +.overlay #branding { + background-color: #fff; + padding-top: 0.3em; } - -body.overlay #branding, -body.overlay #page-title, -body.overlay #page #left, -body.overlay #page #footer { +.overlay .primary, +.overlay #branding h1.page-title, +.overlay #page #left, +.overlay #page #footer { display: none; } - -body.overlay #page { +.overlay #page { margin: 0; +} +.overlay #branding div.breadcrumb { + float: left; + position: relative; + z-index: 10; +} +.overlay ul.secondary { + background: transparent none; + margin: -2.4em 0 0; + padding: 3px 10px; +} +.overlay #content { + padding: 0 20px; +} +.overlay #block-system-main { padding: 0; } - -body.overlay #block-system-main { - padding: 20px; -} Index: themes/garland/style.css =================================================================== --- themes/garland/style.css (revision 1266) +++ themes/garland/style.css (working copy) @@ -508,6 +508,15 @@ body.two-sidebars .region-footer { font-size: 1.5em; } +/* Don't display any header elements when within the overlay, and adjust the page height accordingly. */ +body.overlay #header * { + display: none; +} + +body.overlay { + margin-top: -80px; +} + #wrapper #container #header h1 a:hover { text-decoration: none; } Index: misc/tableheader.js =================================================================== --- misc/tableheader.js (revision 1266) +++ misc/tableheader.js (working copy) @@ -18,8 +18,9 @@ Drupal.behaviors.tableHeader = { var headers = []; $('table.sticky-enabled thead', context).once('tableheader', function () { - // Clone thead so it inherits original jQuery properties. - var headerClone = $(this).clone(true).insertBefore(this.parentNode).wrap('').parent().css({ + // Clone thead so it inherits original jQuery properties. Hide the table + // to avoid a flash of the header clone upon page load. + var headerClone = $(this).clone(true).hide().insertBefore(this.parentNode).wrap('').parent().css({ position: 'fixed', top: '0px' }); @@ -32,6 +33,9 @@ Drupal.behaviors.tableHeader = { headerClone.table = table; // Finish initializing header positioning. tracker(headerClone); + // We hid the header to avoid it showing up erroneously on page load; + // we need to unhide it now so that it will show up when expected. + $(headerClone).children('thead').show(); $(table).addClass('sticky-table'); }); Index: modules/locale/locale.test =================================================================== --- modules/locale/locale.test (revision 1266) +++ modules/locale/locale.test (working copy) @@ -236,7 +236,7 @@ class LocaleTranslationFunctionalTest extends Drup $this->clickLink(t('edit')); // We save the lid from the path. $matches = array(); - preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches); + preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches); $lid = $matches[1]; // No t() here, it's surely not translated yet. $this->assertText($name, t('name found on edit screen.')); Index: modules/overlay/overlay.api.php =================================================================== --- modules/overlay/overlay.api.php (revision 0) +++ modules/overlay/overlay.api.php (revision 0) @@ -0,0 +1,54 @@ +'); + self.iframe.$container = $('
').append(self.iframe.$element); + + $('body').append(self.iframe.$container); + + // Open callback for jQuery UI Dialog. + var dialogOpen = function() { + // Unbind the keypress handler installed by ui.dialog itself. + // IE does not fire keypress events for some non-alphanumeric keys + // such as the tab character. http://www.quirksmode.org/js/keys.html + // Also, this is not necessary here because we need to deal with an + // iframe element that contains a separate window. + // We'll try to provide our own behavior from bindChild() method. + $('.overlay').unbind('keypress.ui-dialog'); + + // Adjust close button features. + $('.overlay .ui-dialog-titlebar-close:not(.overlay-processed)').addClass('overlay-processed') + .attr('href', '#') + .attr('title', Drupal.t('Close')) + .unbind('click') + .bind('click', function() { try { self.close(); } catch(e) {}; return false; }); + + // Replace the title span element with an h1 element for accessibility. + $('.overlay .ui-dialog-title').replaceWith('

' + $('.overlay .ui-dialog-title').html() + '

'); + + // Compute initial dialog size. + var dialogSize = self.sanitizeSize({width: self.options.width, height: self.options.height}); + + // Compute frame size and dialog position based on dialog size. + var frameSize = $.extend({}, dialogSize); + frameSize.height -= $('.overlay .ui-dialog-titlebar').outerHeight(true); + var dialogPosition = self.computePosition($('.overlay'), dialogSize); + + // Adjust size of the iframe element and container. + $('.overlay').width(dialogSize.width).height(dialogSize.height); + self.iframe.$container.width(frameSize.width).height(frameSize.height); + self.iframe.$element.width(frameSize.width).height(frameSize.height); + + // Update the dialog size so that UI internals are aware of the change. + self.iframe.$container.dialog('option', { width: dialogSize.width, height: dialogSize.height }); + + // Hide the dialog, position it on the viewport and then fade it in with + // the frame hidden until the child document is loaded. + self.iframe.$element.hide(); + $('.overlay').hide().css({top: dialogPosition.top, left: dialogPosition.left}); + $('.overlay').fadeIn('fast', function() { + // Load the document on hidden iframe (see bindChild method). + self.load(self.options.url); + }); + + if ($.isFunction(self.options.onOverlayOpen)) { + self.options.onOverlayOpen(self); + } + + self.isOpen = true; + }; + + // Before close callback for jQuery UI Dialog. + var dialogBeforeClose = function() { + if (self.beforeCloseEnabled) { + return true; + } + if (!self.beforeCloseIsBusy) { + self.beforeCloseIsBusy = true; + setTimeout(function() { self.close(); }, 1); + } + return false; + }; + + // Close callback for jQuery UI Dialog. + var dialogClose = function() { + $(document).unbind('keydown.overlay-event'); + $('.overlay .ui-dialog-titlebar-close').unbind('keydown.overlay-event'); + try { + self.iframe.$element.remove(); + self.iframe.$container.dialog('destroy').remove(); + } catch(e) {}; + delete self.iframe.documentSize; + delete self.iframe.Drupal; + delete self.iframe.$element; + delete self.iframe.$container; + if (self.beforeCloseEnabled) { + delete self.beforeCloseEnabled; + } + if (self.beforeCloseIsBusy) { + delete self.beforeCloseIsBusy; + } + self.isOpen = false; + }; + + // Default jQuery UI Dialog options. + var dialogOptions = { + modal: true, + autoOpen: false, + closeOnEscape: true, + resizable: false, + title: Drupal.t('Loading...'), + dialogClass: 'overlay', + zIndex: 500, + open: dialogOpen, + beforeclose: dialogBeforeClose, + close: dialogClose + }; + + // Allow external script override default jQuery UI Dialog options. + $.extend(dialogOptions, self.options.customDialogOptions); + + // Create the jQuery UI Dialog. + self.iframe.$container.dialog(dialogOptions); +}; + +/** + * Load the given URL into the overlay iframe. + * + * Use this method to change the URL being loaded in the overlay if it is + * already open. + */ +Drupal.overlay.load = function(url) { + var self = this; + var iframe = self.iframe.$element.get(0); + // Get the document object of the iframe window. + // @see http://xkr.us/articles/dom/iframe-document/ + var doc = (iframe.contentWindow || iframe.contentDocument); + if (doc.document) { + doc = doc.document; + } + doc.location.replace(url); +}; + +/** + * Check if the dialog can be closed. + */ +Drupal.overlay.canClose = function() { + var self = this; + if (!self.isOpen) { + return false; + } + // Allow external scripts decide if the overlay can be closed. + if ($.isFunction(self.options.onOverlayCanClose)) { + if (!self.options.onOverlayCanClose(self)) { + return false; + } + } + return true; +}; + +/** + * Close the overlay and remove markup related to it from the document. + */ +Drupal.overlay.close = function(args, statusMessages) { + var self = this; + + // Offer the user a chance to change their mind if there is a form on the + // page, which may have unsaved work on it. + var iframeElement = self.iframe.$element.get(0); + var iframeDocument = (iframeElement.contentWindow || iframeElement.contentDocument); + if (iframeDocument.document) { + iframeDocument = iframeDocument.document; + } + + // Check if the dialog can be closed. + if (!self.canClose()) { + delete self.beforeCloseIsBusy; + return false; + } + + // Hide and destroy the dialog. + function closeDialog() { + // Prevent double execution when close is requested more than once. + if (!self.isObject(self.iframe.$container)) { + return; + } + self.beforeCloseEnabled = true; + self.iframe.$container.dialog('close'); + if ($.isFunction(self.options.onOverlayClose)) { + self.options.onOverlayClose(args, statusMessages); + } + } + if (!self.isObject(self.iframe.$element) || !self.iframe.$element.size() || !self.iframe.$element.is(':visible')) { + closeDialog(); + } + else { + self.iframe.$container.animate({height: 'hide'}, { duration: 'fast', 'queue': false }); + $('.overlay').animate({opacity: 'hide'}, closeDialog); + } + return true; +}; + +/** + * Redirect the overlay parent window to the given URL. + * + * @param link + * Can be an absolute URL or a relative link to the domain root. + */ +Drupal.overlay.redirect = function(link) { + if (link.indexOf('http') != 0 && link.indexOf('https') != 0) { + var absolute = location.href.match(/https?:\/\/[^\/]*/)[0]; + link = absolute + link; + } + location.href = link; + return true; +} + +/** + * Bind the child window. + * + * Add tabs on the overlay, keyboard actions and display animation. + */ +Drupal.overlay.bindChild = function(iFrameWindow, isClosing) { + var self = this; + var $iFrameWindow = iFrameWindow.jQuery; + var $iFrameDocument = $iFrameWindow(iFrameWindow.document); + var autoResizing = false; + self.iframe.Drupal = iFrameWindow.Drupal; + + // We are done if the child window is closing. + if (isClosing) { + return; + } + + // Update the dialog title with the child window title. + $('.overlay .ui-dialog-title').html($iFrameDocument.attr('title')).focus(); + // Add a title attribute to the iframe for accessibility. + self.iframe.$element.attr('title', Drupal.t('@title dialog', { '@title': $iFrameDocument.attr('title') })); + + // Remove any existing tabs. + $('.overlay .ui-dialog-titlebar ul').remove(); + + // Setting tabIndex makes the div focusable. + $iFrameDocument.attr('tabindex', -1); + + $('.ui-dialog-titlebar-close-bg').animate({opacity: 0.9999}, 'fast'); + + // Perform animation to show the iframe element. + self.iframe.$element.fadeIn('fast', function() { + // @todo: Watch for experience in the way we compute the size of the + // iframed document. There are many ways to do it, and none of them + // seem to be perfect. Note though, that the size of the iframe itself + // may affect the size of the child document, especially on fluid layouts. + self.iframe.documentSize = { width: $iFrameDocument.width(), height: $iFrameWindow('body').height() + 25 }; + + // Adjust overlay to fit the iframe content? + if (self.options.autoFit) { + self.resize(self.iframe.documentSize); + } + + // Try to enhance keyboard based navigation of the overlay. + // Logic inspired by the open() method in ui.dialog.js, and + // http://wiki.codetalks.org/wiki/index.php/Docs/Keyboard_navigable_JS_widgets + + // Get a reference to the close button. + var $closeButton = $('.overlay .ui-dialog-titlebar-close'); + + // Search tabbable elements on the iframed document to speed up related + // keyboard events. + // @todo: Do we need to provide a method to update these references when + // AJAX requests update the DOM on the child document? + var $iFrameTabbables = $iFrameWindow(':tabbable:not(form)'); + var $firstTabbable = $iFrameTabbables.filter(':first'); + var $lastTabbable = $iFrameTabbables.filter(':last'); + + // Unbind keyboard event handlers that may have been enabled previously. + $(document).unbind('keydown.overlay-event'); + $closeButton.unbind('keydown.overlay-event'); + + // When the focus leaves the close button, then we want to jump to the + // first/last inner tabbable element of the child window. + $closeButton.bind('keydown.overlay-event', function(event) { + if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { + var $target = (event.shiftKey ? $lastTabbable : $firstTabbable); + if (!$target.size()) { + $target = $iFrameDocument; + } + setTimeout(function() { $target.focus(); }, 10); + return false; + } + }); + + // When the focus leaves the child window, then drive the focus to the + // close button of the dialog. + $iFrameDocument.bind('keydown.overlay-event', function(event) { + if (event.keyCode) { + if (event.keyCode == $.ui.keyCode.TAB) { + if (event.shiftKey && event.target == $firstTabbable.get(0)) { + setTimeout(function() { $closeButton.focus(); }, 10); + return false; + } + else if (!event.shiftKey && event.target == $lastTabbable.get(0)) { + setTimeout(function() { $closeButton.focus(); }, 10); + return false; + } + } + else if (event.keyCode == $.ui.keyCode.ESCAPE) { + setTimeout(function() { self.close(); }, 10); + return false; + } + } + }); + + var autoResize = function() { + if (typeof self.iframe.$element == 'undefined') { + autoResizing = false; + $(window).unbind('resize', windowResize); + return; + } + var iframeElement = self.iframe.$element.get(0); + var iframeDocument = (iframeElement.contentWindow || iframeElement.contentDocument); + if (iframeDocument.document) { + iframeDocument = iframeDocument.document; + } + // Use outerHeight() because otherwise the calculation will be off + // because of padding and/or border added by the theme. + var height = $(iframeDocument).find('body').outerHeight() + 25; + self.iframe.$element.css('height', height); + self.iframe.$container.css('height', height); + self.iframe.$container.parent().css('height', height + 45); + // Don't allow the shadow background to shrink so it's not enough to hide + // the whole page. Take the existing document height (with overlay) and + // the body height itself for our base calculation. + var docHeight = Math.min($(document).find('body').outerHeight(), $(document).height()); + $('.ui-widget-overlay').height(Math.max(docHeight, $(window).height(), height + 145)); + setTimeout(autoResize, 150); + }; + + var windowResize = function() { + var width = $(window).width() + var change = lastWidth - width; + var currentWidth = self.iframe.$element.width(); + var newWidth = lastFrameWidth - change; + lastWidth = width; + lastFrameWidth = newWidth; + + if (newWidth >= 300) { + self.iframe.$element.css('width', newWidth); + self.iframe.$container.css('width', newWidth); + self.iframe.$container.parent().css('width', newWidth); + widthBelowMin = false; + } + else { + widthBelowMin = true; + } + } + + if (!autoResizing) { + autoResizing = true; + autoResize(); + var lastFrameWidth = self.iframe.$element.width(); + var lastWidth = $(window).width(); + $(window).resize(windowResize); + } + + // When the focus is captured by the parent document, then try + // to drive the focus back to the first tabbable element, or the + // close button of the dialog (default). + $(document).bind('keydown.overlay-event', function(event) { + if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { + setTimeout(function() { + if (!$iFrameWindow(':tabbable:not(form):first').focus().size()) { + $closeButton.focus(); + } + }, 10); + return false; + } + }); + + // If there are tabs in the page, move them to the titlebar. + var tabs = $iFrameDocument.find('ul.primary').get(0); + + // This breaks in anything less than IE 7. Prevent it from running. + if (typeof tabs != 'undefined' && (!$.browser.msie || parseInt($.browser.version) >= 7)) { + $('.ui-dialog-titlebar').append($(tabs).remove().get(0)); + if ($(tabs).is('.primary')) { + $(tabs).find('a').addClass('to-overlay').removeClass('overlay-processed'); + Drupal.attachBehaviors($(tabs)); + } + // Remove any classes from the list element to avoid theme styles + // clashing with our styling. + $(tabs).removeAttr('class'); + } + }); +}; + +/** + * Unbind the child window. + * + * Remove keyboard event handlers, reset title and hide the iframe. + */ +Drupal.overlay.unbindChild = function(iFrameWindow) { + var self = this; + + // Prevent memory leaks by explicitly unbinding keyboard event handler + // on the child document. + iFrameWindow.jQuery(iFrameWindow.document).unbind('keydown.overlay-event'); + + // Change the overlay title. + $('.overlay .ui-dialog-title').html(Drupal.t('Please, wait...')); + + // Hide the iframe element. + self.iframe.$element.fadeOut('fast'); +}; + +/** + * Check if the given variable is an object. + */ +Drupal.overlay.isObject = function(something) { + return (something !== null && typeof something === 'object'); +}; + +/** + * Sanitize dialog size. + * + * Do not let the overlay go over the 0.78x of the width of the screen and set + * minimal height. The height is not limited due to how we rely on the parent + * window to provide scrolling instead of scrolling in scrolling with the + * overlay. + * + * @param size + * Contains 'width' and 'height' items as numbers. + * @return + * The same structure with sanitized number values. + */ +Drupal.overlay.sanitizeSize = function(size) { + var width, height; + var $window = $(window); + + // Use 300px as the minimum width but at most expand to 78% of the window. + // Ensures that users see that there is an actual website in the background. + var minWidth = 300, maxWidth = parseInt($window.width() * .78); + if (typeof size.width != 'number') { + width = maxWidth; + } + // Set to at least minWidth but at most maxWidth. + else if (size.width < minWidth || size.width > maxWidth) { + width = Math.min(maxWidth, Math.max(minWidth, size.width)); + } + else { + width = size.width; + } + + // Use 100px as the minimum height. Expand to 92% of the window if height + // was invalid, to ensure that we have a reasonable chance to show content. + var minHeight = 100, maxHeight = parseInt($window.height() * .92); + if (typeof size.height != 'number') { + height = maxHeight; + } + else if (size.height < minHeight) { + // Do not consider maxHeight as the actual maximum height, since we rely on + // the parent window scroll bar to scroll the window. Only set up to be at + // least the minimal height. + height = Math.max(minHeight, size.height); + } + else { + height = size.height; + } + return { width: width, height: height }; +}; + +/** + * Compute position to center horizontally and on viewport top vertically. + */ +Drupal.overlay.computePosition = function($element, elementSize) { + var $window = $(window); + // Consider the possibly displayed admin toolbar. + var $toolbar = $('#toolbar'); + var toolbarHeight = $toolbar ? $toolbar.height() : 0; + var position = { + left: Math.max(0, parseInt(($window.width() - elementSize.width) / 2)), + top: toolbarHeight + 20 + }; + + // Reset the scroll to the top of the window so that the overlay is visible again. + window.scrollTo(0, 0); + return position; +}; + +/** + * Resize overlay to the given size. + * + * @param size + * Contains 'width' and 'height' items as numbers. + */ +Drupal.overlay.resize = function(size) { + var self = this; + + // Compute frame and dialog size based on requested document size. + var titleBarHeight = $('.overlay .ui-dialog-titlebar').outerHeight(true); + var frameSize = self.sanitizeSize(size); + var dialogSize = $.extend({}, frameSize); + dialogSize.height += titleBarHeight + 15; + + // Compute position on viewport. + var dialogPosition = self.computePosition($('.overlay'), dialogSize); + + var animationOptions = $.extend(dialogSize, dialogPosition); + + // Perform the resize animation. + $('.overlay').animate(animationOptions, 'fast', function() { + // Proceed only if the dialog still exists. + if (self.isObject(self.iframe.$element) && self.isObject(self.iframe.$container)) { + // Resize the iframe element and container. + $('.overlay').width(dialogSize.width).height(dialogSize.height); + self.iframe.$container.width(frameSize.width).height(frameSize.height); + self.iframe.$element.width(frameSize.width).height(frameSize.height); + + // Update the dialog size so that UI internals are aware of the change. + self.iframe.$container.dialog('option', { width: dialogSize.width, height: dialogSize.height }); + + // Keep the dim background grow or shrink with the dialog. + $('.ui-widget-overlay').height($(document).height()); + + // Animate body opacity, so we fade in the page as it loads in. + $(self.iframe.$element.get(0)).contents().find('body.overlay').animate({opacity: 0.9999}, 'slow'); + } + }); +}; + +})(jQuery); Index: modules/overlay/overlay.module =================================================================== --- modules/overlay/overlay.module (revision 0) +++ modules/overlay/overlay.module (revision 0) @@ -0,0 +1,302 @@ + 'Overlay: Parent', + 'website' => 'http://drupal.org/node/517688', + 'version' => '1.0', + 'js' => array( + $module_path . '/overlay-parent.js' => array(), + ), + 'css' => array( + $module_path . '/overlay-parent.css' => array(), + ), + 'dependencies' => array( + array('system', 'ui.dialog'), + ), + ); + // Overlay child. + $libraries['child'] = array( + 'title' => 'Overlay: Child', + 'website' => 'http://drupal.org/node/517688', + 'version' => '1.0', + 'js' => array( + $module_path . '/overlay-child.js' => array(), + ), + 'dependencies' => array( + array('system', 'ui'), + ), + ); + + return $libraries; +} + +/** + * Implement hook_drupal_goto_alter(). + */ +function overlay_drupal_goto_alter(&$args) { + if (overlay_mode() == 'child') { + if (!empty($args['query'])) { + if (is_array($args['query'])) { + // Query is an array, add our marker as a key. + $args['query']['render'] = 'overlay'; + } + else { + // Query is not an array, add our marker as string. + $args['query'] .= '&render=overlay'; + } + } + else { + $args['query'] = array('render' => 'overlay'); + } + } +} + +/** + * Implement hook_block_info_alter(). + */ +function overlay_block_info_alter(&$blocks) { + global $custom_theme; + + if (!empty($custom_theme) && overlay_mode() == 'child') { + $themes = list_themes(); + $theme = $themes[$custom_theme]; + if (!empty($theme->info['overlay_regions'])) { + // Don't show any blocks except the main page content and the help text if + // we're in the overlay. + foreach ($blocks as $bid => $block) { + if (!in_array($block->region, $theme->info['overlay_regions'])) { + unset($blocks[$bid]); + } + } + } + } +} + +/** + * Implement hook_system_info_alter(). + * + * Add default regions for overlay. + */ +function overlay_system_info_alter(&$info, $file) { + $info['overlay_regions'][] = 'content'; + $info['overlay_regions'][] = 'help'; +} + +/** + * Preprocess template variables for html.tpl.php. + */ +function overlay_preprocess_html(&$variables) { + if (overlay_mode() == 'child') { + // Add overlay class, so themes can react to being displayed in the overlay. + $variables['classes_array'][] = 'overlay'; + // Do not include site name or slogan in the overlay title. + $variables['head_title'] = drupal_get_title(); + } +} + +/** + * Preprocess template variables for page.tpl.php. + */ +function overlay_preprocess_page(&$variables) { + if (overlay_mode() == 'child') { + // Remove 'Home' from the breadcrumbs. + $overlay_breadcrumb = drupal_get_breadcrumb(); + array_shift($overlay_breadcrumb); + $variables['breadcrumb'] = theme('breadcrumb', array('overlay_breadcrumb' => $overlay_breadcrumb)); + } +} + +/** + * Form after build callback. + * + * After all hook_form_alter() implementations have been processed, we look at + * the list of submit handler and add our own at the end, so we can affect the + * redirection done at the end of the form processing if we are in the overlay + * children. + * + * @see _form_builder_handle_input_element() + * @see _form_builder_ie_cleanup() + * @see form_execute_handlers() + * @see form_builder() + * + * @ingroup forms + */ +function overlay_form_after_build($form, &$form_state) { + if (isset($_GET['render']) && $_GET['render'] == 'overlay') { + // Form API may have already captured submit handlers from the submitted + // button before after_build callback is invoked. This may have been done + // by _form_builder_handle_input_element(). + // If so, the list of submit handlers is stored in the $form_state array + // which is something we can also alter from here, luckily. + // Rememeber: our goal here is set $form_state['redirect'] is set to FALSE + // if the API overlay_close_dialog() has been invoked. That's because we + // want to tell the parent window to close the overlay. + if (!empty($form_state['submit_handlers']) && !in_array('overlay_form_submit', $form_state['submit_handlers'])) { + $form_state['submit_handlers'][] = 'overlay_form_submit'; + } + // If this element has submit handlers, then append our own. + if (isset($form['#submit'])) { + $form['#submit'][] = 'overlay_form_submit'; + } + } + return $form; +} + +/** + * Generic form submit handler. + * + * When we are requested to close an overlay, we don't want Form API to + * perform any redirection once the submitted form has been processed. + * + * When $form_state['redirect'] is set to FALSE, then Form API will simply + * re-render the form with the values still in its fields. And this is all + * we need to output the JavaScript that will tell the parent window to close + * the child dialog. + * + * @ingroup forms + */ +function overlay_form_submit($form, &$form_state) { + $settings = &drupal_static(__FUNCTION__); + + // Check if we have a request to close the overlay. + $args = overlay_close_dialog(); + + // Make sure the overlay is closed when a node edit form has been submitted + // in child mode. + if ($args === FALSE && overlay_mode() == 'child' && !empty($form['#node_edit_form'])) { + $args = overlay_close_dialog(TRUE); + } + + // Close the overlay only if specifically requested. + if ($args !== FALSE) { + if (!isset($settings)) { + $settings = array( + 'overlayChild' => array( + 'closeOverlay' => TRUE, + 'statusMessages' => theme('status_messages'), + 'args' => $args, + ), + ); + // Tell the child window to perform the redirection when requested to. + if (!empty($form_state['redirect'])) { + $settings['overlayChild']['redirect'] = url($form_state['redirect']); + } + drupal_add_js($settings, array('type' => 'setting')); + } + // Tell FAPI to redraw the form without redirection after all submit + // callbacks have been processed. + $form_state['redirect'] = FALSE; + } +} + +/** + * Set overlay mode and add proper JavaScript and styles to the page. + * + * @param $mode + * To set the mode, pass in either 'parent' or 'child'. 'parent' is used in + * the context of a parent overlay window, where the overlay may appear, and + * JavaScript is added accordingly to present an overlay dialog. 'child' is + * used in the context of the child overlay window, the one actually appearing + * within the overlay, and certain JavaScript and CSS is added so that Drupal + * behaves nicely from within the overlay. This parameter is optional, and if + * omitted, the current mode will be returned with no action taken. + * @return + * The current mode, if any has been set, or NULL if no mode has been set. + * @ingroup overlay_api + */ +function overlay_mode($mode = NULL) { + global $base_path; + $overlay_mode = &drupal_static(__FUNCTION__); + + // Make sure external resources are not included more than once. Also return + // current mode, if no mode was specified. + if (isset($overlay_mode) || !isset($mode)) { + return $overlay_mode; + } + $overlay_mode = $mode; + + switch ($overlay_mode) { + case 'parent': + drupal_add_library('overlay', 'parent'); + + // Allow modules to act upon overlay events. + module_invoke_all('overlay_parent_initialize'); + break; + + case 'child': + drupal_add_library('overlay', 'child'); + + // Allow modules to act upon overlay events. + module_invoke_all('overlay_child_initialize'); + break; + } + return $overlay_mode; +} + +/** + * Callback to close the overlay dialog. + * + * @param $set + * If set, will set the current close dialog mode to the given state. + * Use FALSE to disable close dialog mode. Otherwise, the argument will + * be forwarded to the onOverlayClose callback of the overlay. + * @return + * The current overlay close dialog mode, TRUE if the overlay should close or + * FALSE if it should not (default). + */ +function overlay_close_dialog($set = NULL) { + $close = &drupal_static(__FUNCTION__, FALSE); + if (isset($set)) { + $close = $set; + } + return $close; +} Index: modules/overlay/overlay.info =================================================================== --- modules/overlay/overlay.info (revision 1266) +++ modules/overlay/overlay.info (working copy) @@ -0,0 +1,7 @@ +; $Id$ +name = Overlay +description = Displays the Drupal administration interface in an overlay. +package = Core +version = VERSION +core = 7.x +files[] = overlay.module Index: modules/overlay/overlay-child.js =================================================================== --- modules/overlay/overlay-child.js (revision 0) +++ modules/overlay/overlay-child.js (revision 0) @@ -0,0 +1,149 @@ +// $Id: child.js,v 1.1.4.3 2009/06/17 15:16:26 markuspetrux Exp $ + +(function ($) { + +/** + * Overlay object for child windows. + */ +Drupal.overlayChild = Drupal.overlayChild || { processed: false, behaviors: {} }; + +/** + * Attach the child dialog behavior to new content. + */ +Drupal.behaviors.overlayChild = { + attach: function(context, settings) { + var self = Drupal.overlayChild; + var settings = settings.overlayChild || {}; + + // Make sure this behavior is not processed more than once. + if (self.processed) { + return; + } + self.processed = true; + + // If we cannot reach the parent window, then we have nothing else to do + // here. + if (!self.isObject(parent.Drupal) || !self.isObject(parent.Drupal.overlay)) { + return; + } + + // If a form has been submitted successfully, then the server side script + // may have decided to tell us the parent window to close the popup dialog. + if (settings.closeOverlay) { + parent.Drupal.overlay.bindChild(window, true); + // Close the child window from a separate thread because the current + // one is busy processing Drupal behaviors. + setTimeout(function() { + // We need to store the parent variable locally because it will + // disappear as soon as we close the iframe. + var p = parent; + p.Drupal.overlay.close(settings.args, settings.statusMessages); + if (typeof settings.redirect == 'string') { + p.Drupal.overlay.redirect(settings.redirect); + } + }, 1); + return; + } + + // Ok, now we can tell the parent window we're ready. + parent.Drupal.overlay.bindChild(window); + + // Install onBeforeUnload callback, if module is present. + if (self.isObject(Drupal.onBeforeUnload) && !Drupal.onBeforeUnload.callbackExists('overlayChild')) { + Drupal.onBeforeUnload.addCallback('overlayChild', function() { + // Tell the parent window we're unloading. + parent.Drupal.overlay.unbindChild(window); + }); + } + + // Attach child related behaviors to the iframe document. + self.attachBehaviors(context, settings); + } +}; + +/** + * Add the isObject() method to the overlayChild object for convenience. + */ +Drupal.overlayChild.isObject = parent.Drupal.overlay.isObject; + +/** + * Attach child related behaviors to the iframe document. + */ +Drupal.overlayChild.attachBehaviors = function(context, settings) { + $.each(this.behaviors, function() { + this(context, settings); + }); +}; + +/** + * Scroll to the top of the page. + * + * This makes the overlay visible to users even if it is not as tall as the + * previously shown overlay was. + */ +Drupal.overlayChild.behaviors.scrollToTop = function(context, settings) { + window.scrollTo(0, 0); +}; + +/** + * Modify links and forms depending on their relation to the overlay. + * + * By default, forms and links are assumed to keep the flow in the overlay. + * Thus their action and href attributes respectively get a ?render=overlay + * suffix. Links having the .overlay-escape class should however close the + * overlay and redirect the parent page to the given link. Such are links + * in a content listing, where administration options are mixed with links + * to the actual content to be shown on the site out of the overlay. + */ +Drupal.overlayChild.behaviors.parseLinks = function(context, settings) { + $('a:not(.overlay-exclude)', context).once('overlay').each(function() { + // Links that have the class "overlay-escape" should close the overlay and + // open in the main window. + if ($(this).hasClass('overlay-escape')) { + $(this).click(function() { + // We need to store the parent variable locally because it will + // disappear as soon as we close the iframe. + var parentWindow = parent; + if (parentWindow.Drupal.overlay.close(false)) { + parentWindow.Drupal.overlay.redirect($(this).attr('href')); + } + return false; + }); + return; + } + // Obtain the href attribute of the link. + var href = $(this).attr('href'); + // Do not process links with an empty href, or that only have the fragment. + if (href.length <= 0 || href.charAt(0) == '#') { + return; + } + if (href.indexOf('http') != 0 && href.indexOf('https') != 0) { + // Keep internal linked pages in the overlay. + var fragmentIndex = href.indexOf('#'); + var fragment = ''; + if (fragmentIndex != -1) { + fragment = href.substr(fragmentIndex); + href = href.substr(0, fragmentIndex); + } + href += (href.indexOf('?') > -1 ? '&' : '?') + 'render=overlay' + fragment; + $(this).attr('href', href); + } + else { + $(this).attr('target', '_new'); + } + }); + $('form:not(.overlay-processed)', context).addClass('overlay-processed').each(function() { + // Obtain the action attribute of the form. + var action = $(this).attr('action'); + if (action.indexOf('http') != 0 && action.indexOf('https') != 0) { + // Keep internal forms in the overlay. + action += (action.indexOf('?') > -1 ? '&' : '?') + 'render=overlay'; + $(this).attr('action', action); + } + else { + $(this).attr('target', '_new'); + } + }); +}; + +})(jQuery); Index: modules/node/node.admin.inc =================================================================== --- modules/node/node.admin.inc (revision 1266) +++ modules/node/node.admin.inc (working copy) @@ -453,6 +453,10 @@ function node_admin_nodes() { 'status' => $node->status ? t('published') : t('not published'), 'changed' => format_date($node->changed, 'short'), ); + // Set a class to flag to the overlay, if present, not to open the link in + // the overlay. + $options[$node->nid]['#attributes'] = array('class' => array('overlay-escape')); + if ($multilanguage) { $options[$node->nid]['language'] = empty($node->language) ? t('Language neutral') : t($languages[$node->language]->name); } Index: modules/toolbar/toolbar.module =================================================================== --- modules/toolbar/toolbar.module (revision 1266) +++ modules/toolbar/toolbar.module (working copy) @@ -36,12 +36,29 @@ function toolbar_theme($existing, $type, $theme, $ * Add admin toolbar to the page_top region automatically. */ function toolbar_page_build(&$page) { - if (user_access('access toolbar')) { + if (user_access('access toolbar') && toolbar_enabled()) { $page['page_top']['toolbar'] = toolbar_build(); } } /** + * Enable or disable the toolbar, or find out the current status of the toolbar. + * + * @param $enabled + * If given, will change the display mode of the toolbar to the given + * boolean. + * @return + * The current status of the toolbar, TRUE for enabled, FALSE for disabled. + */ +function toolbar_enabled($enabled = NULL) { + $setting = &drupal_static(__FUNCTION__, TRUE); + if (isset($enabled)) { + $setting = $enabled; + } + return $setting; +} + +/** * Implement hook_preprocess_page(). * * Add some page classes, so global page theming can adjust to the toolbar. @@ -189,3 +206,13 @@ function toolbar_in_active_trail($path) { } return in_array($path, $active_paths); } + +/** + * Implement hook_overlay_child_initialize(). + */ +function toolbar_overlay_child_initialize() { + // Disable admin toolbar, which is something child windows don't need and + // shouldn't have. + toolbar_enabled(FALSE); +} + Index: modules/toolbar/toolbar.css =================================================================== --- modules/toolbar/toolbar.css (revision 1266) +++ modules/toolbar/toolbar.css (working copy) @@ -36,7 +36,7 @@ div#toolbar { left: 0; right: 0; top: 0; - z-index: 100; + z-index: 600; } div#toolbar .collapsed { Index: modules/toolbar/toolbar.js =================================================================== --- modules/toolbar/toolbar.js (revision 1266) +++ modules/toolbar/toolbar.js (working copy) @@ -15,6 +15,16 @@ Drupal.behaviors.admin = { Drupal.admin.toolbar.toggle(); return false; }); + + // Set the most recently clicked item as active. + $('#toolbar a').once().click(function() { + $('#toolbar a').each(function() { + $(this).removeClass('active'); + }); + if ($(this).parents('div.toolbar-shortcuts').length) { + $(this).addClass('active'); + } + }); } };