diff --git a/includes/common.inc b/includes/common.inc
index e22ce998ac275bf9f065b582928389cf4381831f..9f8b6a66b81dd551db9473e609042a9b84ac02cb 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -3590,6 +3590,28 @@ function drupal_html_id($id) {
return $id;
}
+/**
+ * Provides a standard HTML class name that identifies a page region.
+ *
+ * It is recommended that template preprocess functions apply this class to any
+ * page region that is output by the theme (Drupal core already handles this in
+ * the standard template preprocess implementation). Standardizing the class
+ * names in this way allows modules to implement certain features, such as
+ * drag-and-drop or dynamic AJAX loading, in a theme-independent way.
+ *
+ * @param $region
+ * The name of the page region (for example, 'page_top' or 'content').
+ *
+ * @return
+ * An HTML class that identifies the region (for example, 'region-page-top'
+ * or 'region-content').
+ *
+ * @see template_preprocess_region()
+ */
+function drupal_region_class($region) {
+ return drupal_html_class("region-$region");
+}
+
/**
* Add a JavaScript file, setting or inline code to the page.
*
diff --git a/includes/menu.inc b/includes/menu.inc
index ecdd15075baa0cd59229333d043df318b0254dd7..934160684a28f5be95cadf37981855d7db6e87cd 100644
--- a/includes/menu.inc
+++ b/includes/menu.inc
@@ -2185,6 +2185,9 @@ function menu_cache_clear($menu_name = 'navigation') {
register_shutdown_function('cache_clear_all', 'links:' . $menu_name . ':', 'cache_menu', TRUE);
$cache_cleared[$menu_name] = 2;
}
+
+ // Also clear the menu system static caches.
+ menu_reset_static_cache();
}
/**
@@ -2193,6 +2196,16 @@ function menu_cache_clear($menu_name = 'navigation') {
*/
function menu_cache_clear_all() {
cache_clear_all('*', 'cache_menu', TRUE);
+ menu_reset_static_cache();
+}
+
+/**
+ * Resets the menu system static cache.
+ */
+function menu_reset_static_cache() {
+ drupal_static_reset('menu_tree');
+ drupal_static_reset('menu_tree_all_data');
+ drupal_static_reset('menu_tree_page_data');
}
/**
diff --git a/includes/path.inc b/includes/path.inc
index 3ce40a818b30d9694046e3eaadffe5a3f08a7765..12214daadb70af2a706bb2d7d5087422b36dc250 100644
--- a/includes/path.inc
+++ b/includes/path.inc
@@ -482,3 +482,65 @@ function path_delete($criteria) {
drupal_clear_path_cache();
}
+/**
+ * Determine whether a path is in the administrative section of the site.
+ *
+ * By default, paths are considered to be non-administrative. If a path does not
+ * match any of the patterns in path_get_admin_paths(), or if it matches both
+ * administrative and non-administrative patterns, it is considered
+ * non-administrative.
+ *
+ * @param $path
+ * A Drupal path.
+ * @return
+ * TRUE if the path is administrative, FALSE otherwise.
+ *
+ * @see path_get_admin_paths()
+ * @see hook_admin_paths()
+ * @see hook_admin_paths_alter()
+ */
+function path_is_admin($path) {
+ $path_map = &drupal_static(__FUNCTION__);
+ if (!isset($path_map['admin'][$path])) {
+ $patterns = path_get_admin_paths();
+ $path_map['admin'][$path] = drupal_match_path($path, $patterns['admin']);
+ $path_map['non_admin'][$path] = drupal_match_path($path, $patterns['non_admin']);
+ }
+ return $path_map['admin'][$path] && !$path_map['non_admin'][$path];
+}
+
+/**
+ * Get a list of administrative and non-administrative paths.
+ *
+ * @return array
+ * An associative array containing the following keys:
+ * 'admin': An array of administrative paths and regular expressions
+ * in a format suitable for drupal_match_path().
+ * 'non_admin': An array of non-administrative paths and regular expressions.
+ *
+ * @see hook_admin_paths()
+ * @see hook_admin_paths_alter()
+ */
+function path_get_admin_paths() {
+ $patterns = &drupal_static(__FUNCTION__);
+ if (!isset($patterns)) {
+ $paths = module_invoke_all('admin_paths');
+ drupal_alter('admin_paths', $paths);
+ // Combine all admin paths into one array, and likewise for non-admin paths,
+ // for easier handling.
+ $patterns = array();
+ $patterns['admin'] = array();
+ $patterns['non_admin'] = array();
+ foreach ($paths as $path => $enabled) {
+ if ($enabled) {
+ $patterns['admin'][] = $path;
+ }
+ else {
+ $patterns['non_admin'][] = $path;
+ }
+ }
+ $patterns['admin'] = implode("\n", $patterns['admin']);
+ $patterns['non_admin'] = implode("\n", $patterns['non_admin']);
+ }
+ return $patterns;
+}
diff --git a/includes/theme.inc b/includes/theme.inc
index 8fb6a5acb4e6af599118505086c7f6d7be5d7a2d..63af79711fc358692accd3d733367172153e0d9f 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -2580,6 +2580,7 @@ function template_preprocess_maintenance_page(&$variables) {
* pluggable template engine. Uses the region name to generate a template file
* suggestions. If none are found, the default region.tpl.php is used.
*
+ * @see drupal_region_class()
* @see region.tpl.php
*/
function template_preprocess_region(&$variables) {
@@ -2587,7 +2588,7 @@ function template_preprocess_region(&$variables) {
$variables['content'] = $variables['elements']['#children'];
$variables['region'] = $variables['elements']['#region'];
- $region = 'region-' . str_replace('_', '-', $variables['region']);
+ $region = drupal_region_class($variables['region']);
$variables['classes_array'][] = $region;
$variables['template_files'][] = $region;
}
diff --git a/misc/jquery.ba-bbq.js b/misc/jquery.ba-bbq.js
new file mode 100644
index 0000000000000000000000000000000000000000..73e64df4f519cdd56fbb2b9b7cffa5384436d59f
--- /dev/null
+++ b/misc/jquery.ba-bbq.js
@@ -0,0 +1,11 @@
+// $Id$
+
+/*
+ * jQuery BBQ: Back Button & Query Library - v1.0.2 - 10/10/2009
+ * http://benalman.com/projects/jquery-bbq-plugin/
+ *
+ * Copyright (c) 2009 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+(function($,c){var g,k=document.location,i=Array.prototype.slice,E=decodeURIComponent,a=$.param,m,d,p,n=$.bbq=$.bbq||{},o,e,z,b="hashchange",v="querystring",y="fragment",q="hash",x="elemUrlAttr",h="href",D="src",C=$.browser,l=C.msie&&C.version<8,j="on"+b in c&&!l,r=/^.*\?|#.*$/g,A=/^.*\#/,t={};function s(F){return typeof F==="string"}function w(G){var F=i.call(arguments,1);return function(){return G.apply(this,F.concat(i.call(arguments)))}}function f(G,O,F,H,K){var M,L,J,N,I;if(H!==g){J=F.match(G?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);I=J[3]||"";if(K===2&&s(H)){L=H.replace(O,"")}else{N=d(J[2]);H=s(H)?d[G?y:v](H):H;L=K===2?H:K===1?$.extend({},H,N):$.extend({},N,H);L=a(L)}M=J[1]+(G?"#":L||!J[1]?"?":"")+L+I}else{if(F){M=F.replace(O,"")}else{M=G?k[q]?k[h].replace(O,""):"":k.search.replace(/^\??/,"")}}return M}a[v]=w(f,0,r);a[y]=m=w(f,1,A);$.deparam=d=function(I,G){var H={},F={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(M,O){var L=O.split("="),P=E(L[0]),K,Q=H,N=0,R=P.split("]["),J=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[J])){R[J]=R[J].replace(/\]$/,"");R=R.shift().split("[").concat(R);J=R.length-1}else{J=0}if(L.length===2){K=E(L[1]);if(G){K=K&&!isNaN(K)?+K:K==="undefined"?g:F[K]!==g?F[K]:K}if(J){for(;N<=J;N++){P=R[N]===""?Q.length:R[N];Q=Q[P]=N').hide().appendTo("body")[0].contentWindow;J=function(){return F.document.location[q].replace(/^#/,"")};H=function(N,L){if(N!==L){var M=F.document;M.open();M.close();M.location[q]="#"+N}};H(m())}}G.start=function(){if(K){return}var M=m();H||I();(function L(){var O=m(),N=J(M);if(O!==M){H(M=O,N);$(c).trigger(b)}else{if(N!==M){o("#"+N)}}K=setTimeout(L,n.pollDelay)})()};G.stop=function(){if(!F){K&&clearTimeout(K);K=0}};return G})()})(jQuery,this);
\ No newline at end of file
diff --git a/modules/dashboard/dashboard.css b/modules/dashboard/dashboard.css
index d6bd6cd7026722f10fe4b4aa95d032b39dd9f14f..840d38ce3cb54e8bd76487a41bd31da1eeaa8567 100644
--- a/modules/dashboard/dashboard.css
+++ b/modules/dashboard/dashboard.css
@@ -64,9 +64,11 @@
border: 0;
}
-#dashboard .canvas-content input {
+#dashboard .canvas-content a.button {
float: right;
margin: 0 0 0 10px;
+ color: #5a5a5a;
+ text-decoration: none;
}
#dashboard .region {
diff --git a/modules/dashboard/dashboard.js b/modules/dashboard/dashboard.js
index 2b2c6cc45e3533f773296026c4d9e80598e53b7b..2199053170d655e74aa1aa9d0b244e013576fbc3 100644
--- a/modules/dashboard/dashboard.js
+++ b/modules/dashboard/dashboard.js
@@ -65,7 +65,7 @@ Drupal.behaviors.dashboard = {
* Helper for enterCustomizeMode; sets up drag-and-drop and close button.
*/
setupDrawer: function () {
- $('div.customize .canvas-content').prepend('');
+ $('div.customize .canvas-content').prepend('' + Drupal.t('Done') + '');
$('div.customize .canvas-content input').click(Drupal.behaviors.dashboard.exitCustomizeMode);
// Initialize drag-and-drop.
diff --git a/modules/node/node.module b/modules/node/node.module
index d91139f831725342206bab81d94b755d567f7378..24ca6c4c3bbdeff8132f958e96259f09970952ca 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -246,6 +246,20 @@ function node_field_build_modes($obj_type) {
return $modes;
}
+/**
+ * Implement hook_admin_paths().
+ */
+function node_admin_paths() {
+ $paths = array(
+ 'node/*/add' => TRUE,
+ 'node/*/edit' => TRUE,
+ 'node/*/delete' => TRUE,
+ 'node/add' => TRUE,
+ 'node/add/*' => TRUE,
+ );
+ return $paths;
+}
+
/**
* Gather a listing of links to nodes.
*
diff --git a/modules/overlay/images/close.png b/modules/overlay/images/close.png
new file mode 100644
index 0000000000000000000000000000000000000000..b76db1fcf7f303cb0e2be9b5f040c009cc96f200
--- /dev/null
+++ b/modules/overlay/images/close.png
@@ -0,0 +1,4 @@
+PNG
+
+
IHDR JL sBIT|d pHYs ~ tEXtCreation Time 09/09/2009 tEXtSoftware Adobe FireworksON SIDATHŖq0E2)N"W u-PJ:
+Bfn oG$=6͎xDu4;9l X_bEQ9GQ1v-UU zUU95*s:2r39'Au]QBP4=X])ɷ$,AI=X,KR !p8({VIP=(RzʦZ[3ncJ2[P*!D"I@R[33E轗
դwM|B{./A![6E.7P=_- IENDB`
\ No newline at end of file
diff --git a/modules/overlay/images/loading.gif b/modules/overlay/images/loading.gif
new file mode 100644
index 0000000000000000000000000000000000000000..57e45b8573880a25996c32d590a5f2bd70764dd1
--- /dev/null
+++ b/modules/overlay/images/loading.gif
@@ -0,0 +1 @@
+GIF89a% % Ž{{{sssccc!NETSCAPE2.0 ! , % % 'didDKDAGFSA$
1@P$$ ުytIv5qFqx1~ o~+4"/1$;D;z)i4T+Ok7; 4XrwQq46â¡L,
x?t,`{m*y
*r#}d;{ p! ! , '`Dq9|A ! , hAp%a_ ! , '`y! ! ,! `h( ! , '`Cq!| ! , @aIE ! ,
! G'F` ! , Pa1 ! , @a&j ! ,
W'hF` ! , F!D)! ! , P'hp9_ ! , g_ELsBƉ| ! , 7$'@Q4iT2Q ! ,
w$ErF! ! ,! ABd&iH ! , WEHs6i| ! , `,b13 ! ,
! DWDh!`& ! , 0EK ! , `0b&38j ! ,
DWD0 ! , 7 WD%hP|@ ;
\ No newline at end of file
diff --git a/modules/overlay/overlay-child.js b/modules/overlay/overlay-child.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b8e54a72eb8b5a78a08596a5e904d29ffce806e
--- /dev/null
+++ b/modules/overlay/overlay-child.js
@@ -0,0 +1,153 @@
+// $Id$
+
+(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 (!$.isObject(parent.Drupal) || !$.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;
+ }
+
+ // If one of the regions displaying outside the overlay needs to be
+ // reloaded, let the parent window know.
+ if (settings.refreshRegions) {
+ parent.Drupal.overlay.refreshRegions(settings.refreshRegions);
+ }
+
+ // Ok, now we can tell the parent window we're ready.
+ parent.Drupal.overlay.bindChild(window);
+
+ // If a form is being displayed, it has a hidden field for the parent
+ // window's location. Pass it that information. Letting the server side
+ // know the parent window's location lets us avoid unnecessary redirects
+ // when the overlay window is being closed automatically.
+ var re = new RegExp('^' + parent.Drupal.settings.basePath);
+ var path = parent.window.location.pathname.replace(re, '');
+ $('#edit-overlay-parent-url').val(path);
+
+ // Install onBeforeUnload callback, if module is present.
+ if ($.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);
+ }
+};
+
+/**
+ * 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. Non-administrative links should however close the overlay and
+ * redirect the parent page to the given link. This would include 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.
+ *
+ * @see Drupal.overlay.isAdminLink()
+ */
+Drupal.overlayChild.behaviors.parseLinks = function (context, settings) {
+ $('a:not(.overlay-exclude)', context).once('overlay').each(function () {
+ // Non-admin links should close the overlay and open in the main window.
+ if (!parent.Drupal.overlay.isAdminLink(this.href)) {
+ $(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;
+ }
+ else {
+ var href = $(this).attr('href');
+ if (href.indexOf('http') > 0 || href.indexOf('https') > 0) {
+ $(this).attr('target', '_new');
+ }
+ else {
+ $(this).each(function(){
+ this.href = parent.Drupal.overlay.fragmentizeLink(this);
+ }).click(function () {
+ parent.window.location.href = this.href;
+ return false;
+ });
+ }
+ }
+ });
+ $('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 a/modules/overlay/overlay-parent.css b/modules/overlay/overlay-parent.css
new file mode 100644
index 0000000000000000000000000000000000000000..d4c9566951756ce910a511e8d64bd9066db86409
--- /dev/null
+++ b/modules/overlay/overlay-parent.css
@@ -0,0 +1,130 @@
+/* $Id$ */
+
+/**
+ * ui-dialog overlay.
+ */
+.ui-widget-overlay {
+ background-color: #000;
+ opacity: 0.7;
+ filter: alpha(opacity=80);
+ background-image: none;
+}
+
+/**
+ * jQuery UI Dialog classes.
+ */
+.overlay {
+ padding-right: 26px;
+}
+
+.overlay.ui-widget-content, .overlay .ui-widget-header {
+ background: none;
+ border: none;
+}
+
+.overlay .ui-dialog-titlebar {
+ white-space: nowrap;
+ padding: 0 20px;
+}
+
+.overlay .ui-dialog-title {
+ font-family: Verdana,sans-serif;
+ margin: 0;
+ padding: 0.3em 0;
+ color: #fff;
+ font-size: 20px;
+}
+.overlay .ui-dialog-title:active,
+.overlay .ui-dialog-title:focus {
+ outline: 0;
+}
+.overlay .ui-dialog-titlebar-close,
+.overlay .ui-dialog-titlebar-close:hover {
+ display: block;
+ right: -25px;
+ top: 100%;
+ margin: 0;
+ border: none;
+ padding: 0;
+ width: 26px;
+ height: 36px;
+ background: transparent url(images/close.png) no-repeat;
+ -moz-border-radius-topleft: 0;
+ -webkit-border-top-left-radius: 0;
+}
+.overlay .ui-dialog-titlebar-close span {
+ display: none;
+}
+.overlay .ui-dialog-content {
+ color: #292929;
+ background-color: #f8f8f8;
+}
+
+/**
+ * Overlay content and shadows.
+ */
+.overlay #overlay-container {
+ margin: 0;
+ padding: 0;
+ overflow: visible;
+ 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 #overlay-element {
+ overflow: hidden;
+}
+
+/**
+ * Tabs on the overlay.
+ */
+.overlay .ui-dialog-titlebar ul {
+ position: absolute;
+ right: 20px;
+ bottom: 0;
+ margin: 0;
+ line-height: 27px;
+ text-transform: uppercase;
+}
+.overlay .ui-dialog-titlebar ul li {
+ display: inline-block;
+ list-style: none;
+ margin: 0 0 0 -3px;
+ padding: 0;
+}
+.overlay .ui-dialog-titlebar ul li a,
+.overlay .ui-dialog-titlebar ul li a:active,
+.overlay .ui-dialog-titlebar ul li a:visited,
+.overlay .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: 5px 14px;
+ text-decoration: none;
+ font-size: 11px;
+}
+.overlay .ui-dialog-titlebar ul li.active a,
+.overlay .ui-dialog-titlebar ul li.active a.active,
+.overlay .ui-dialog-titlebar ul li.active a:active,
+.overlay .ui-dialog-titlebar ul li.active a:visited {
+ background-color: #fff;
+ padding-bottom: 7px;
+}
+.overlay .ui-dialog-titlebar ul li a:hover {
+ color: #fff;
+}
+.overlay .ui-dialog-titlebar ul li.active a:hover {
+ color: #000;
+}
+
+/**
+ * Add to shortcuts link
+ */
+.overlay div.add-or-remove-shortcuts {
+ padding-top: 0.9em;
+}
diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js
new file mode 100644
index 0000000000000000000000000000000000000000..d0dae0a4e6f940a42ec481c159fe64261ef776c5
--- /dev/null
+++ b/modules/overlay/overlay-parent.js
@@ -0,0 +1,885 @@
+// $Id$
+
+(function ($) {
+
+/**
+ * Open the overlay, or load content into it, when an admin link is clicked.
+ */
+Drupal.behaviors.overlayParent = {
+ attach: function (context, settings) {
+ // Alter all admin links so that they will open in the overlay.
+ $('a', context).filter(function () {
+ return Drupal.overlay.isAdminLink(this.href);
+ })
+ .once('overlay')
+ .each(function () {
+ // Move the link destination to a URL fragment.
+ this.href = Drupal.overlay.fragmentizeLink(this);
+ });
+
+ // Simulate the native click event for all links that appear outside the
+ // overlay. jQuery UI Dialog prevents all clicks outside a modal dialog.
+ $('.overlay-displace-top a', context)
+ .add('.overlay-displace-bottom a', context)
+ .click(function () {
+ window.location.href = this.href;
+ });
+
+
+ // Resize the overlay when the toolbar drawer is toggled.
+ $('#toolbar a.toggle', context).once('overlay').click(function () {
+ setTimeout(function () {
+ Drupal.overlay.resize(Drupal.overlay.iframe.documentSize);
+ }, 150);
+
+ });
+
+ // Make sure the onhashchange handling below is only processed once.
+ if (this.processed) {
+ return;
+ }
+ this.processed = true;
+
+ // When the hash (URL fragment) changes, open the overlay if needed.
+ $(window).bind('hashchange', function (e) {
+ // If we changed the hash to reflect an internal redirect in the overlay,
+ // its location has already been changed, so don't do anything.
+ if ($.data(window.location, window.location.href) === 'redirect') {
+ $.data(window.location, window.location.href, null);
+ }
+ // Otherwise, change the contents of the overlay to reflect the new hash.
+ else {
+ Drupal.overlay.trigger();
+ }
+ });
+
+ // Trigger the hashchange event once, after the page is loaded, so that
+ // permalinks open the overlay.
+ $(window).trigger('hashchange');
+ }
+};
+
+/**
+ * Overlay object for parent windows.
+ */
+Drupal.overlay = Drupal.overlay || {
+ options: {},
+ iframe: { $container: null, $element: null },
+ isOpen: false
+};
+
+/**
+ * Open an overlay.
+ *
+ * Ensure that only one overlay is opened ever. Use Drupal.overlay.load() if
+ * the overlay is already open but a new page needs to be opened.
+ *
+ * @param options
+ * Properties of the overlay to open:
+ * - url: the URL of the page to open in the overlay.
+ * - width: width of the overlay in pixels.
+ * - height: height of the overlay in pixels.
+ * - autoFit: boolean indicating whether the overlay should be resized to
+ * fit the contents of the document loaded.
+ * - onOverlayOpen: callback to invoke when the overlay is opened.
+ * - onOverlayCanClose: callback to allow external scripts decide if the
+ * overlay can be closed.
+ * - onOverlayClose: callback to invoke when the overlay is closed.
+ * - customDialogOptions: an object with custom jQuery UI Dialog options.
+ *
+ * @return
+ * If the overlay was opened true, otherwise false.
+ */
+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),
+ onOverlayOpen: options.onOverlayOpen,
+ onOverlayCanClose: options.onOverlayCanClose,
+ onOverlayClose: options.onOverlayClose,
+ customDialogOptions: options.customDialogOptions || {}
+ }
+
+ self.options = $.extend(defaultOptions, options);
+
+ // Create the dialog and related DOM elements.
+ self.create();
+
+ // Open the dialog offscreen where we can set its size, etc.
+ var temp = self.iframe.$container.dialog('option', { position: ['-999em', '-999em'] }).dialog('open');;
+
+ return true;
+};
+
+/**
+ * Create the underlying markup and behaviors for the overlay.
+ *
+ * Reuses jQuery UI's dialog component to construct the overlay markup and
+ * behaviors, sanitizing the options previously set in self.options.
+ */
+Drupal.overlay.create = function () {
+ var self = this;
+
+ self.iframe.$element = $(Drupal.theme('overlayElement'));
+ self.iframe.$container = $(Drupal.theme('overlayContainer')).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) {}
+ // Allow the click event to propagate, to clear the hash state.
+ return true;
+ });
+
+ // Replace the title span element with an h1 element for accessibility.
+ $('.overlay .ui-dialog-title').replaceWith(Drupal.theme('overlayTitleHeader', $('.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;
+ }
+ // location.replace doesn't create a history entry. location.href does.
+ // In this case, we want location.replace, as we're creating the history
+ // entry using URL fragments.
+ 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 (!$.isObject(self.iframe.$container)) {
+ return;
+ }
+ self.beforeCloseEnabled = true;
+ self.iframe.$container.dialog('close');
+ if ($.isFunction(self.options.onOverlayClose)) {
+ self.options.onOverlayClose(args, statusMessages);
+ }
+ }
+ if (!$.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;
+ }
+
+ // Make sure the parent window URL matches the child window URL.
+ self.syncChildLocation($iFrameDocument[0].location);
+ // 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') }));
+
+ // If the shortcut add/delete button exists, move it to the dialog title.
+ var addToShortcuts = $('.add-or-remove-shortcuts', $iFrameDocument);
+ if (addToShortcuts.length) {
+ // Remove any existing shortcut button markup in the title section.
+ $('.ui-dialog-titlebar .add-or-remove-shortcuts').remove();
+ // Make the link overlay-friendly.
+ var $link = $('a', addToShortcuts);
+ $link.attr('href', Drupal.overlay.fragmentizeLink($link.get(0)));
+ // Move the button markup to the title section.
+ $('.overlay .ui-dialog-title').after(addToShortcuts);
+ }
+
+ // 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').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 link is in the administrative section of the site.
+ *
+ * @param url
+ * The url to be tested.
+ * @return boolean
+ * TRUE if the URL represents an administrative link, FALSE otherwise.
+ */
+Drupal.overlay.isAdminLink = function (url) {
+ var self = this;
+ // Create a native Link object, so we can use its object methods.
+ var link = $(url.link(url)).get(0);
+ var path = link.pathname.replace(new RegExp(Drupal.settings.basePath), '');
+ if (path == '') {
+ // If the path appears empty, it might mean the path is represented in the
+ // query string (clean URLs are not used).
+ var match = new RegExp("(\\?|&)q=(.+)(&|$)").exec(link.search);
+ if (match && match.length == 4) {
+ path = match[2];
+ }
+ }
+
+ // Turn the list of administrative paths into a regular expression.
+ if (!self.adminPathRegExp) {
+ var adminPaths = '^(' + Drupal.settings.overlay.paths.admin.replace(/\s+/g, ')$|^(') + ')$';
+ var nonAdminPaths = '^(' + Drupal.settings.overlay.paths.non_admin.replace(/\s+/g, ')$|^(') + ')$';
+ adminPaths = adminPaths.replace(/\*/g, '.*');
+ nonAdminPaths = nonAdminPaths.replace(/\*/g, '.*');
+ self.adminPathRegExp = new RegExp(adminPaths);
+ self.nonAdminPathRegExp = new RegExp(nonAdminPaths);
+ }
+
+ return self.adminPathRegExp.exec(path) && !self.nonAdminPathRegExp.exec(path);
+}
+
+/**
+ * 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 any region that should be visible above the overlay (such as
+ // an admin toolbar).
+ var $toolbar = $('.overlay-displace-top');
+ var toolbarHeight = 0;
+ $toolbar.each(function () {
+ toolbarHeight += $toolbar.height();
+ });
+ 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 ($.isObject(self.iframe.$element) && $.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');
+ }
+ });
+};
+
+/**
+ * Add overlay rendering GET parameter to the given href.
+ */
+Drupal.overlay.addOverlayParam = function (href) {
+ return $.param.querystring(href, {'render': 'overlay'});
+ // Do not process links with an empty href, or that only have the fragment or
+ // which are external links.
+ if (href.length > 0 && href.charAt(0) != '#' && href.indexOf('http') != 0 && href.indexOf('https') != 0) {
+ 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;
+ }
+ return href;
+};
+
+/**
+ * Open, reload, or close the overlay, based on the current URL fragment.
+ */
+Drupal.overlay.trigger = function () {
+ // Get the overlay URL from the current URL fragment.
+ var state = $.bbq.getState('overlay');
+ if (state) {
+ // 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 = Drupal.overlay.addOverlayParam(Drupal.settings.basePath + state);
+
+ // If the modal frame is already open, replace the loaded document with
+ // this new one.
+ if (Drupal.overlay.isOpen) {
+ Drupal.overlay.load(linkURL);
+ }
+ else {
+ // There is not an overlay opened yet; we should open a new one.
+ var overlayOptions = {
+ url: linkURL,
+ onOverlayClose: function () {
+ // Clear the overlay URL fragment.
+ $.bbq.pushState();
+ // Remove active class from all header buttons.
+ $('a.overlay-processed').each(function () {
+ $(this).removeClass('active');
+ });
+ },
+ draggable: false
+ };
+ Drupal.overlay.open(overlayOptions);
+ }
+ }
+ else {
+ // If there is no overlay URL in the fragment, close the overlay.
+ try {
+ Drupal.overlay.close();
+ }
+ catch(e) {
+ // The close attempt may have failed because the overlay isn't open.
+ // If so, no special handling is needed here.
+ }
+ }
+};
+
+/**
+ * Make a regular admin link into a URL that will trigger the overlay to open.
+ *
+ * @param link
+ * A Javascript Link object (i.e. an element).
+ * @return
+ * A URL that will trigger the overlay (in the form
+ * /node/1#overlay=admin/config).
+ */
+Drupal.overlay.fragmentizeLink = function (link) {
+ // Don't operate on links that are already overlay-ready.
+ var params = $.deparam.fragment(link.href);
+ if (params.overlay) {
+ return link.href;
+ }
+
+ // Determine the link's original destination, and make it relative to the
+ // Drupal site.
+ var fullpath = link.pathname;
+ var re = new RegExp('^' + Drupal.settings.basePath);
+ var path = fullpath.replace(re, '');
+ // Preserve existing query and fragment parameters in the URL.
+ var fragment = link.hash;
+ var querystring = link.search;
+ // If the query includes ?render=overlay, leave it out.
+ if (querystring.indexOf('render=overlay') !== -1) {
+ querystring = querystring.replace(/render=overlay/, '');
+ if (querystring === '?') {
+ querystring = '';
+ }
+ }
+
+ var destination = path + querystring + fragment;
+
+ // Assemble the overlay-ready link.
+ var base = window.location.href;
+ return $.param.fragment(base, {'overlay':destination});
+}
+
+/**
+ * Make sure the internal overlay URL is reflected in the parent URL fragment.
+ *
+ * Normally the parent URL fragment determines the overlay location. However, if
+ * the overlay redirects internally, the parent doesn't get informed, and the
+ * parent URL fragment will be out of date. This is a sanity check to make
+ * sure we're in the right place.
+ *
+ * @param childLocation
+ * The child window's location object.
+ */
+Drupal.overlay.syncChildLocation = function (childLocation) {
+ var expected = $.bbq.getState('overlay');
+ // This is just a sanity check, so we're comparing paths, not query strings.
+ expected = Drupal.settings.basePath + expected.replace(/\?.+/, '');
+ var actual = childLocation.pathname;
+ if (expected !== actual) {
+ // There may have been a redirect inside the child overlay window that the
+ // parent wasn't aware of. Update the parent URL fragment appropriately.
+ var newLocation = Drupal.overlay.fragmentizeLink(childLocation);
+ // Set a 'redirect' flag on the new location so the hashchange event handler
+ // knows not to change the overlay's content.
+ $.data(window.location, newLocation, 'redirect');
+ window.location.href = newLocation;
+ }
+};
+
+/**
+ * Refresh any regions of the page that are displayed outside the overlay.
+ *
+ * @param data
+ * An array of objects with information on the page regions to be refreshed.
+ * For each object, the key is a CSS class identifying the region to be
+ * refreshed, and the value represents the section of the Drupal $page array
+ * corresponding to this region.
+ */
+Drupal.overlay.refreshRegions = function (data) {
+ $.each(data, function () {
+ var region_info = this;
+ $.each(region_info, function (regionClass) {
+ var regionName = region_info[regionClass];
+ var regionSelector = '.' + regionClass;
+ $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + regionName, function (newElement) {
+ $(regionSelector).replaceWith($(newElement));
+ Drupal.attachBehaviors($(regionSelector), Drupal.settings);
+ });
+ });
+ });
+};
+
+/**
+ * Theme function to create the overlay iframe element.
+ */
+Drupal.theme.prototype.overlayElement = function () {
+ // 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.
+ return '';
+};
+
+/**
+ * Theme function to create a container for the overlay iframe element.
+ */
+Drupal.theme.prototype.overlayContainer = function () {
+ return '';
+}
+
+/**
+ * Theme function for the overlay title markup.
+ */
+Drupal.theme.prototype.overlayTitleHeader = function (text) {
+ return '