diff --git misc/tableheader.js misc/tableheader.js
index 87799ca..0b2e3b3 100644
--- misc/tableheader.js
+++ misc/tableheader.js
@@ -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');
});
diff --git modules/locale/locale.test modules/locale/locale.test
index 411c6e9..877645f 100644
--- modules/locale/locale.test
+++ modules/locale/locale.test
@@ -236,7 +236,7 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
$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.'));
diff --git modules/node/node.admin.inc modules/node/node.admin.inc
index 8835f14..af55d45 100644
--- modules/node/node.admin.inc
+++ modules/node/node.admin.inc
@@ -447,6 +447,9 @@ function node_admin_nodes() {
foreach ($result as $node) {
$nodes[$node->nid] = '';
$options = empty($node->language) ? array() : array('language' => $languages[$node->language]);
+ // Set a class to flag to the overlay, if present, not to open the link in
+ // the overlay.
+ $options['attributes']['class'] = 'overlay-escape';
$form['title'][$node->nid] = array('#markup' => l($node->title, 'node/' . $node->nid, $options) . ' ' . theme('mark', node_mark($node->nid, $node->changed)));
$form['name'][$node->nid] = array('#markup' => check_plain(node_type_get_name($node)));
$form['username'][$node->nid] = array('#markup' => theme('username', $node));
diff --git modules/overlay/overlay-child.js modules/overlay/overlay-child.js
new file mode 100644
index 0000000..b9a184a
--- /dev/null
+++ modules/overlay/overlay-child.js
@@ -0,0 +1,148 @@
+// $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: {} };
+
+/**
+ * Drupal will automatically attach the child dialog behavior to new content.
+ */
+Drupal.behaviors.overlayChild = {
+ attach: function(context, settings) {
+ Drupal.overlayChild.attachBehavior(context, settings);
+ }
+};
+
+/**
+ * Attach child dialog behavior.
+ */
+Drupal.overlayChild.attachBehavior = 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.skipWarning = true;
+ 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.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 iframed document.
+ */
+Drupal.overlayChild.attachBehaviors = function(context, settings) {
+ $.each(this.behaviors, function() {
+ this(context, settings);
+ });
+};
+
+/**
+ * Scroll to the top of the page so that we don't get weird positioning errors
+ * in some browsers.
+ */
+Drupal.overlayChild.behaviors.scrollToTop = function(context, settings) {
+ window.scrollTo(0, 0);
+};
+
+/**
+ * Add target="_new" to all external URLs.
+ */
+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) {
+ var 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);
diff --git modules/overlay/overlay-parent.css modules/overlay/overlay-parent.css
new file mode 100644
index 0000000..5ac2135
--- /dev/null
+++ modules/overlay/overlay-parent.css
@@ -0,0 +1,142 @@
+/* $Id$ */
+
+/**
+ * jQuery UI Dialog classes.
+ */
+.overlay {
+ color: #000;
+ padding-right: 42px;
+}
+
+.overlay .ui-dialog-titlebar {
+ position: relative;
+ height: 30px;
+ white-space: nowrap;
+ z-index: 2;
+ padding-right: 20px;
+}
+
+.overlay .ui-dialog-title {
+ display: block;
+ font-family: Verdana,sans-serif;
+ position: absolute;
+ top: 0;
+ left: 0;
+ margin: 0.2em 0.5em;
+ padding: 0;
+ color: #fff;
+ font-size: 20px;
+}
+
+.overlay .ui-dialog-title:active,
+.overlay .ui-dialog-title:focus {
+ outline: 0;
+}
+
+.overlay .ui-dialog-titlebar-close {
+ display: block;
+ position: absolute;
+ right: -26px;
+ top: 30px;
+ margin: 0;
+ padding: 0;
+ width: 26px;
+ height: 26px;
+ -webkit-box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+ -moz-box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+ box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+ -moz-border-radius: 0 13px 13px 0;
+ -webkit-border-top-left-radius: 8px;
+ -webkit-border-top-right-radius: 8px;
+ border-radius: 8px 8px 0 0;+}
+
+.overlay .ui-dialog-titlebar-close span {
+ display: none;
+}
+
+.overlay .ui-dialog-content {
+ color: #292929;
+ background-color: #f8f8f8;
+}
+
+/**
+ * ui-dialog overlay.
+ */
+.ui-widget-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: #000;
+ opacity: 0.7;
+ filter: alpha(opacity=80);
+}
+
+/**
+ * Overlay content and shadows.
+ */
+#overlay-container {
+ margin: 0;
+ padding: 0;
+ background: #fff url(images/loading.gif) no-repeat 50% 50%;
+ -webkit-box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+ -moz-box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+ box-shadow: 8px 8px 8px rgba(0,0,0,.5);
+}
+
+#overlay-element {
+ margin: 0;
+ padding: 0;
+ border: none;
+ overflow: hidden;
+}
+
+/**
+ * Tabs on the overlay.
+ */
+.ui-dialog-titlebar ul {
+ float: right;
+ margin: 0 0 -10px 0;
+ padding: 3px 0 0 8px;
+ line-height: 30px;
+ text-transform: uppercase;
+ font-size: 12px;
+}
+
+.ui-dialog-titlebar ul li {
+ display: inline;
+ list-style: none;
+ margin-left: 0;
+}
+
+.ui-dialog-titlebar ul li a,
+.ui-dialog-titlebar ul li a:active,
+.ui-dialog-titlebar ul li a:visited,
+.ui-dialog-titlebar ul li a:hover {
+ background-color: #a6a7a2;
+ -moz-border-radius: 8px 8px 0 0;
+ -webkit-border-top-left-radius: 8px;
+ -webkit-border-top-right-radius: 8px;
+ border-radius: 8px 8px 0 0;
+ color: #000;
+ font-weight: bold;
+ padding: 8px 20px 3px 20px;
+ text-decoration: none;
+}
+
+.ui-dialog-titlebar ul li.active a,
+.ui-dialog-titlebar ul li.active a.active,
+.ui-dialog-titlebar ul li.active a:active,
+.ui-dialog-titlebar ul li.active a:visited {
+ background-color: #fff;
+ padding: 8px 20px 5px 20px;
+}
+
+.ui-dialog-titlebar ul li a:hover {
+ color: #fff;
diff --git modules/overlay/overlay-parent.js modules/overlay/overlay-parent.js
new file mode 100644
index 0000000..ffdc40d
--- /dev/null
+++ modules/overlay/overlay-parent.js
@@ -0,0 +1,558 @@
+// $Id: parent.js,v 1.1.4.4 2009/06/19 15:32:57 markuspetrux Exp $
+
+(function ($) {
+
+Drupal.behaviors.keepOverlay = {
+ attach: function(context, settings) {
+
+ // Attach on the .to-overlay class.
+ $('a.to-overlay:not(.overlay-exclude)').once('overlay').click(function() {
+
+ // Remove the active class from where it was, and add the active class to
+ // this link, so the button keeps highlighting where we are. Only
+ // highlight active items in the shortcuts bar.
+ $('#toolbar a').each(function() {
+ $(this).removeClass('active');
+ });
+ if ($(this).parents('div.toolbar-shortcuts').length) {
+ $(this).addClass('active');
+ }
+
+ // Append render variable, so the server side can choose the right
+ // rendering and add child modal frame code to the page if needed.
+ var linkURL = $(this).attr('href');
+ linkURL += (linkURL.indexOf('?') > -1 ? '&' : '?') + 'render=overlay';
+
+ // If the modal frame is already open, replace the loaded document with
+ // this new one. Keeps browser history.
+ if (Drupal.overlay.isOpen) {
+ Drupal.overlay.load(linkURL);
+ return false;
+ }
+
+ // There is overlay opened yet, we should open a new one.
+ var toolbarHeight = $('#toolbar').height();
+ var overlayOptions = {
+ url: linkURL,
+ width: $(window).width() - 40,
+ height: $(window).height() - 40 - toolbarHeight,
+ // Remove active class from all header buttons.
+ onOverlayClose: function() { $('#toolbar a').each(function() { $(this).removeClass('active'); }); },
+ draggable: false
+ };
+ Drupal.overlay.open(overlayOptions);
+
+ // Set position and styling to let the admin toolbar work.
+ $('.overlay').css('top', toolbarHeight + 20);
+ $('#toolbar').css('z-index', 2000);
+
+ // Prevent default action of the link click event.
+ return false;
+ });
+ }
+};
+
+/**
+ * Overlay object for parent windows.
+ */
+Drupal.overlay = Drupal.overlay || {
+ options: {},
+ iframe: { $container: null, $element: null },
+ isOpen: false
+};
+
+/**
+ * Open an overlay.
+ */
+Drupal.overlay.open = function(options) {
+ var self = this;
+
+ // Just one overlay is allowed.
+ if (self.isOpen || $('#overlay-container').size()) {
+ return false;
+ }
+
+ var defaultOptions = {
+ url: options.url,
+ width: options.width,
+ height: options.height,
+ autoFit: (options.autoFit == undefined || options.autoFit),
+ onOverlayClose: options.onOverlayClose
+ }
+
+ self.options = $.extend(defaultOptions, options);
+
+ // Create the dialog and related DOM elements.
+ self.create(options);
+
+ // Open the dialog offscreen where we can set its size, etc.
+ self.iframe.$container.dialog('option', {position: ['-999em', '-999em']}).dialog('open');
+
+ return true;
+};
+
+/**
+ * Create the overlay.
+ */
+Drupal.overlay.create = function() {
+ var self = this;
+
+ // Note: We use scrolling="yes" for IE as a workaround to yet another IE bug
+ // where the horizontal scrollbar is always rendered no matter how wide the
+ // iframe element is defined.
+ self.iframe.$element = $('');
+ self.iframe.$container = $('').append(self.iframe.$element);
+
+ $('body').append(self.iframe.$container);
+
+ self.iframe.$container.dialog({
+ modal: true,
+ autoOpen: false,
+ closeOnEscape: true,
+ resizable: false,
+ title: Drupal.t('Loading...'),
+ dialogClass: 'overlay',
+ open: 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(false); } catch(e) {}; return false; });
+
+ // 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);
+ });
+
+ self.isOpen = true;
+ },
+ beforeclose: function() {
+ if (self.beforeCloseEnabled) {
+ return true;
+ }
+ if (!self.beforeCloseIsBusy) {
+ self.beforeCloseIsBusy = true;
+ setTimeout(function() { self.close(false); }, 1);
+ }
+ return false;
+ },
+ close: 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;
+ }
+ });
+ // Replace the title span element with an h1 element for accessibility.
+ $('.overlay .ui-dialog-title').replaceWith('' + $('.overlay .ui-dialog-title').html() + '
');
+};
+
+/**
+ * Load the given URL into the dialog iframe.
+ */
+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;
+ }
+ return true;
+};
+
+/**
+ * Close the overlay.
+ */
+Drupal.overlay.close = function(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;
+ }
+ if (!self.skipWarning && $(iframeDocument).find('form').size() && !confirm(Drupal.t('Are you sure you want to close this dialog? Any unsaved work will be lost.'))) {
+ return false;
+ }
+
+ // 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(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;
+};
+
+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.
+ */
+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(false); }, 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.
+ $('.ui-widget-overlay').height(Math.max($('.ui-widget-overlay').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.
+ */
+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.
+ */
+Drupal.overlay.sanitizeSize = function(size) {
+ var width, height;
+ var $window = $(window);
+ var minWidth = 300, maxWidth = parseInt($window.width() * .78);
+ if (typeof size.width != 'number') {
+ width = maxWidth;
+ }
+ else if (size.width < minWidth || size.width > maxWidth) {
+ width = Math.min(maxWidth, Math.max(minWidth, size.width));
+ }
+ else {
+ width = size.width;
+ }
+ var minHeight = 100, maxHeight = parseInt($window.height() * .92);
+ if (typeof size.height != 'number') {
+ height = maxHeight;
+ }
+ else if (size.height < minHeight) {
+ // Do not consider maxHeight, 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.
+ */
+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 page as it loads in.
+ $(self.iframe.$element.get(0)).contents().find('body.overlay').animate({opacity: 0.9999}, 'slow');
+ }
+ });
+};
+
+})(jQuery);
diff --git modules/overlay/overlay.api.php modules/overlay/overlay.api.php
new file mode 100644
index 0000000..6136d17
--- /dev/null
+++ modules/overlay/overlay.api.php
@@ -0,0 +1,50 @@
+ 'overlay');
+ }
+ }
+}
+
+/**
+ * Implement hook_block_info_alter().
+ */
+function overlay_block_info_alter(&$blocks) {
+ if (overlay_mode() == OVERLAY_CHILD) {
+ // 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 (!($block->module == 'system' && in_array($block->delta, array('main', 'help')))) {
+ unset($blocks[$bid]);
+ }
+ }
+ }
+}
+
+/**
+ * Preprocess template variables for page.tpl.php.
+ */
+function overlay_preprocess_page(&$variables) {
+ if (overlay_mode() == OVERLAY_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();
+ // Remove 'Home' from the breadcrumbs and add the current page title at the end.
+ $overlay_breadcrumb = drupal_get_breadcrumb();
+ array_shift($overlay_breadcrumb);
+ if ($overlay_breadcrumb) {
+ $overlay_breadcrumb[] = $variables['title'];
+ }
+ $variables['breadcrumb'] = theme('breadcrumb', $overlay_breadcrumb);
+ }
+}
+
+/**
+ * Form after build callback.
+ *
+ * Ok, all hook_form_alter() have been processed. Now, if someone has enabled
+ * the global variable $GLOBALS['overlay_page_template'], then we want to
+ * scan the form structure in search of elements with submit handlers.
+ *
+ * @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;
+}
+
+/**
+ * Implement hook_node_insert().
+ */
+function overlay_node_insert($node) {
+ // If we are within the overlay, close the dialog. This cannot be done within
+ // hook_form_alter() because the node module forces the submit handlers to
+ // run before the node form submit handler, so the node would not be saved if
+ // the overlay forced the page to print and close the overlay and redirect
+ // early.
+ if (overlay_mode() == OVERLAY_CHILD) {
+ overlay_close_dialog(TRUE);
+ }
+}
+
+/**
+ * Implement hook_node_update().
+ */
+function overlay_node_update($node) {
+ // If we are within the overlay, close the dialog. This cannot be done within
+ // hook_form_alter() because the node module forces the submit handlers to
+ // run before the node form submit handler, so the node would not be saved if
+ // the overlay forced the page to print and close the overlay and redirect
+ // early.
+ if (overlay_mode() == OVERLAY_CHILD) {
+ overlay_close_dialog(TRUE);
+ }
+}
+
+/**
+ * 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) {
+ // Close the overlay only if specifically requested.
+ if (overlay_close_dialog()) {
+ $settings = array(
+ 'overlayChild' => array(
+ 'closeOverlay' => TRUE,
+ 'statusMessages' => theme('status_messages'),
+ 'redirect' => $form_state['redirect'] ? url($form_state['redirect']) : $form_state['redirect'],
+ ),
+ );
+ drupal_add_js($settings, array('type' => 'setting'));
+ // Print the page with no content to avoid a flash of the reloaded page in
+ // the overlay.
+ print drupal_render_page('');
+ drupal_page_footer();
+ exit();
+ }
+}
+
+/**
+ * Set overlay mode and add proper JavaScript and styles to the page.
+ *
+ * @param $mode
+ * To set the mode, pass in an overlay constant, either OVERLAY_PARENT or
+ * OVERLAY_CHILD. OVERLAY_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. OVERLAY_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;
+ $module_path = drupal_get_path('module', 'overlay');
+
+ switch ($mode) {
+ case OVERLAY_PARENT:
+ // Add required jQuery UI elements. Note that we don't use
+ // drupal_add_library() here, since we have no use for the CSS files added
+ // by the library.
+ drupal_add_js('misc/ui/ui.core.js', array('weight' => JS_LIBRARY + 5));
+ drupal_add_js('misc/ui/ui.dialog.js', array('weight' => JS_LIBRARY + 6));
+
+ drupal_add_css($module_path . '/overlay-parent.css');
+ drupal_add_js($module_path . '/overlay-parent.js');
+
+ // Allow modules to act upon overlay events.
+ module_invoke_all('overlay_parent_initialize');
+ break;
+
+ case OVERLAY_CHILD:
+ // Disable admin toolbar, which is something child windows don't need and
+ // shouldn't have.
+ if (module_exists('toolbar')) {
+ toolbar_enabled(FALSE);
+ }
+
+ // This is required to get access to jQuery UI extensions to jQuery itself,
+ // such as the ':focusable' and ':tabbable' selectors. No need for the whole
+ // library, so not using drupal_add_library().
+ drupal_add_js('misc/ui/ui.core.js', array('weight' => JS_LIBRARY + 5));
+
+ // Add JavaScript to the child page.
+ drupal_add_js($module_path . '/overlay-child.js');
+
+ // 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 boolean.
+ * @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;
+}
diff --git modules/toolbar/toolbar.module modules/toolbar/toolbar.module
index 15fe2b0..f8cf9c8 100644
--- modules/toolbar/toolbar.module
+++ modules/toolbar/toolbar.module
@@ -36,12 +36,30 @@ function toolbar_theme($existing, $type, $theme, $path) {
* 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 (enabled or disabled).
+ *
+ * @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.
diff --git profiles/default/default.info profiles/default/default.info
index 9b0ed6c..a6a8be0 100644
--- profiles/default/default.info
+++ profiles/default/default.info
@@ -14,6 +14,7 @@ dependencies[] = taxonomy
dependencies[] = dblog
dependencies[] = search
dependencies[] = toolbar
+dependencies[] = overlay
dependencies[] = field_ui
dependencies[] = file
files[] = default.profile
diff --git profiles/default/default.install profiles/default/default.install
index b692c14..251c865 100644
--- profiles/default/default.install
+++ profiles/default/default.install
@@ -210,5 +210,4 @@ function default_install() {
->condition('name', 'seven')
->execute();
variable_set('admin_theme', 'seven');
- variable_set('node_admin_theme', '1');
}
diff --git themes/garland/style.css themes/garland/style.css
index 208326d..e785b9b 100644
--- themes/garland/style.css
+++ themes/garland/style.css
@@ -508,6 +508,15 @@ body.two-sidebars #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;
}
diff --git themes/seven/style.css themes/seven/style.css
index 7d06230..bea3e80 100644
--- themes/seven/style.css
+++ themes/seven/style.css
@@ -55,7 +55,7 @@ legend {
*/
#branding {
overflow: hidden;
- padding: 20px 40px 0 40px;
+ padding: 20px 20px 0 20px;
position: relative;
background-color: #e0e0d8;
}
@@ -158,7 +158,7 @@ legend {
/**
* Console.
*/
-#page .console {
+#console {
border-top: 1px solid #ccc;
padding: 9px 0 10px;
}
@@ -261,7 +261,7 @@ ul.secondary li.active a.active {
}
#block-system-main {
- padding: 20px 0;
+ padding-bottom: 20px;
background: #fff;
color: #333;
}
@@ -611,9 +611,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;
@@ -713,18 +718,27 @@ body.overlay {
background: #fff;
}
-body.overlay #branding,
-body.overlay #page-title,
+body.overlay .primary,
+body.overlay .page-title,
body.overlay #page #left,
body.overlay #page #footer {
display: none;
}
+/* Branding needs a white background to look good in the overlay. */
+body.overlay #branding {
+ background-color: #ffffff;
+}
+
+body.overlay #branding {
+ padding-top: 5px;
+}
+
body.overlay #page {
margin: 0;
padding: 0;
}
-body.overlay #block-system-main {
+body.overlay #content {
padding: 20px;
}