Index: modules/popuppage/popuppage.info
===================================================================
RCS file: modules/popuppage/popuppage.info
diff -N modules/popuppage/popuppage.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/popuppage/popuppage.info	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,6 @@
+; $Id:$
+name = Popups
+description = General dialog creation utilities
+package = User Interface
+core = 6.x
+
Index: modules/popuppage/popuppage.module
===================================================================
RCS file: modules/popuppage/popuppage.module
diff -N modules/popuppage/popuppage.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/popuppage/popuppage.module	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,294 @@
+<?php
+// $Id:$
+
+/**
+ * @file popuppage.module
+ *
+ * This module uses the popup API to enhance the Administration Pages by allowing pages
+ *  to be shown inside modual dialogs.
+ * It also provides a hook_popups for other pages that want to use this functionality. 
+ *
+ * @todo 
+ * * Adding Javascript into popups doesn't always work.
+ * *   tabledrag onmouse up action. 
+ * *   user.js and teaser.js bugs.
+ * * Get cursor visible in Firefox 2: Ugly! https://bugzilla.mozilla.org/show_bug.cgi?id=167801
+ * * * Also: http://groups.google.com/group/jquery-ui/browse_thread/thread/7d448f5d1d2abd78/e7abb68dd7325952?#e7abb68dd7325952
+ * * * Might not be solvable before FF 3 comes out.
+ * * * Partial fix is :focus {background-color: #FFA}, but does not good for color-blind folk.
+ * * Cache the results of hook_popup.
+ * * Make the message-in-popup behavior configurable (?).
+ * * Taxonomy > Add vocab.  Adding second item to page does not trigger d-n-d transformation.
+ *     Might be a problem with Taxonomy.  Menus doesn't have problem (adds d-n-d on first item).
+ */
+
+
+// **************************************************************************
+// CORE HOOK FUNCTIONS   ****************************************************
+// **************************************************************************
+
+/**
+ * hook_menu
+ *
+ * @return array of new menu items.
+ */
+function popuppage_menu() {
+  $items['admin/settings/popuppage'] = array(
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('popuppage_admin_settings'),
+    'title' => 'Popups',
+    'description' => 'Configure the page-in-a-dialog behavior.',
+  );
+  return $items;
+}
+
+/**
+ * hook_init
+ * 
+ * Look at the page path and see if popup behavior has been requested for any links in this page.
+ */
+function popuppage_init() {  
+  $popups = popuppage_get_popups();
+  if (isset($popups[$_GET['q']])) {
+    popuppage_add_popups( $popups[$_GET['q']] );
+  }
+}
+
+/**
+ * hook_form_alter
+ * 
+ * Look at the form_id and see if popup behavior has been requested for any links in this form.
+ *
+ * @param form_array $form
+ * @param array $form_state
+ * @param str $form_id: 
+ */
+function popuppage_form_alter(&$form, $form_state, $form_id) {
+//  print $form_id;
+  // Add popup behavior to the form if requested.
+  $popups = popuppage_get_popups();
+  if (isset($popups[$form_id])) {
+    popuppage_add_popups( $popups[$form_id] );
+  }
+}
+
+// **************************************************************************
+// UTILITY FUNCTIONS   ******************************************************
+// **************************************************************************
+
+/**
+ * Build the list of popup rules from all modules that implement hook_popups.
+ * 
+ * @todo - Add some caching so we don't rebuild everytime.
+ */
+function popuppage_get_popups() {
+  static $popups = NULL;
+  if (!isset($popups)) {
+    $popups = module_invoke_all('popups');
+  }
+  return $popups;
+}
+
+/**
+ * Attach the popup behavior to the page.
+ * 
+ * The default behavoir of a popup is to open a form that will modify the original page.  The popup submits
+ * the form and reloads the original page with the resulting new content. The popup then replaces
+ * the original page's content area with the new copy of that content area.
+ *
+ * @param array $rule: Array of rules to apply to the page or form, keyed by jQuery link selector.
+ * Options:
+ *   noReload: Does the popup NOT modify the original page (Default: false).
+ *   updateTitle: Does the popup modify the title of the current page (Default: false).
+ *   surpressMessages: Don't show the messages the form returns in a popup (Default: false).
+ *   targetSelector: Defines the area on the original page that will be updated (Default: system-wide setting)
+ *   resultsSubselector: Defines the resulting new content to put into the targetSelector (Default: use the entire new results)
+ *       TODO - come up with a good use cases for resultsSubselector and targetSelector.
+ *   singleRow: Array of selectors descripting the elements inside a row to be replaced (Default: replace entire targetSelector)
+ *   additionalJavascript: Array of JavaScript files that must be included to correctly run the page in the popup. 
+ *   additionalCss: Array of CSS files that must be included to correctly style the page in the popup. 
+ *  
+ * Rule Format Example:
+ * 'admin/content/taxonomy' => array( // Act only on the links on this page. 
+ *   'div#tabs-wrapper a:eq(1)',  // No options, so use defaults. Note: Selector must select <a> element(s).
+ *   'table td:nth-child(2) a' => array( 
+ *     'noReload' => true, // Popup will not modify original page.
+ *   ),
+ * )
+ * 
+ */
+function popuppage_add_popups($rules=null) { 
+  static $added = FALSE;
+  if (!$added) {
+//    drupal_add_css(drupal_get_path('module', 'popups') .'/popups.css');
+    drupal_add_js('misc/popupapi.js', 'core');    
+    drupal_add_js(drupal_get_path('module', 'popuppage') .'/popuppage.js');
+    drupal_add_js('misc/jquery.form.js');
+    if (is_array($rules)) {
+      $settings = array( 'popups' => array(
+        'defaultTargetSelector' => variable_get('popups_content_selector', 'div.left-corner > div.clear-block:last'),
+        'defaultTitleSelector' => variable_get('popups_title_selector', 'div.left-corner > h2:eq(0)'),
+        'links' => array(),  
+      ));
+      foreach ($rules as $popup_selector => $options) { 
+        if (is_array($options)) {
+          $settings['popups']['links'][$popup_selector] = $options;
+          if (isset($options['additionalJavascript']) && is_array($options['additionalJavascript'])) {
+            foreach ($options['additionalJavascript'] as $file) {
+              drupal_add_js($file);
+            }
+          }
+          if (isset($options['additionalCss']) && is_array($options['additionalCss'])) {
+            foreach ($options['additionalCss'] as $file) {
+              drupal_add_css($file);
+            }
+          }
+        }
+        else {
+          $settings['popups']['links'][$options] = array();
+        }
+      }
+      drupal_add_js( $settings, 'setting' );
+    }
+    
+    $added = TRUE;
+  }
+}
+
+/**
+ * hook_popups
+ * 
+ * This implements hook_popups, defined in popuppage_get_popups.
+ * It adds page-in-popup behavior to the core admin pages.
+ * See the comments in popuppage_add_popups for explination of the options.
+ *
+ * @return: Array of link selectors to apply popup behavior to.
+ *          Keyed by path or form_id.
+ */
+function popuppage_popups() {
+  $operations = preg_replace('/[\W]+/', '-', strtolower(t('Operations')));
+  
+  return array(
+    'admin/build/block' => array( // Blocks admin page.
+      '#tabs-wrapper a[href$=admin/build/block/add]', // Add Block
+      '#blocks a[href~=admin/build/block/configure/block]', // configure
+      '#blocks a[href~=admin/build/block/delete]', // delete
+    ),
+    'admin/build/path' => array( // URL aliases admin page.
+      '#tabs-wrapper a[href$=admin/build/path/add]', // Add alias
+      'td:nth-child(3) a[href~=admin/build/path/edit]', // edit alias
+      'td:nth-child(4) a[href~=admin/build/path/delete]', // delete alias
+    ),
+    'admin/content/taxonomy' => array( // Taxonomy admin page.
+      // TODO: If there are not more than one items to start with, d-n-d files aren't loaded into page.
+      // This causes trouble when the 2nd item is added, no d-n-d.
+      // Might be bug in taxonomy table building (or at least inconsistancy).
+      '#tabs-wrapper a[href$=admin/content/taxonomy/add/vocabulary]' => array( // Add vocabulary
+        'additionalJavascript' => array('misc/tabledrag.js'),
+      ),       
+      '#taxonomy-overview-vocabularies td a:contains('. t('edit vocabulary') .')', // edit vocabulary
+      '#taxonomy-overview-vocabularies td a:contains('. t('list terms') .')' => array( // list terms
+        'noReload' => true,
+        'additionalJavascript' => array('misc/tabledrag.js'),
+      ),
+      '#taxonomy-overview-vocabularies td a:contains('. t('add terms') .')' => array(  // add terms
+        'noReload' => true,
+        'additionalJavascript' => array('misc/collapse.js'),
+      ),
+    ),
+    'admin/content/types' => array( // Content Type admin page
+      '#tabs-wrapper a[href$=admin/content/types/add]' => array(  // Add content type
+        'additionalJavascript' => array('misc/collapse.js'),
+      ),
+      'table td:nth-child(4) a, table td:nth-child(5) a' // edit, delete
+    ),
+    'admin/content/node' => array( // Existing Content admin page
+      '#node-admin-content td a:contains('. t('edit') .')' => array( // edit
+        'additionalJavascript' => array('misc/collapse.js'),
+        // TODO: teaser.js not working: Drupal.settings.teaser has no properties
+        // 'additionalJavascript' => array('misc/collapse.js', 'misc/teaser.js'),
+      )      
+    ),
+    'page_node_form' => array( // Node edit form
+      'a[href$=filter/tips]' => array( // Fixes insane "More information..." link
+        'noReload' => true,
+        //TODO 'addCloseButton' => true - just an idea.
+      )      
+    ),
+    'admin/content/comment' => array( // Comments admin page.
+      'table td:nth-child(2) a' => array( // view (TODO: popup too small)
+        'noReload' => true,
+        'additionalCss' => array(), //TODO: needs some custom css to get #comments to stay in the popup (no -25px margin).
+      ),
+      '#comment-admin-overview td a:contains('. t('edit') .')' => array( // edit
+        'additionalJavascript' => array('misc/collapse.js'),
+      ),
+    ),
+    'admin/user/rules' => array( // Access rules admin page.
+      '#tabs-wrapper a[href$=admin/user/rules/add]', // Add rule
+      'table td:nth-child(4) a, table td:nth-child(5) a', // edit, delete
+      '#tabs-wrapper a[href$=/admin/user/rules/check]' => array( // Check rule 
+        'noReload' => true,
+      ),
+    ),
+    'admin/user/user' => array( // Manage all users admin page.
+      //Add user (TODO: Can't test, keeps crashing apache!)
+      '#tabs-wrapper a[href$=admin/user/user/create]' => array(
+        // TODO: "translate has no properties" user.js line 16.
+        'additionalJavascript' => array(drupal_get_path('module', 'user') .'/user.js'),
+      ),
+      '#user-admin-account td:nth-child(2) a' => array( // View the user
+        'noReload' => true,
+      ),
+      
+    ),
+    'menu_overview_form' => array( // Menu admin form.
+      // Add Item, , edit, delete
+      '#tabs-wrapper a:eq(1), table#menu-overview td:nth-child(5) a, table#menu-overview td:nth-child(6) a',
+      '#tabs-wrapper a:eq(2)' => array( // Edit menu: update just page title.
+        'updateTitle' => true,
+        'noReload' => true,
+      ),
+    ),
+    
+    // CCK - Manage fields page.
+    'content_admin_field_overview_form' => array( 
+      'div#tabs-wrapper a:eq(0)' => array( // Edit
+        'updateTitle' => true,
+        'noReload' => true,
+      ),
+      'div#tabs-wrapper a:eq(2)' => array( // Display fields
+        'noReload' => true,
+      ),
+      'div#tabs-wrapper a:eq(3), div#tabs-wrapper a:eq(4)', // Add field, Add group
+      'table td:nth-child(5) a' => array( // configure 
+        'singleRow' => array( 'td:eq(0)' ), // Just update the row (still expiremental).
+      ),
+      'table td:nth-child(6) a', //  remove
+    ),
+  );
+}
+
+// **************************************************************************
+// ADMIN SETTINGS   *********************************************************
+// **************************************************************************
+
+function popuppage_admin_settings() {
+  drupal_set_title("Popups Settings");
+  $form = array();
+
+  $form['popuppage_title_selector'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Title Selector'),
+    '#default_value' => variable_get('popuppage_title_selector', 'div.left-corner > h2:eq(0)'),
+    '#description' => t("jQuery selector to define the pag'e title on your Admin theme."),
+  );
+  $form['popuppage_content_selector'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Content Selector'),
+    '#default_value' => variable_get('popuppage_content_selector', 'div.left-corner > div.clear-block:last'),
+    '#description' => t('jQuery selector to define the page\'s content area on your Admin theme.'),
+  );  
+   
+  return system_settings_form($form);
+}
Index: modules/popuppage/popuppage.js
===================================================================
RCS file: modules/popuppage/popuppage.js
diff -N modules/popuppage/popuppage.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/popuppage/popuppage.js	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,247 @@
+// $Id:$
+
+/**
+ * This module uses the Popup API to build dialogs that show the content from a Drupal page.
+ */
+
+
+/**
+ * Attach the popup bevior to the all the requested links on the page.
+ *
+ * @param context: The jQuery object to apply the behaviors to.
+ */
+Drupal.behaviors.popups = function(context) {
+  var popups = new Drupal.popups();
+  
+  // Add the popup-link-in-dialog behavior to links defined in Drupal.settings.popups.links array.
+  // TODO: how to handle popups-in-popups?
+  for (var link in Drupal.settings.popups.links) {
+    var options = Drupal.settings.popups.links[link];
+    popups.attach(context, link, options); // Needs to be seperate function for closure.
+  }
+};
+
+/**
+ * Attach the popup behavior to a particular link.
+ *
+ * @param link - link that was clicked.
+ * @param options - options associated with the link.
+ */
+Drupal.popups.prototype.attach = function(context, link, options) {
+  var popups = this;
+  $(link, context).click( function(e){ 
+    var a = this;
+    // If the option is distructive, check if the page is already modified, and offer to save.
+    var page_is_dirty = $('span.tabledrag-changed').size() > 0;
+    var will_modify_original = !options.noReload && !options.singleRow;
+    if( page_is_dirty && will_modify_original ) {
+      // The user will lose modifications, so popup dialog offering to save current state.
+      var body = Drupal.t("There are unsaved changes on this page, which you will lose if you continue.");
+      var buttons = {
+       'popups_save': {title: Drupal.t('Save Changes'), func: function(){popups.save_page(a, options)}},
+       'popups_submit': {title: Drupal.t('Continue'), func: function(){Drupal.popups.close(); popups.open_path(a, options)}},
+       'popups_cancel': {title: Drupal.t('Cancel'), func: Drupal.popups.close}
+      };
+      return popups.open( Drupal.t('Warning: Please Confirm'), body, buttons );
+    }
+    else {
+      return popups.open_path(a, options);
+    } 
+  });
+};
+
+
+/**
+ * Deal with the param string of a url to make the response popup friendly.
+ * Add 'unthemed=true' param.
+ * 
+ * @param url 
+ *   String: The original url.
+ * @return url 
+ *   String: The url with the corrected parameters.
+ */
+Drupal.popups.prepUrl = function(url) {
+  var u = Drupal.popups.parseUrl(url);  
+  // Add our param to the filtered array of existing params.
+  if (jQuery.inArray('unthemed=true', u.params) == -1) {
+    u.params.unshift('unthemed=true');
+  }  
+  // Rebuild the url with the new param, the old params and the old fragment.
+  return Drupal.popups.buildUrl(u);
+}
+
+/**
+ * Use Ajax to open the link in a popup window.
+ *
+ * @param a - link that was clicked.
+ * @param options - options associated with the link.
+ */
+Drupal.popups.prototype.open_path = function( a, options ) {
+  var popup = this;
+  // let the user know something is happening
+  $('body').css("cursor", "wait");
+  var $overlay = Drupal.popups.add_overlay(); 
+  Drupal.popups.add_loading();
+
+  var url = Drupal.popups.prepUrl(a.href); 
+  $.get(url, function(data) {
+     var $data = $(data);
+     var title = $data.find('#title').html();
+     var messages = $data.find('#messages').html();
+     var content = messages + $data.find('#content').html();
+     popup.open_content(title, content, options, a);
+     $('body').css("cursor", "auto"); // Return the cursor to normal state.
+  });        
+  return false;         
+};
+
+
+Drupal.popups.prototype.open_content = function(title, content, options, a) {
+  this.open(title, content); 
+  // Add behaviors to content in popup. 
+  // TODO: d-n-d: need to click to let go of selection.
+  Drupal.attachBehaviors($('#popups-body'));
+  // Adding collapse moves focus.
+  this.refocus();
+
+  // If the popup contains a form, capture submits.
+  var $form = $('form', '#popups-body');
+  $form.ajaxForm({      
+    beforeSubmit: Drupal.popups.beforeSubmit,
+    success: function(response, status) { Drupal.popups.formSuccess(response, options, a) },
+  });
+}
+
+/**
+ * Do before the form in the popup is submitted.
+ *
+ */
+Drupal.popups.beforeSubmit = function(form_data, $form, options) {
+  Drupal.popups.remove_popup(); // Remove just the dialog, but not the overlay.
+  Drupal.popups.add_loading();
+  // Send the original page back to Drupal with a flag to return the form results unthemed. 
+  options.url = Drupal.popups.prepUrl(options.url);
+};
+
+/**
+ * The form in the popup was successfully submitted
+ * Update the originating page.
+ * Show any messages in a popup (TODO - make this a configurable option).
+ * 
+ * @param response - specially formated page contents from server.
+ * @param options - hash of per link options.
+ * @param a - the link that was clicked.
+ */
+Drupal.popups.formSuccess = function (response, options, a) {
+  var $data = $(response);
+  if ($data.size() > 1) { // Bad html response, show an error message.
+    var $response = $('<div></div>');
+    $response.html( response );
+    var msg = 'Messages: ' + $('.messages', $response).text();
+    Drupal.popups.message('Error: Bad response.', msg);
+    Drupal.popups.remove_loading();
+  }
+  else { // Got a good response back from the server.
+    var messages = $data.find('#messages').html();
+        
+    // Are we at an end point, or just moving from one popup to another?
+    var path = $data.find('#path').text();
+    if (!location.href.match(path)) { // Not done yet, so show results in new popup.
+      var title = $data.find('#title').html();
+      var content = $data.find('#content').html();
+      Drupal.popups.remove_loading();
+      var popups = new Drupal.popups();     
+      popups.open_content(title, messages + content, options, a);
+    }
+    else { // Done, so show messages in dialog and embed the results in the original page.
+      if (messages) {
+        Drupal.popups.message(messages);
+	      // Also insert the message into the page above the content.
+	      // Might not be the standard spot, but it is the easiest to find.
+	      var $next = $(Drupal.settings.popups.defaultTargetSelector);
+	      $next.parent().find('div.messages').remove(); // Remove the current messages.
+	      $next.before(messages);
+	    }
+	        
+	    // Just update a single row out of a table (still expiremental). 
+	    // Loop through, with special case for first element.
+	    if (options.singleRow) {
+	      var href = $(a).attr('href');
+	      var selector = 'table a[href=' + href + ']';
+	      var $new_row = $data.find(selector).parents('tr'); // new tr
+	      var $target_row = $(selector).parents('tr'); // target tr.
+	        for (var i in options.singleRow) {
+	          var col = options.singleRow[i];
+	          $new_row.find(col).contents().not('div.indentation').wrapAll('<div id="newvalue"/>');
+	          $target_row.find(col).contents()
+	            .not('a.tabledrag-handle').not('span.warning').not('div.indentation')
+	            .wrapAll('<div id="killme"/>');
+	          $('#killme').replaceWith( $new_row.find('#newvalue').html() );
+	        }
+	    }
+	    // Update the entire content area (defined by 'target selector').
+	    else if (!options.noReload) { 
+	      var target = options.targetSelector;
+	      if (!target) {
+	        target = Drupal.settings.popups.defaultTargetSelector;
+	      }
+	      
+	      // Remove unthemed=true param from form's action. 
+	      var action = $data.find('form').attr('action');
+	      if (action) { 
+	        action = Drupal.popups.parseUrl(action);
+	        action.params = jQuery.grep(action.params, function(n, i){
+	          return n != 'unthemed=true';
+	        });
+	        $data.find('form').attr( 'action', Drupal.popups.buildUrl(action) );
+	      }
+	      
+	      // Update the original page.      
+	      var content = $data.find('#content').html();
+	      var $c = $(target).html(content); // Inject the new content into the page.
+	      Drupal.attachBehaviors($c);
+	    }
+	    
+	    // Update the title of the page.
+	    if (options.updateTitle) {
+	      var title = $data.find('#title').html();
+	      $(Drupal.settings.popups.defaultTitleSelector).html(title);
+	      document.title = title; // Also update the browser page title (TODO: include site name?).
+	    }
+	    
+	    // Done with changes to the original page, remove effects.
+	    Drupal.popups.remove_loading();
+	    if (!messages) { 
+	      // If there is not a messages popup remove the overlay.
+	      Drupal.popups.remove_overlay();
+		  }
+	  }  // End of updating original page.
+  } // End of good response.
+}; 
+
+/**
+ * Submit the page and reload the results, before popping up the real dialog.
+ *
+ * @param a - link that was clicked.
+ * @param options - options associated with the link.
+ */
+Drupal.popups.prototype.save_page = function(a, options) {
+  var popups = this;
+  // TODO - what if clicking on link with option['targetSelector']?
+  var target = Drupal.settings.popups.defaultTargetSelector;
+  var $form = $('form', target);
+  var ajaxOptions = {
+    beforeSubmit: Drupal.popups.beforeSubmit,
+    success: function(response, status) { // Sync up the current page contents with the submit.		  
+		  var $data = $(response);
+      var content = $data.find('#content').html();
+      var $c = $(target).html(content); // Inject the new content into the page.
+      Drupal.attachBehaviors($c);
+      Drupal.popups.close();
+      // The form has been saved, the page reloaded, now safe to show the link in a popup.
+      popups.open_path(a, options); 
+    }		  
+  };
+  $form.ajaxSubmit( ajaxOptions ); // Submit the form. 
+};
+
