summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAngie Byron2009-12-02 07:28:22 (GMT)
committerAngie Byron2009-12-02 07:28:22 (GMT)
commitc912008307dd49b5e3e85c78069153f2f9f70411 (patch)
tree6e219858505bd38130a7d679488d3a41e689ce1d
parentcfc96df9a7181526cb1c71cf4e39886026c668fe (diff)
#610234 by Gábor Hojtsy, ksenzee, cwgordon7, David_Rothstein, seutje, marcvangend, sun, JoshuaRogers, markus_petrux, Bojhan, Rob Loach, Everett Zufelt, drifter, markboulton, leisareichelt, et al: Added Overlay module to core, which shows administrative pages in a JS overlay, retaining context on the front-end site.
-rw-r--r--includes/common.inc22
-rw-r--r--includes/menu.inc13
-rw-r--r--includes/path.inc62
-rw-r--r--includes/theme.inc3
-rw-r--r--misc/jquery.ba-bbq.js11
-rw-r--r--modules/dashboard/dashboard.css4
-rw-r--r--modules/dashboard/dashboard.js2
-rw-r--r--modules/node/node.module14
-rw-r--r--modules/overlay/images/close.pngbin0 -> 505 bytes
-rw-r--r--modules/overlay/images/loading.gifbin0 -> 1154 bytes
-rw-r--r--modules/overlay/overlay-child.js153
-rw-r--r--modules/overlay/overlay-parent.css130
-rw-r--r--modules/overlay/overlay-parent.js885
-rw-r--r--modules/overlay/overlay.api.php46
-rw-r--r--modules/overlay/overlay.info8
-rw-r--r--modules/overlay/overlay.install19
-rw-r--r--modules/overlay/overlay.module777
-rw-r--r--modules/shortcut/shortcut.css1
-rw-r--r--modules/shortcut/shortcut.module14
-rw-r--r--modules/system/system.api.php46
-rw-r--r--modules/system/system.module31
-rw-r--r--modules/toolbar/toolbar.css3
-rw-r--r--modules/toolbar/toolbar.js10
-rw-r--r--modules/toolbar/toolbar.module13
-rw-r--r--profiles/default/default.info1
-rw-r--r--themes/garland/style.css10
-rw-r--r--themes/seven/style.css57
27 files changed, 2306 insertions, 29 deletions
diff --git a/includes/common.inc b/includes/common.inc
index e22ce99..9f8b6a6 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -3591,6 +3591,28 @@ function drupal_html_id($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.
*
* The behavior of this function depends on the parameters it is called with.
diff --git a/includes/menu.inc b/includes/menu.inc
index ecdd150..9341606 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 3ce40a8..12214da 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 8fb6a5a..63af797 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 0000000..73e64df
--- /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<J?Q[P]||(R[N+1]&&isNaN(R[N+1])?{}:[]):K}}else{if($.isArray(H[P])){H[P].push(K)}else{if(H[P]!==g){H[P]=[H[P],K]}else{H[P]=K}}}}else{if(P){H[P]=G?g:""}}});return H};function u(I,H,G,F){if(G===g||typeof G==="boolean"){F=G;G=a[I]()}else{G=s(G)?G.replace(H,""):G}return d(G,F)}d[v]=w(u,v,r);d[y]=p=w(u,y,A);$[x]||($[x]=function(F){return $.extend(t,F)})({a:h,base:h,iframe:D,img:D,input:D,form:"action",link:h,script:D});e=$[x];function B(I,F,H,G){if(!s(H)&&typeof H!=="object"){G=H;H=F;F=g}return this.each(function(){var L=$(this),J=F||e()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,G))})}$.fn[v]=w(B,v);$.fn[y]=w(B,y);n.pushState=o=function(I,H){if(s(I)&&/^#/.test(I)&&H===g){H=2}var G=I!==g,F=m(k[h],G?I:{},G?H:2);k[h]=F+(/#/.test(F)?"":"#")};n.getState=function(G,F){return G===g||typeof G==="boolean"?p(G):p(F)[G]};n.pollDelay=100;$.event.special[b]={setup:function(){if(j){return false}z.start()},teardown:function(){if(j){return false}z.stop()},add:function(F,H,G){return function(J){var I=J[y]=m();J.getState=function(L,K){return L===g||typeof L==="boolean"?d(I,L):d(I,K)[L]};F.apply(this,arguments)}}};z=(function(){var G={},K,F,H,J;function I(){H=J=function(L){return L};if(l){F=$('<iframe src="javascript:0"/>').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 d6bd6cd..840d38c 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 2b2c6cc..2199053 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('<input type="button" class="form-submit" value="' + Drupal.t('Done') + '"></input>');
+ $('div.customize .canvas-content').prepend('<a class="button" href="">' + Drupal.t('Done') + '</a>');
$('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 d91139f..24ca6c4 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -247,6 +247,20 @@ function node_field_build_modes($obj_type) {
}
/**
+ * 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.
*
* @param $result
diff --git a/modules/overlay/images/close.png b/modules/overlay/images/close.png
new file mode 100644
index 0000000..b76db1f
--- /dev/null
+++ b/modules/overlay/images/close.png
Binary files differ
diff --git a/modules/overlay/images/loading.gif b/modules/overlay/images/loading.gif
new file mode 100644
index 0000000..57e45b8
--- /dev/null
+++ b/modules/overlay/images/loading.gif
Binary files differ
diff --git a/modules/overlay/overlay-child.js b/modules/overlay/overlay-child.js
new file mode 100644
index 0000000..1b8e54a
--- /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 0000000..d4c9566
--- /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 0000000..d0dae0a
--- /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 <a> 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 '<iframe id="overlay-element" frameborder="0" name="overlay-element"'+ ($.browser.msie ? ' scrolling="yes"' : '') +'/>';
+};
+
+/**
+ * Theme function to create a container for the overlay iframe element.
+ */
+Drupal.theme.prototype.overlayContainer = function () {
+ return '<div id="overlay-container"/>';
+}
+
+/**
+ * Theme function for the overlay title markup.
+ */
+Drupal.theme.prototype.overlayTitleHeader = function (text) {
+ return '<h1 id="ui-dialog-title-overlay-container" class="ui-dialog-title" tabindex="-1" unselectable="on">' + text + '</h1>';
+};
+
+/**
+ * Theme function for the shortcuts button next to the overlay title.
+ */
+Drupal.theme.prototype.overlayShortcutsButton = function (text) {
+ return '<div class="add-or-remove-shortcuts">' + text + '</div>';
+}
+
+})(jQuery);
diff --git a/modules/overlay/overlay.api.php b/modules/overlay/overlay.api.php
new file mode 100644
index 0000000..499c157
--- /dev/null
+++ b/modules/overlay/overlay.api.php
@@ -0,0 +1,46 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Hooks provided by Overlay module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Allow modules to act when an overlay parent window is initialized.
+ *
+ * The parent window is initialized when a page is displayed in which the
+ * overlay might be required to be displayed, so modules can act here if they
+ * need to take action to accomodate the possibility of the overlay appearing
+ * within a Drupal page.
+ */
+function hook_overlay_parent_initialize() {
+ // Add our custom JavaScript.
+ drupal_add_js(drupal_get_path('module', 'hook') . '/hook-overlay.js');
+}
+
+/**
+ * Allow modules to act when an overlay child window is initialized.
+ *
+ * The child window is initialized when a page is displayed from within the
+ * overlay, so modules can act here if they need to take action to work from
+ * within the confines of the overlay.
+ */
+function hook_overlay_child_initialize() {
+ // Use a different theme for content administration pages.
+ if (arg(0) == 'admin' && arg(1) == 'content') {
+ if ($theme = variable_get('content_administration_pages_theme', FALSE)) {
+ global $custom_theme;
+ $custom_theme = $theme;
+ }
+ }
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info
index e69de29..7afc059 100644
--- a/modules/overlay/overlay.info
+++ b/modules/overlay/overlay.info
@@ -0,0 +1,8 @@
+; $Id$
+name = Overlay
+description = Displays the Drupal administration interface in an overlay.
+package = Core
+version = VERSION
+core = 7.x
+files[] = overlay.module
+files[] = overlay.install
diff --git a/modules/overlay/overlay.install b/modules/overlay/overlay.install
new file mode 100644
index 0000000..a864b5e
--- /dev/null
+++ b/modules/overlay/overlay.install
@@ -0,0 +1,19 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Install, update and uninstall functions for the overlay module.
+ */
+
+/**
+ * Implements hook_enable().
+ *
+ * If the module is being enabled through the admin UI, and not from an
+ * install profile, reopen the modules page in an overlay.
+ */
+function overlay_enable() {
+ if (strpos(current_path(), 'admin/config/modules') === 0) {
+ drupal_goto('<front>', array('fragment' => 'overlay=admin/config/modules'));
+ }
+}
diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module
new file mode 100644
index 0000000..1015990
--- /dev/null
+++ b/modules/overlay/overlay.module
@@ -0,0 +1,777 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Displays the Drupal administration interface in an overlay.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function overlay_menu() {
+ $items['overlay-ajax/%'] = array(
+ 'title' => '',
+ 'page callback' => 'overlay_ajax_render_region',
+ 'page arguments' => array(1),
+ 'access arguments' => array('access overlay'),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function overlay_permission() {
+ return array(
+ 'access overlay' => array(
+ 'title' => t('Access the administrative overlay'),
+ 'description' => t('View administrative pages in the overlay.'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_init().
+ *
+ * Determine whether the current page request is destined to appear in the
+ * parent window or in the overlay window, and format the page accordingly.
+ *
+ * @see overlay_set_mode()
+ */
+function overlay_init() {
+ // @todo: custom_theme does not exist anymore.
+ global $custom_theme;
+ // Only act if the user has access to administration pages. Other modules can
+ // also enable the overlay directly for other uses of the JavaScript.
+ if (user_access('access overlay')) {
+ if (isset($_GET['render']) && $_GET['render'] == 'overlay') {
+ // If this page shouldn't be rendered here, redirect to the parent.
+ if (!path_is_admin($_GET['q'])) {
+ overlay_close_dialog();
+ }
+ // If system module did not switch the theme yet (i.e. this is not an
+ // admin page, per se), we should switch the theme here.
+ $admin_theme = variable_get('admin_theme', 0);
+ if ($custom_theme != $admin_theme) {
+ $custom_theme = $admin_theme;
+ drupal_add_css(drupal_get_path('module', 'system') . '/admin.css');
+ }
+ // Indicate that we are viewing an overlay child page.
+ overlay_set_mode('child');
+ }
+ else {
+ // Otherwise add overlay parent code and our behavior.
+ overlay_set_mode('parent');
+ }
+ }
+}
+
+/**
+ * Implements hook_exit().
+ *
+ * When viewing an overlay child page, check if we need to trigger a refresh of
+ * the supplemental regions of the overlay on the next page request.
+ */
+function overlay_exit() {
+ // Check that we are in an overlay child page. Note that this should never
+ // return TRUE on a cached page view, since the child mode is not set until
+ // overlay_init() is called.
+ if (overlay_get_mode() == 'child') {
+ // Load any markup that was stored earlier in the page request, via calls
+ // to overlay_store_rendered_content(). If none was stored, this is not a
+ // page request where we expect any changes to the overlay supplemental
+ // regions to have occurred, so we do not need to proceed any further.
+ $original_markup = overlay_get_rendered_content();
+ if (!empty($original_markup)) {
+ // Compare the original markup to the current markup that we get from
+ // rendering each overlay supplemental region now. If they don't match,
+ // something must have changed, so we request a refresh of that region
+ // within the parent window on the next page request.
+ foreach (overlay_supplemental_regions() as $region) {
+ if (!isset($original_markup[$region]) || $original_markup[$region] != overlay_render_region($region)) {
+ overlay_request_refresh($region);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function overlay_element_info_alter(&$types) {
+ foreach (array('submit', 'button', 'image_button', 'form') as $type) {
+ $types[$type]['#after_build'][] = 'overlay_form_after_build';
+ }
+}
+
+/**
+ * Implements hook_library().
+ */
+function overlay_library() {
+ $module_path = drupal_get_path('module', 'overlay');
+
+ // Overlay parent.
+ $libraries['parent'] = array(
+ 'title' => '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'),
+ array('system', 'jquery-bbq'),
+ ),
+ );
+ // 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;
+}
+
+/**
+ * Implements hook_form_alter().
+ *
+ * For forms displayed in the overlay, add a hidden form field that lets us pass
+ * the parent window's URL into the form.
+ */
+function overlay_form_alter(&$form, &$form_state, $form_id) {
+ if (overlay_get_mode() == 'child') {
+ $form['overlay_parent_url'] = array(
+ '#type' => 'hidden',
+ );
+ }
+}
+
+/**
+ * Implements hook_drupal_goto_alter().
+ *
+ * If the current page request is inside the overlay, add ?render=overlay to
+ * the new path, so that it appears correctly inside the overlay.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_drupal_goto_alter(&$path, &$options, &$http_response_code) {
+ if (overlay_get_mode() == 'child') {
+ if (isset($options['query'])) {
+ $options['query'] += array('render' => 'overlay');
+ }
+ else {
+ $options['query'] = array('render' => 'overlay');
+ }
+ }
+}
+
+/**
+ * Implements hook_batch_alter().
+ *
+ * If the current page request is inside the overlay, add ?render=overlay to
+ * the success callback URL, so that it appears correctly within the overlay.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_batch_alter(&$batch) {
+ if (overlay_get_mode() == 'child') {
+ if (isset($batch['url_options']['query'])) {
+ $batch['url_options']['query']['render'] = 'overlay';
+ }
+ else {
+ $batch['url_options']['query'] = array('render' => 'overlay');
+ }
+ }
+}
+
+/**
+ * Implements hook_page_alter().
+ */
+function overlay_page_alter(&$page) {
+ // If we are limiting rendering to a subset of page regions, deny access to
+ // all other regions so that they will not be processed.
+ if ($regions_to_render = overlay_get_regions_to_render()) {
+ $skipped_regions = array_diff(element_children($page), $regions_to_render);
+ foreach ($skipped_regions as $skipped_region) {
+ $page[$skipped_region]['#access'] = FALSE;
+ }
+ }
+}
+
+/**
+ * Implements hook_block_info_alter().
+ */
+function overlay_block_info_alter(&$blocks) {
+ // If we are limiting rendering to a subset of page regions, hide all blocks
+ // which appear in regions not on that list. Note that overlay_page_alter()
+ // does a more comprehensive job of preventing unwanted regions from being
+ // displayed (regardless of whether they contain blocks or not), but the
+ // reason for duplicating effort here is performance; we do not even want
+ // these blocks to be built if they are not going to be displayed.
+ if ($regions_to_render = overlay_get_regions_to_render()) {
+ foreach ($blocks as $bid => $block) {
+ if (!in_array($block->region, $regions_to_render)) {
+ unset($blocks[$bid]);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_system_info_alter().
+ *
+ * Add default regions for the overlay.
+ */
+function overlay_system_info_alter(&$info, $file, $type) {
+ if ($type == 'theme') {
+ $info['overlay_regions'][] = 'content';
+ $info['overlay_regions'][] = 'help';
+ }
+}
+
+/**
+ * Preprocess template variables for html.tpl.php.
+ *
+ * If the current page request is inside the overlay, add appropriate classes
+ * to the <body> element, and simplify the page title.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_preprocess_html(&$variables) {
+ if (overlay_get_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.
+ *
+ * Display breadcrumbs correctly inside the overlay.
+ *
+ * @see overlay_get_mode()
+ */
+function overlay_preprocess_page(&$variables) {
+ if (overlay_get_mode() == 'child') {
+ // Remove 'Home' from the breadcrumbs.
+ $overlay_breadcrumb = drupal_get_breadcrumb();
+ array_shift($overlay_breadcrumb);
+ $variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => $overlay_breadcrumb));
+ }
+}
+
+/**
+ * Preprocess template variables for toolbar.tpl.php.
+ *
+ * Adding the 'overlay-displace-top' class to the toolbar pushes the overlay
+ * down, so it appears below the toolbar.
+ */
+function overlay_preprocess_toolbar(&$variables) {
+ $variables['classes_array'][] = "overlay-displace-top";
+}
+
+/**
+ * Form after_build callback.
+ *
+ * After all hook_form_alter() implementations have been processed, we look at
+ * the list of submit handlers and add our own at the end. The added handler
+ * determines whether or not the user is redirected done at the end of form
+ * processing, so that it's possible to close the overlay after submitting
+ * a form.
+ *
+ * @see _form_builder_handle_input_element()
+ * @see _form_builder_ie_cleanup()
+ * @see form_execute_handlers()
+ * @see form_builder()
+ * @see overlay_form_submit()
+ *
+ * @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 to set
+ // $form_state['redirect'] to FALSE if the API function
+ // overlay_request_dialog_close() 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.
+ *
+ * @see overlay_get_mode()
+ * @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_request_dialog_close();
+
+ // Close the overlay if the overlay module has been disabled
+ if (!module_exists('overlay')) {
+ $args = overlay_request_dialog_close(TRUE);
+ }
+
+ // If there is a form redirect to a non-admin page, close the overlay.
+ if (isset($form_state['redirect'])) {
+ // A destination set in the URL trumps $form_state['redirect'].
+ if (isset($_GET['destination'])) {
+ $url = $_GET['destination'];
+ $url_settings = array();
+ }
+ elseif (is_array($form_state['redirect'])) {
+ $url = $form_state['redirect'][0];
+ $url_settings = $form_state['redirect'][1];
+ }
+ else {
+ $url = $form_state['redirect'];
+ $url_settings = array();
+ }
+ if (!path_is_admin($url)) {
+ $args = overlay_request_dialog_close(TRUE);
+ }
+ }
+
+ // If the overlay is to be closed, pass that information through JavaScript.
+ 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($url, $settings);
+ }
+ // If the redirect destination is the same as the parent window, just
+ // close the overlay without redirecting the parent.
+ if (url($form['overlay_parent_url']['#value']) == $settings['overlayChild']['redirect']) {
+ unset($settings['overlayChild']['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;
+ }
+}
+
+/**
+ * Get the current overlay mode.
+ *
+ * @see overlay_set_mode()
+ */
+function overlay_get_mode() {
+ return overlay_set_mode(NULL);
+}
+
+/**
+ * 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 window (a regular browser window), and JavaScript
+ * is added so that administrative links in the parent window will open in
+ * an overlay. 'child' is used in the context of the child overlay window (the
+ * page actually appearing within the overlay iframe) and JavaScript and CSS
+ * are 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_set_mode($mode = NULL) {
+ global $base_path;
+ $overlay_mode = &drupal_static(__FUNCTION__);
+
+ // Make sure external resources are not included more than once. Also return
+ // the 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');
+ drupal_add_library('overlay', 'jquery-bbq');
+
+ // 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;
+}
+
+/**
+ * Implements hook_overlay_parent_initialize().
+ */
+function overlay_overlay_parent_initialize() {
+ // Let the client side know which paths are administrative.
+ $paths = path_get_admin_paths();
+ foreach ($paths as &$type) {
+ $type = str_replace('<front>', variable_get('site_frontpage', 'node'), $type);
+ }
+ drupal_add_js(array('overlay' => array('paths' => $paths)), 'setting');
+ // Pass along the AJAX callback for rerendering sections of the parent window.
+ drupal_add_js(array('overlay' => array('ajaxCallback' => 'overlay-ajax')), 'setting');
+}
+
+/**
+ * Implements hook_overlay_child_initialize().
+ */
+function overlay_overlay_child_initialize() {
+ // Check if the parent window needs to refresh any page regions on this page
+ // request.
+ overlay_trigger_regions_to_refresh();
+ // If this is a POST request, or a GET request with a token parameter, we
+ // have an indication that something in the supplemental regions of the
+ // overlay might change during the current page request. We therefore store
+ // the initial rendered content of those regions here, so that we can compare
+ // it to the same content rendered in overlay_exit(), at the end of the page
+ // request. This allows us to check if anything actually did change, and, if
+ // so, trigger an AJAX refresh of the parent window.
+ if (!empty($_POST) || isset($_GET['token'])) {
+ foreach (overlay_supplemental_regions() as $region) {
+ overlay_store_rendered_content($region, overlay_render_region($region));
+ }
+ }
+ // Indicate that when the main page rendering occurs later in the page
+ // request, only the regions that appear within the overlay should be
+ // rendered.
+ overlay_set_regions_to_render(overlay_regions());
+}
+
+/**
+ * Callback to request that the overlay close on the next page load.
+ *
+ * @param $value
+ * By default, the dialog will not close. Set to TRUE or a value evaluating to
+ * TRUE to request the dialog to close. Use FALSE to disable closing the
+ * dialog (if it was previously enabled). The value passed will be forwarded
+ * to the onOverlayClose callback of the overlay.
+ *
+ * @return
+ * The current overlay close dialog mode, a value evaluating to TRUE if the
+ * overlay should close or FALSE if it should not (default).
+ */
+function overlay_request_dialog_close($value = NULL) {
+ $close = &drupal_static(__FUNCTION__, FALSE);
+ if (isset($value)) {
+ $close = $value;
+ }
+ return $close;
+}
+
+/**
+ * Close the overlay and redirect the parent window to a new path.
+ *
+ * @param $redirect
+ * The path that should open in the parent window after the overlay closes.
+ */
+function overlay_close_dialog($redirect = NULL) {
+ if (empty($redirect)) {
+ $path = $_GET['q'];
+ }
+ $settings = array(
+ 'overlayChild' => array(
+ 'closeOverlay' => TRUE,
+ 'statusMessages' => theme('status_messages'),
+ 'args' => $args,
+ 'redirect' => url($redirect),
+ ),
+ );
+ drupal_add_js($settings, array('type' => 'setting'));
+ return $settings;
+}
+
+/**
+ * Returns a list of page regions that appear in the overlay.
+ *
+ * Overlay regions correspond to the entire contents of the overlay child
+ * window and are refreshed each time a new page request is made within the
+ * overlay.
+ *
+ * @return
+ * An array of region names that correspond to those which appear in the
+ * overlay, within the theme that is being used to display the current page.
+ *
+ * @see overlay_supplemental_regions()
+ */
+function overlay_regions() {
+ return _overlay_region_list('overlay_regions');
+}
+
+/**
+ * Returns a list of supplemental page regions for the overlay.
+ *
+ * Supplemental overlay regions are those which are technically part of the
+ * parent window, but appear to the user as being related to the overlay
+ * (usually because they are displayed next to, rather than underneath, the
+ * main overlay regions) and therefore need to be dynamically refreshed if any
+ * administrative actions taken within the overlay change their contents.
+ *
+ * An example of a typical overlay supplemental region would be the 'page_top'
+ * region, in the case where a toolbar is being displayed there.
+ *
+ * @return
+ * An array of region names that correspond to supplemental overlay regions,
+ * within the theme that is being used to display the current page.
+ *
+ * @see overlay_regions()
+ */
+function overlay_supplemental_regions() {
+ return _overlay_region_list('overlay_supplemental_regions');
+}
+
+/**
+ * Helper function for returning a list of page regions related to the overlay.
+ *
+ * @param $type
+ * The type of regions to return. This can either be 'overlay_regions' or
+ * 'overlay_supplemental_regions'.
+ *
+ * @return
+ * An array of region names of the given type, within the theme that is being
+ * used to display the current page.
+ *
+ * @see overlay_regions()
+ * @see overlay_supplemental_regions()
+ */
+function _overlay_region_list($type) {
+ // Obtain the current theme. We need to first make sure the theme system is
+ // initialized, since this function can be called early in the page request.
+ drupal_theme_initialize();
+ $themes = list_themes();
+ $theme = $themes[$GLOBALS['theme']];
+ // Return the list of regions stored within the theme's info array, or an
+ // empty array if no regions of the appropriate type are defined.
+ return !empty($theme->info[$type]) ? $theme->info[$type] : array();
+}
+
+/**
+ * Returns a list of page regions that rendering should be limited to.
+ *
+ * @return
+ * An array containing the names of the regions that will be rendered when
+ * drupal_render_page() is called. If empty, then no limits will be imposed,
+ * and all regions of the page will be rendered.
+ *
+ * @see overlay_page_alter()
+ * @see overlay_block_info_alter()
+ * @see overlay_set_regions_to_render()
+ */
+function overlay_get_regions_to_render() {
+ return overlay_set_regions_to_render();
+}
+
+/**
+ * Sets the regions of the page that rendering will be limited to.
+ *
+ * @param $regions
+ * (Optional) An array containing the names of the regions that should be
+ * rendered when drupal_render_page() is called. Pass in an empty array to
+ * remove all limits and cause drupal_render_page() to render all page
+ * regions (the default behavior). If this parameter is omitted, no change
+ * will be made to the current list of regions to render.
+ *
+ * @return
+ * The current list of regions to render, or an empty array if the regions
+ * are not being limited.
+ *
+ * @see overlay_page_alter()
+ * @see overlay_block_info_alter()
+ * @see overlay_get_regions_to_render()
+ */
+function overlay_set_regions_to_render($regions = NULL) {
+ $regions_to_render = &drupal_static(__FUNCTION__, array());
+ if (isset($regions)) {
+ $regions_to_render = $regions;
+ }
+ return $regions_to_render;
+}
+
+/**
+ * Renders an individual page region.
+ *
+ * This function is primarily intended to be used for checking the content of
+ * supplemental overlay regions (e.g., a region containing a toolbar). Passing
+ * in a region that is intended to display the main page content is not
+ * supported; the region will be rendered by this function, but the main page
+ * content will not appear in it.
+ *
+ * @param $region
+ * The name of the page region that should be rendered.
+ *
+ * @return
+ * The rendered HTML of the provided region.
+ */
+function overlay_render_region($region) {
+ // Indicate the region that we will be rendering, so that other regions will
+ // be hidden by overlay_page_alter() and overlay_block_info_alter().
+ overlay_set_regions_to_render(array($region));
+ // Do what is necessary to force drupal_render_page() to only display HTML
+ // from the requested region. Specifically, declare that the main page
+ // content does not need to automatically be added to the page, and pass in
+ // a page array that has all theme functions removed (so that overall HTML
+ // for the page will not be added either).
+ $system_main_content_added = &drupal_static('system_main_content_added');
+ $system_main_content_added = TRUE;
+ $page = array(
+ '#type' => 'page',
+ '#theme' => NULL,
+ '#theme_wrappers' => array(),
+ );
+ $markup = drupal_render_page($page);
+ // Indicate that the main page content has not, in fact, been displayed, so
+ // that future calls to drupal_render_page() will be able to render it
+ // correctly.
+ $system_main_content_added = FALSE;
+ // Restore the original behavior of rendering all regions for the next time
+ // drupal_render_page() is called.
+ overlay_set_regions_to_render(array());
+ return $markup;
+}
+
+/**
+ * Returns any rendered content that was stored earlier in the page request.
+ *
+ * @return
+ * An array of all rendered HTML that was stored earlier in the page request,
+ * keyed by the identifier with which it was stored. If no content was
+ * stored, an empty array is returned.
+ *
+ * @see overlay_store_rendered_content()
+ */
+function overlay_get_rendered_content() {
+ return overlay_store_rendered_content();
+}
+
+/**
+ * Stores strings representing rendered HTML content.
+ *
+ * This function is used to keep a static cache of rendered content that can be
+ * referred to later in the page request.
+ *
+ * @param $id
+ * (Optional) An identifier for the content which is being stored, which will
+ * be used as an array key in the returned array. If omitted, no change will
+ * be made to the current stored data.
+ * @param $content
+ * (Optional) A string representing the rendered data to store. This only has
+ * an effect if $id is also provided.
+ *
+ * @return
+ * An array representing all data that is currently being stored, or an empty
+ * array if there is none.
+ *
+ * @see overlay_get_rendered_content()
+ */
+function overlay_store_rendered_content($id = NULL, $content = NULL) {
+ $rendered_content = &drupal_static(__FUNCTION__, array());
+ if (isset($id)) {
+ $rendered_content[$id] = $content;
+ }
+ return $rendered_content;
+}
+
+/**
+ * Request that the parent window refresh a particular page region.
+ *
+ * @param $region
+ * The name of the page region to refresh. The parent window will trigger a
+ * refresh of this region on the next page load.
+ *
+ * @see overlay_trigger_regions_to_refresh()
+ * @see Drupal.overlay.refreshRegions()
+ */
+function overlay_request_refresh($region) {
+ $class = drupal_region_class($region);
+ $_SESSION['overlay_regions_to_refresh'][] = array($class => $region);
+}
+
+/**
+ * Check if the parent window needs to refresh any regions on this page load.
+ *
+ * If the previous page load requested that any page regions be refreshed, pass
+ * that request via JavaScript to the child window, so it can in turn pass the
+ * request to the parent window.
+ *
+ * @see overlay_request_refresh()
+ * @see Drupal.overlay.refreshRegions()
+ */
+function overlay_trigger_regions_to_refresh() {
+ if (!empty($_SESSION['overlay_regions_to_refresh'])) {
+ $settings = array(
+ 'overlayChild' => array(
+ 'refreshRegions' => $_SESSION['overlay_regions_to_refresh'],
+ ),
+ );
+ drupal_add_js($settings, array('type' => 'setting'));
+ unset($_SESSION['overlay_regions_to_refresh']);
+ }
+}
+
+/**
+ * Prints the markup obtained by rendering a single region of the page.
+ *
+ * This function is intended to be called via AJAX.
+ *
+ * @param $region
+ * The name of the page region to render.
+ *
+ * @see Drupal.overlay.refreshRegions()
+ */
+function overlay_ajax_render_region($region) {
+ print overlay_render_region($region);
+}
diff --git a/modules/shortcut/shortcut.css b/modules/shortcut/shortcut.css
index 3fc4514..b07f6a0 100644
--- a/modules/shortcut/shortcut.css
+++ b/modules/shortcut/shortcut.css
@@ -7,7 +7,6 @@ div#toolbar div.toolbar-shortcuts ul {
padding: 5px 0;
height: 40px;
line-height: 30px;
- overflow: hidden;
float: left;
margin-left:5px;
}
diff --git a/modules/shortcut/shortcut.module b/modules/shortcut/shortcut.module
index 92bd58c..225cb8e 100644
--- a/modules/shortcut/shortcut.module
+++ b/modules/shortcut/shortcut.module
@@ -330,6 +330,7 @@ function shortcut_set_assign_user($shortcut_set, $account) {
->key(array('uid' => $account->uid))
->fields(array('set_name' => $shortcut_set->set_name))
->execute();
+ drupal_static_reset('shortcut_current_displayed_set');
}
/**
@@ -600,3 +601,16 @@ function shortcut_preprocess_page(&$variables) {
$variables['add_or_remove_shortcut'] = drupal_render($variables['page']['add_or_remove_shortcut']);
}
}
+
+/**
+ * Implements hook_system_info_alter().
+ *
+ * If the overlay module is enabled, indicate that the link for adding or
+ * removing shortcuts is one of the page "regions" that should display in the
+ * overlay.
+ */
+function shortcut_system_info_alter(&$info, $file, $type) {
+ if (module_exists('overlay') && $type == 'theme') {
+ $info['overlay_regions'][] = 'add_or_remove_shortcut';
+ }
+}
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 2464396..f85c014 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -166,6 +166,52 @@ function hook_entity_load($entities, $type) {
}
/**
+ * Define administrative paths.
+ *
+ * Modules may specify whether or not the paths they define in hook_menu() are
+ * to be considered administrative. Other modules may use this information to
+ * display those pages differently (e.g. in a modal overlay, or in a different
+ * theme).
+ *
+ * To change the administrative status of menu items defined in another module's
+ * hook_menu(), modules should implement hook_admin_paths_alter().
+ *
+ * @return
+ * An associative array. For each item, the key is the path in question, in
+ * a format acceptable to drupal_match_path(). The value for each item should
+ * be TRUE (for paths considered administrative) or FALSE (for non-
+ * administrative paths).
+ *
+ * @see hook_menu()
+ * @see drupal_match_path()
+ * @see hook_admin_paths_alter()
+ */
+function hook_admin_paths() {
+ $paths = array(
+ 'mymodule/*/add' => TRUE,
+ 'mymodule/*/edit' => TRUE,
+ );
+ return $paths;
+}
+
+/**
+ * Redefine administrative paths defined by other modules.
+ *
+ * @param $paths
+ * An associative array of administrative paths, as defined by implementations
+ * of hook_admin_paths().
+ *
+ * @see hook_admin_paths()
+ */
+function hook_admin_paths_alter(&$paths) {
+ // Treat all user pages as administrative.
+ $paths['user'] = TRUE;
+ $paths['user/*'] = TRUE;
+ // Treat the forum topic node form as a non-administrative page.
+ $paths['node/add/forum'] = FALSE;
+}
+
+/**
* Perform periodic actions.
*
* This hook will only be called if cron.php is run (e.g. by crontab).
diff --git a/modules/system/system.module b/modules/system/system.module
index 33219ed..1d4d90f 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -1082,6 +1082,16 @@ function system_library() {
),
);
+ // jQuery BBQ plugin.
+ $libraries['jquery-bbq'] = array(
+ 'title' => 'jQuery BBQ',
+ 'website' => 'http://benalman.com/projects/jquery-bbq-plugin/',
+ 'version' => '1.0.2',
+ 'js' => array(
+ 'misc/jquery.ba-bbq.js',
+ ),
+ );
+
// Contextual links.
$libraries['contextual-links'] = array(
'title' => 'Contextual links',
@@ -3069,13 +3079,16 @@ function system_page_build(&$page) {
'#markup' => theme('system_run_cron_image', array('image_path' => 'system/run-cron-image')),
);
}
+}
- // Find all block regions so they can be rendered.
+/**
+ * Implements hook_page_alter().
+ */
+function system_page_alter(&$page) {
+ // Find all non-empty page regions, and add a theme wrapper function that
+ // allows them to be consistently themed.
$regions = system_region_list($GLOBALS['theme']);
-
- // Load all region content assigned via blocks.
foreach (array_keys($regions) as $region) {
- // Don't render empty regions.
if (!empty($page[$region])) {
$page[$region]['#theme_wrappers'][] = 'region';
$page[$region]['#region'] = $region;
@@ -3661,3 +3674,13 @@ function system_build_contextual_links($element) {
return $build;
}
+/**
+ * Implement hook_admin_paths().
+ */
+function system_admin_paths() {
+ $paths = array(
+ 'admin' => TRUE,
+ 'admin/*' => TRUE,
+ );
+ return $paths;
+}
diff --git a/modules/toolbar/toolbar.css b/modules/toolbar/toolbar.css
index 2e1894f..9f9838d 100644
--- a/modules/toolbar/toolbar.css
+++ b/modules/toolbar/toolbar.css
@@ -36,7 +36,7 @@ div#toolbar {
left: 0;
right: 0;
top: 0;
- z-index: 100;
+ z-index: 600;
}
div#toolbar .collapsed {
@@ -71,7 +71,6 @@ div#toolbar div.toolbar-menu {
height: 25px;
line-height: 20px;
padding: 5px 10px 0;
- overflow: hidden;
position: relative;
}
diff --git a/modules/toolbar/toolbar.js b/modules/toolbar/toolbar.js
index 9c07f78..18b3661 100644
--- a/modules/toolbar/toolbar.js
+++ b/modules/toolbar/toolbar.js
@@ -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');
+ }
+ });
}
};
diff --git a/modules/toolbar/toolbar.module b/modules/toolbar/toolbar.module
index ddb1a45..f668cb1 100644
--- a/modules/toolbar/toolbar.module
+++ b/modules/toolbar/toolbar.module
@@ -134,6 +134,19 @@ function toolbar_preprocess_html(&$vars) {
}
/**
+ * Implements hook_system_info_alter().
+ *
+ * If the overlay module is enabled, indicate that the 'page_top' region (in
+ * which the toolbar will be displayed) is one of the overlay supplemental
+ * regions that should be refreshed whenever its content is updated.
+ */
+function toolbar_system_info_alter(&$info, $file, $type) {
+ if (module_exists('overlay') && $type == 'theme') {
+ $info['overlay_supplemental_regions'][] = 'page_top';
+ }
+}
+
+/**
* Build the admin menu as a structured array ready for drupal_render().
*/
function toolbar_build() {
diff --git a/profiles/default/default.info b/profiles/default/default.info
index 100a6e1..aced644 100644
--- a/profiles/default/default.info
+++ b/profiles/default/default.info
@@ -16,6 +16,7 @@ dependencies[] = dblog
dependencies[] = search
dependencies[] = shortcut
dependencies[] = toolbar
+dependencies[] = overlay
dependencies[] = field_ui
dependencies[] = file
dependencies[] = rdf
diff --git a/themes/garland/style.css b/themes/garland/style.css
index 0f92c75..51cfedb 100644
--- a/themes/garland/style.css
+++ b/themes/garland/style.css
@@ -574,6 +574,16 @@ div#branding strong {
margin-bottom: 2em;
}
+/* 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;
+}
+
/**
* Primary navigation
*/
diff --git a/themes/seven/style.css b/themes/seven/style.css
index c3c4742..053a6dd 100644
--- a/themes/seven/style.css
+++ b/themes/seven/style.css
@@ -101,7 +101,7 @@ legend {
*/
#branding {
overflow: hidden;
- padding: 20px 40px 0 40px;
+ padding: 20px 20px 0 20px;
position: relative;
background-color: #e0e0d8;
}
@@ -161,7 +161,7 @@ legend {
*/
div.messages {
padding: 9px;
- margin: 1em 0;
+ margin: 0.5em 0 0;
color: #036;
background: #bdf;
border: 1px solid #ace;
@@ -208,9 +208,8 @@ div.status {
/**
* Console.
*/
-#page .console {
- border-top: 1px solid #ccc;
- padding: 9px 0 10px;
+#console {
+ margin: 9px 0 10px;
}
/**
@@ -613,7 +612,7 @@ body div.form-type-radio div.description, body div.form-type-checkbox div.descri
}
/* Buttons */
-input.form-submit {
+input.form-submit, a.button {
cursor: pointer;
padding: 4px 17px;
color: #5a5a5a;
@@ -630,6 +629,11 @@ input.form-submit {
font-size: 1.1em;
}
+a.button:link, a.button:visited, a.button:hover, a.button:active {
+ text-decoration: none;
+ color: #5a5a5a;
+}
+
div.node-form input#edit-submit,
div.node-form input#edit-submit-1 {
border: 1px solid #8eB7cd;
@@ -687,9 +691,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;
@@ -853,24 +862,34 @@ ol.task-list li.done {
}
/* Overlay theming */
-body.overlay {
- background: #fff;
+.overlay #branding {
+ background-color: #fff;
+ padding-top: 15px;
}
-
-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;
- padding: 0;
}
-
-body.overlay #block-system-main {
- padding: 20px;
+.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;
}
/* Shortcut theming */