Commit c9120083 authored by webchick's avatar webchick

#610234 by Gábor Hojtsy, ksenzee, cwgordon7, David_Rothstein, seutje,...

#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.
parent cfc96df9
......@@ -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.
*
......
......@@ -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');
}
/**
......
......@@ -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;
}
......@@ -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;
}
// $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
......@@ -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 {
......
......@@ -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.
......
......@@ -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.
*
......
// $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);
/* $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;
}
This diff is collapsed.
<?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".
*/
; $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
<?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'));
}
}
This diff is collapsed.
......@@ -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;
}
......
......@@ -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';
}
}
......@@ -165,6 +165,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).
*