Added autosave functionality and resurrected the old toolbar code that was added about a year ago but never uesd.
authorDan
Tue, 12 Feb 2008 00:37:46 -0500
changeset 413 6607cd646d6d
parent 412 4746dd07cc48
child 414 818b4cd12b8b
Added autosave functionality and resurrected the old toolbar code that was added about a year ago but never uesd.
ajax.php
images/editor/diff.gif
images/editor/discard.gif
images/editor/preview.gif
images/editor/revert.gif
images/editor/save.gif
includes/clientside/css/enano-shared.css
includes/clientside/static/ajax.js
includes/clientside/static/dynano.js
includes/clientside/static/editor.js
includes/clientside/static/misc.js
includes/pageutils.php
includes/template.php
install/schemas/mysql_stage2.sql
install/schemas/postgresql_stage2.sql
install/schemas/upgrade/1.1.1-1.1.2-mysql.sql
install/schemas/upgrade/1.1.1-1.1.2-postgresql.sql
language/english/core.json
plugins/admin/SecurityLog.php
themes/oxygen/css/bleu.css
themes/oxygen/images/tb-bkg.gif
themes/oxygen/images/tb-frg.gif
themes/oxygen/images/tb-psh.gif
themes/oxygen/toolbar.tpl
--- a/ajax.php	Mon Feb 11 14:33:49 2008 -0500
+++ b/ajax.php	Tue Feb 12 00:37:46 2008 -0500
@@ -96,14 +96,26 @@
       echo PageUtils::checkusername($_GET['name']);
       break;
     case "getsource":
-      header('Content-type: application/json');
+      header('Content-type: text/plain');
       $password = ( isset($_GET['pagepass']) ) ? $_GET['pagepass'] : false;
       $revid = ( isset($_GET['revid']) ) ? intval($_GET['revid']) : 0;
       $page = new PageProcessor($paths->page_id, $paths->namespace, $revid);
       $page->password = $password;
+      $have_draft = false;
       if ( $src = $page->fetch_source() )
       {
         $allowed = true;
+        $q = $db->sql_query('SELECT author, time_id, page_text FROM ' . table_prefix . 'logs WHERE log_type = \'page\' AND action = \'edit\'
+                               AND page_id = \'' . $db->escape($paths->page_id) . '\'
+                               AND namespace = \'' . $db->escape($paths->namespace) . '\'
+                               AND is_draft = 1;');
+        if ( !$q )
+          $db->die_json();
+        
+        if ( $db->numrows() > 0 )
+        {
+          $have_draft = true;
+        }
       }
       else if ( $src !== false )
       {
@@ -127,9 +139,22 @@
           'time' => time(),
           'require_captcha' => false,
           'allow_wysiwyg' => $auth_wysiwyg,
-          'revid' => $revid
+          'revid' => $revid,
+          'have_draft' => false
         );
       
+      if ( $have_draft )
+      {
+        $row = $db->fetchrow($q);
+        $return['have_draft'] = true;
+        $return['draft_author'] = $row['author'];
+        $return['draft_time'] = enano_date('d M Y h:i a', intval($row['time_id']));
+        if ( isset($_GET['get_draft']) && @$_GET['get_draft'] === '1' )
+        {
+          $return['src'] = $row['page_text'];
+        }
+      }
+      
       if ( $revid > 0 )
       {
         // Retrieve information about this revision and the current one
@@ -138,25 +163,35 @@
     ON ( l2.time_id = ' . $revid . '
          AND l2.log_type  = \'page\'
          AND l2.action    = \'edit\'
-         AND l2.page_id   = \'ACL_Tests\'
-         AND l2.namespace = \'Article\'
+         AND l2.page_id   = \'' . $db->escape($paths->page_id)   . '\'
+         AND l2.namespace = \'' . $db->escape($paths->namespace) . '\'
         )
   WHERE l1.log_type  = \'page\'
     AND l1.action    = \'edit\'
-    AND l1.page_id   = \'ACL_Tests\'
-    AND l1.namespace = \'Article\'
+    AND l1.page_id   = \'' . $db->escape($paths->page_id)   . '\'
+    AND l1.namespace = \'' . $db->escape($paths->namespace) . '\'
     AND l1.time_id >= ' . $revid . '
   ORDER BY l1.time_id DESC;');
         if ( !$q )
           $db->die_json();
         
         $rev_count = $db->numrows() - 1;
-        $row = $db->fetchrow();
-        $return['undo_info'] = array(
-          'old_author'     => $row['oldrev_author'],
-          'current_author' => $row['currentrev_author'],
-          'undo_count'     => $rev_count
-        );
+        if ( $rev_count == -1 )
+        {
+          $return = array(
+              'mode' => 'error',
+              'error' => '[Internal] No rows returned by revision info query. SQL:<pre>' . $db->latest_query . '</pre>'
+            );
+        }
+        else
+        {
+          $row = $db->fetchrow();
+          $return['undo_info'] = array(
+            'old_author'     => $row['oldrev_author'],
+            'current_author' => $row['currentrev_author'],
+            'undo_count'     => $rev_count
+          );
+        }
       }
       
       if ( $auth_edit && !$session->user_logged_in && getConfig('guest_edit_require_captcha') == '1' )
@@ -165,6 +200,9 @@
         $return['captcha_id'] = $session->make_captcha();
       }
       
+      $template->load_theme();
+      $return['toolbar_templates'] = $template->extract_vars('toolbar.tpl');
+      
       echo enano_json_encode($return);
       break;
     case "getpage":
@@ -181,7 +219,7 @@
       $summ = ( isset($_POST['summary']) ) ? $_POST['summary'] : '';
       $minor = isset($_POST['minor']);
       $e = PageUtils::savepage($paths->page_id, $paths->namespace, $_POST['text'], $summ, $minor);
-      if($e=='good')
+      if ( $e == 'good' )
       {
         $page = new PageProcessor($paths->page_id, $paths->namespace);
         $page->send();
@@ -194,77 +232,126 @@
     case "savepage_json":
       header('Content-type: application/json');
       if ( !isset($_POST['r']) )
-        die('Invalid request');
+        die('Invalid request [1]');
       
       $request = enano_json_decode($_POST['r']);
-      if ( !isset($request['src']) || !isset($request['summary']) || !isset($request['minor_edit']) || !isset($request['time']) )
-        die('Invalid request');
+      if ( !isset($request['src']) || !isset($request['summary']) || !isset($request['minor_edit']) || !isset($request['time']) || !isset($request['draft']) )
+        die('Invalid request [2]<pre>' . htmlspecialchars(print_r($request, true)) . '</pre>');
       
       $time = intval($request['time']);
       
-      // Verify that no edits have been made since the editor was requested
-      $q = $db->sql_query('SELECT time_id, author FROM ' . table_prefix . "logs WHERE log_type = 'page' AND action = 'edit' AND page_id = '{$paths->page_id}' AND namespace = '{$paths->namespace}' ORDER BY time_id DESC LIMIT 1;");
-      if ( !$q )
-        $db->die_json();
-      
-      $row = $db->fetchrow();
-      $db->free_result();
-      
-      if ( $row['time_id'] > $time )
-      {
-        $return = array(
-          'mode' => 'obsolete',
-          'author' => $row['author'],
-          'date_string' => enano_date('d M Y h:i a', $row['time_id']),
-          'time' => $row['time_id'] // time() ???
-          );
-        echo enano_json_encode($return);
-        break;
-      }
-      
-      // Verify captcha, if needed
-      if ( !$session->user_logged_in && getConfig('guest_edit_require_captcha') == '1' )
+      if ( $request['draft'] )
       {
-        if ( !isset($request['captcha_id']) || !isset($request['captcha_code']) )
-        {
-          die('Invalid request, need captcha metadata');
-        }
-        $code_correct = strtolower($session->get_captcha($request['captcha_id']));
-        $code_input = strtolower($request['captcha_code']);
-        if ( $code_correct !== $code_input )
-        {
-          $return = array(
-            'mode' => 'errors',
-            'errors' => array($lang->get('editor_err_captcha_wrong')),
-            'new_captcha' => $session->make_captcha()
-          );
-          echo enano_json_encode($return);
-          break;
-        }
-      }
-      
-      // Verification complete. Start the PageProcessor and let it do the dirty work for us.
-      $page = new PageProcessor($paths->page_id, $paths->namespace);
-      if ( $page->update_page($request['src'], $request['summary'], ( $request['minor_edit'] == 1 )) )
-      {
+        //
+        // The user wants to save a draft version of the page.
+        //
+        
+        // Delete any draft copies if they exist
+        $q = $db->sql_query('DELETE FROM ' . table_prefix . 'logs WHERE log_type = \'page\' AND action = \'edit\'
+                               AND page_id = \'' . $db->escape($paths->page_id) . '\'
+                               AND namespace = \'' . $db->escape($paths->namespace) . '\'
+                               AND is_draft = 1;');
+        if ( !$q )
+          $db->die_json();
+        
+        $src = RenderMan::preprocess_text($request['src'], false, false);
+        
+        // Save the draft
+        $q = $db->sql_query('INSERT INTO ' . table_prefix . 'logs ( log_type, action, page_id, namespace, author, edit_summary, page_text, is_draft, time_id )
+                               VALUES (
+                                 \'page\',
+                                 \'edit\',
+                                 \'' . $db->escape($paths->page_id) . '\',
+                                 \'' . $db->escape($paths->namespace) . '\',
+                                 \'' . $db->escape($session->username) . '\',
+                                 \'' . $db->escape($request['summary']) . '\',
+                                 \'' . $db->escape($src) . '\',
+                                 1,
+                                 ' . time() . '
+                               );');
+        
+        // Done!
         $return = array(
-            'mode' => 'success'
+            'mode' => 'success',
+            'is_draft' => true
           );
       }
       else
       {
-        $errors = array();
-        while ( $err = $page->pop_error() )
+        // Verify that no edits have been made since the editor was requested
+        $q = $db->sql_query('SELECT time_id, author FROM ' . table_prefix . "logs WHERE log_type = 'page' AND action = 'edit' AND page_id = '{$paths->page_id}' AND namespace = '{$paths->namespace}' ORDER BY time_id DESC LIMIT 1;");
+        if ( !$q )
+          $db->die_json();
+        
+        $row = $db->fetchrow();
+        $db->free_result();
+        
+        if ( $row['time_id'] > $time )
         {
-          $errors[] = $err;
+          $return = array(
+            'mode' => 'obsolete',
+            'author' => $row['author'],
+            'date_string' => enano_date('d M Y h:i a', $row['time_id']),
+            'time' => $row['time_id'] // time() ???
+            );
+          echo enano_json_encode($return);
+          break;
         }
-        $return = array(
-          'mode' => 'errors',
-          'errors' => array_values($errors)
-          );
+        
+        // Verify captcha, if needed
         if ( !$session->user_logged_in && getConfig('guest_edit_require_captcha') == '1' )
         {
-          $return['new_captcha'] = $session->make_captcha();
+          if ( !isset($request['captcha_id']) || !isset($request['captcha_code']) )
+          {
+            die('Invalid request, need captcha metadata');
+          }
+          $code_correct = strtolower($session->get_captcha($request['captcha_id']));
+          $code_input = strtolower($request['captcha_code']);
+          if ( $code_correct !== $code_input )
+          {
+            $return = array(
+              'mode' => 'errors',
+              'errors' => array($lang->get('editor_err_captcha_wrong')),
+              'new_captcha' => $session->make_captcha()
+            );
+            echo enano_json_encode($return);
+            break;
+          }
+        }
+        
+        // Verification complete. Start the PageProcessor and let it do the dirty work for us.
+        $page = new PageProcessor($paths->page_id, $paths->namespace);
+        if ( $page->update_page($request['src'], $request['summary'], ( $request['minor_edit'] == 1 )) )
+        {
+          $return = array(
+              'mode' => 'success',
+              'is_draft' => false
+            );
+        }
+        else
+        {
+          $errors = array();
+          while ( $err = $page->pop_error() )
+          {
+            $errors[] = $err;
+          }
+          $return = array(
+            'mode' => 'errors',
+            'errors' => array_values($errors)
+            );
+          if ( !$session->user_logged_in && getConfig('guest_edit_require_captcha') == '1' )
+          {
+            $return['new_captcha'] = $session->make_captcha();
+          }
+        }
+        
+        // If this is based on a draft version, delete the draft - we no longer need it.
+        if ( @$request['used_draft'] )
+        {
+          $q = $db->sql_query('DELETE FROM ' . table_prefix . 'logs WHERE log_type = \'page\' AND action = \'edit\'
+                                 AND page_id = \'' . $db->escape($paths->page_id) . '\'
+                                 AND namespace = \'' . $db->escape($paths->namespace) . '\'
+                                 AND is_draft = 1;');
         }
       }
       
Binary file images/editor/diff.gif has changed
Binary file images/editor/discard.gif has changed
Binary file images/editor/preview.gif has changed
Binary file images/editor/revert.gif has changed
Binary file images/editor/save.gif has changed
--- a/includes/clientside/css/enano-shared.css	Mon Feb 11 14:33:49 2008 -0500
+++ b/includes/clientside/css/enano-shared.css	Tue Feb 12 00:37:46 2008 -0500
@@ -52,7 +52,7 @@
   opacity: 0.6;
   /*filter: alpha(opacity=60);*/
 }
-div.toolbar a:hover img {
+div.toolbar a:hover img, div.toolbar a:focus img {
   opacity: 1;
   /*filter: alpha(opacity=100);*/
 }
@@ -67,7 +67,7 @@
   max-height: 16px;
   text-decoration: none;
 }
-div.toolbar a:hover {
+div.toolbar a:hover, div.toolbar a:focus {
   border: 1px solid #202090;
   background-color: #ceceed;
   color: #000000;
@@ -649,3 +649,10 @@
   font-weight: bold;
 }
 
+div.noborderbottom * {
+  border-bottom-width: 0px;
+}
+
+div.nobordertop * {
+  border-top-width: 0px;
+}
--- a/includes/clientside/static/ajax.js	Mon Feb 11 14:33:49 2008 -0500
+++ b/includes/clientside/static/ajax.js	Tue Feb 12 00:37:46 2008 -0500
@@ -97,7 +97,7 @@
   else
   {
     customerror  = 'We unexpectedly received the following response from the server. The response should have been in the JSON ';
-    customerror += 'serialization format, but the response wasn\'t composed only of the JSON response. There are three possible triggers';
+    customerror += 'serialization format, but the response wasn\'t composed only of the JSON response. There are three possible triggers ';
     customerror += 'for this problem:';
     var el = document.createElement('p');
     el.appendChild(document.createTextNode(customerror));
--- a/includes/clientside/static/dynano.js	Mon Feb 11 14:33:49 2008 -0500
+++ b/includes/clientside/static/dynano.js	Tue Feb 12 00:37:46 2008 -0500
@@ -22,6 +22,7 @@
     this.switchToMCE = DN_switchToMCE;
     this.destroyMCE = DN_destroyMCE;
     this.getContent = DN_mceFetchContent;
+    this.setContent = DN_mceSetContent;
   }
 }
 function __DNObjGetHeight(o) {
@@ -148,6 +149,23 @@
   }
 }
 
+function DN_mceSetContent(text)
+{
+  if ( this.object.name )
+  {
+    this.object.value = text;
+    if ( tinyMCE.get(this.object.id) )
+    {
+      var editor = tinyMCE.get(this.object.id);
+      editor.setContent(text);
+    }
+  }
+  else
+  {
+    this.object.value = text;
+  }
+}
+
 // A basic Wikitext to XHTML converter
 function DN_WikitextToXHTML(text)
 {
--- a/includes/clientside/static/editor.js	Mon Feb 11 14:33:49 2008 -0500
+++ b/includes/clientside/static/editor.js	Tue Feb 12 00:37:46 2008 -0500
@@ -11,6 +11,11 @@
 
 var do_popups = ( is_Safari ) ? '' : ',inlinepopups';
 var _skin = ( typeof(tinymce_skin) == 'string' ) ? tinymce_skin : 'default';
+var editor_img_path = scriptPath + '/images/editor';
+
+// Idle time required for autosave, in seconds
+var AUTOSAVE_TIMEOUT = 15;
+var AutosaveTimeoutObj = null;
 
 var enano_tinymce_options = {
   mode : "none",
@@ -84,12 +89,12 @@
         // do we need to enter a captcha before saving the page?
         var captcha_hash = ( response.require_captcha ) ? response.captcha_id : false;
         
-        ajaxBuildEditor(response.src, (!response.auth_edit), response.time, response.allow_wysiwyg, captcha_hash, response.revid, response.undo_info);
+        ajaxBuildEditor((!response.auth_edit), response.time, response.allow_wysiwyg, captcha_hash, response.revid, response.undo_info, response);
       }
     });
 }
 
-function ajaxBuildEditor(content, readonly, timestamp, allow_wysiwyg, captcha_hash, revid, undo_info)
+function ajaxBuildEditor(readonly, timestamp, allow_wysiwyg, captcha_hash, revid, undo_info, response)
 {
   // Set flags
   // We don't want the fancy confirmation framework to trigger if the user is only viewing the page source
@@ -97,7 +102,7 @@
   {
     editor_open = true;
     disableUnload();
-  }  
+  }
   
   // Destroy existing contents of page container
   var edcon = document.getElementById('ajaxEditContainer');
@@ -106,6 +111,8 @@
     edcon.removeChild(edcon.childNodes[i]);
   }
   
+  var content = response.src;
+  
   //
   // BUILD EDITOR
   //
@@ -174,13 +181,22 @@
     return false;
   }
   
+  // Draft notice
+  if ( response.have_draft )
+  {
+    var dn = document.createElement('div');
+    dn.className = 'warning-box';
+    dn.id = 'ajax_edit_draft_notice';
+    dn.innerHTML = '<b>' + $lang.get('editor_msg_have_draft_title') + '</b><br />';
+    dn.innerHTML += $lang.get('editor_msg_have_draft_body', { author: response.draft_author, time: response.draft_time });
+  }
+  
   // Old-revision notice
   if ( revid > 0 )
   {
     var oldrev_box = document.createElement('div');
     oldrev_box.className = 'usermessage';
     oldrev_box.appendChild(document.createTextNode($lang.get('editor_msg_editing_old_revision')));
-    form.appendChild(oldrev_box);
   }
   
   // Preview holder
@@ -196,18 +212,12 @@
   ta_wrapper.style.margin = '10px 0';
   // ta_wrapper.style.clear = 'both';
   var textarea = document.createElement('textarea');
-  textarea.value = content;
-  textarea._edTimestamp = timestamp;
+  ta_wrapper.appendChild(textarea);
+  
   textarea.id = 'ajaxEditArea';
   textarea.rows = '20';
   textarea.cols = '60';
   textarea.style.width = '98.7%';
-  if ( readonly )
-  {
-    textarea.className = 'mce_readonly';
-    textarea.setAttribute('readonly', 'readonly');
-  }
-  ta_wrapper.appendChild(textarea);
   
   // Revision metadata controls
   var tblholder = document.createElement('div');
@@ -220,19 +230,22 @@
   if ( readonly )
   {
     // Close Viewer button
-    var tr3 = document.createElement('tr');
-    var td3 = document.createElement('th');
-    td3.setAttribute('colspan', '2');
-    td3.className = 'subhead';
+    var toolbar = '';
+    var head = new templateParser(response.toolbar_templates.toolbar_start);
+    var button = new templateParser(response.toolbar_templates.toolbar_button);
+    var tail = new templateParser(response.toolbar_templates.toolbar_end);
     
-    var btn_cancel = document.createElement('input');
-    btn_cancel.type = 'button';
-    btn_cancel.value = $lang.get('editor_btn_closeviewer');
-    btn_cancel.onclick = function() { ajaxReset(true); return false; };
-    td3.appendChild(btn_cancel);
-    tr3.appendChild(td3);
+    button.assign_bool({
+        show_title: true
+      });
     
-    metatable.appendChild(tr3);
+    // Button: close
+    button.assign_vars({
+        TITLE: $lang.get('editor_btn_closeviewer'),
+        IMAGE: editor_img_path + '/discard.gif',
+        FLAGS: 'href="#" onclick="ajaxReset(true); return false;"'
+      });
+    toolbar += button.run();
   }
   else
   {
@@ -332,49 +345,74 @@
     }
     
     // Third row: controls
-    var tr3 = document.createElement('tr');
-    var td3 = document.createElement('th');
-    td3.setAttribute('colspan', '2');
-    td3.className = 'subhead';
+    
+    var toolbar = '';
+    var head = new templateParser(response.toolbar_templates.toolbar_start);
+    var button = new templateParser(response.toolbar_templates.toolbar_button);
+    var label = new templateParser(response.toolbar_templates.toolbar_label);
+    var tail = new templateParser(response.toolbar_templates.toolbar_end);
+    
+    button.assign_bool({
+        show_title: true
+      });
+    
+    toolbar += head.run();
     
-    var btn_save = document.createElement('input');
-    btn_save.type = 'button';
-    btn_save.value = $lang.get('editor_btn_save');
-    btn_save.onclick = function() { ajaxEditorSave(); return false; };
-    td3.appendChild(btn_save);
-    
-    td3.appendChild(document.createTextNode(' '));
+    // Button: Save
+    button.assign_vars({
+        TITLE: $lang.get('editor_btn_save'),
+        IMAGE: editor_img_path + '/save.gif',
+        FLAGS: 'href="#" onclick="ajaxEditorSave(); return false;"'
+      });
+    toolbar += button.run();
     
-    var btn_preview = document.createElement('input');
-    btn_preview.type = 'button';
-    btn_preview.value = $lang.get('editor_btn_preview');
-    btn_preview.onclick = function() { ajaxEditorGenPreview(); return false; };
-    td3.appendChild(btn_preview);
-    
-    td3.appendChild(document.createTextNode(' '));
+    // Button: preview
+    button.assign_vars({
+        TITLE: $lang.get('editor_btn_preview'),
+        IMAGE: editor_img_path + '/preview.gif',
+        FLAGS: 'href="#" onclick="ajaxEditorGenPreview(); return false;"'
+      });
+    toolbar += button.run();
     
-    var btn_revert = document.createElement('input');
-    btn_revert.type = 'button';
-    btn_revert.value = $lang.get('editor_btn_revert');
-    btn_revert.onclick = function() { ajaxEditorRevertToLatest(); return false; };
-    td3.appendChild(btn_revert);
+    // Button: revert
+    button.assign_vars({
+        TITLE: $lang.get('editor_btn_revert'),
+          IMAGE: editor_img_path + '/revert.gif',
+        FLAGS: 'href="#" onclick="ajaxEditorRevertToLatest(); return false;"'
+      });
+    toolbar += button.run();
     
-    td3.appendChild(document.createTextNode(' '));
+    // Button: diff
+    button.assign_vars({
+        TITLE: $lang.get('editor_btn_diff'),
+        IMAGE: editor_img_path + '/diff.gif',
+        FLAGS: 'href="#" onclick="ajaxEditorShowDiffs(); return false;"'
+      });
+    toolbar += button.run();
     
-    var btn_diff = document.createElement('input');
-    btn_diff.type = 'button';
-    btn_diff.value = $lang.get('editor_btn_diff');
-    btn_diff.onclick = function() { ajaxEditorShowDiffs(); return false; };
-    td3.appendChild(btn_diff);
+    // Button: cancel
+    button.assign_vars({
+        TITLE: $lang.get('editor_btn_cancel'),
+        IMAGE: editor_img_path + '/discard.gif',
+        FLAGS: 'href="#" onclick="ajaxEditorCancel(); return false;"'
+      });
+    toolbar += button.run();
     
-    td3.appendChild(document.createTextNode(' '));
+    // Separator
+    label.assign_vars({
+        TITLE: ' '
+      });
+    toolbar += label.run();
     
-    var btn_cancel = document.createElement('input');
-    btn_cancel.type = 'button';
-    btn_cancel.value = $lang.get('editor_btn_cancel');
-    btn_cancel.onclick = function() { ajaxEditorCancel(); return false; };
-    td3.appendChild(btn_cancel);
-    tr3.appendChild(td3);
+    // Button: Save draft
+    button.assign_vars({
+        TITLE: $lang.get('editor_btn_savedraft'),
+        IMAGE: editor_img_path + '/savedraft.gif',
+        FLAGS: 'href="#" onclick="ajaxPerformAutosave(); return false;" id="ajax_edit_savedraft_btn"'
+      });
+    toolbar += button.run();
+    
+    toolbar += tail.run();
     
     metatable.appendChild(tr1);
     metatable.appendChild(tr2);
@@ -382,7 +420,7 @@
     {
       metatable.appendChild(tr4);
     }
-    metatable.appendChild(tr3);
+    // metatable.appendChild(tr3);
   }
   tblholder.appendChild(metatable);
   
@@ -399,10 +437,18 @@
   form.appendChild(heading);
   if ( allow_wysiwyg )
     form.appendChild(toggler);
+  
+  if ( dn )
+    form.appendChild(dn);
+  
+  if ( oldrev_box )
+    form.appendChild(oldrev_box);
+  
   form.appendChild(preview_anchor);
   form.appendChild(preview_container);
   form.appendChild(ta_wrapper);
   form.appendChild(tblholder);
+  form.innerHTML += '<div style="margin: 10px 0 0 0;">' + toolbar + '</div>';
   edcon.appendChild(form);
   
   if ( editNotice && !readonly )
@@ -410,37 +456,85 @@
     edcon.appendChild(en_div);
   }
   
+  // more textarea attribs/init
+  var textarea = document.getElementById('ajaxEditArea');
+  textarea.as_last_save = 0;
+  textarea.content_orig = content;
+  textarea.used_draft = false;
+  textarea.onkeyup = function()
+  {
+    if ( this.needReset )
+    {
+      var img = $('ajax_edit_savedraft_btn').object.getElementsByTagName('img')[0];
+      var lbl = $('ajax_edit_savedraft_btn').object.getElementsByTagName('span')[0];
+      img.src = editor_img_path + '/savedraft.gif';
+      lbl.innerHTML = $lang.get('editor_btn_savedraft');
+    }
+    if ( AutosaveTimeoutObj )
+      clearTimeout(AutosaveTimeoutObj);
+    AutosaveTimeoutObj = setTimeout('ajaxAutosaveDraft();', ( AUTOSAVE_TIMEOUT * 1000 ));
+  }
+  
+  if ( readonly )
+  {
+    textarea.className = 'mce_readonly';
+    textarea.setAttribute('readonly', 'readonly');
+  }
+  
   // If the editor preference is tinymce, switch the editor to TinyMCE now
   if ( readCookie('enano_editor_mode') == 'tinymce' && allow_wysiwyg )
   {
     $dynano('ajaxEditArea').switchToMCE();
   }
   $dynano('ajaxEditArea').object.focus();
+  $dynano('ajaxEditArea').object._edTimestamp = timestamp;
+  $dynano('ajaxEditArea').setContent(content);
+  
+  // Autosave every 5 minutes           (m  *  s  *  ms)
+  setInterval('ajaxPerformAutosave();', ( 5 * 60 * 1000 ));
 }
 
-function ajaxEditorSave()
+function ajaxEditorSave(is_draft)
 {
-  ajaxSetEditorLoading();
+  if ( !is_draft )
+    ajaxSetEditorLoading();
   var ta_content = $('ajaxEditArea').getContent();
   
-  if ( ta_content == '' || ta_content == '<p></p>' || ta_content == '<p>&nbsp;</p>' )
+  if ( !is_draft && ( ta_content == '' || ta_content == '<p></p>' || ta_content == '<p>&nbsp;</p>' ) )
   {
     new messagebox(MB_OK|MB_ICONSTOP, $lang.get('editor_err_no_text_title'), $lang.get('editor_err_no_text_body'));
     ajaxUnSetEditorLoading();
     return false;
   }
   
+  if ( is_draft )
+  {
+    // ajaxSetEditorLoading();
+    var img = $('ajax_edit_savedraft_btn').object.getElementsByTagName('img')[0];
+    var lbl = $('ajax_edit_savedraft_btn').object.getElementsByTagName('span')[0];
+    img.src = scriptPath + '/images/loading.gif';
+    var d = new Date();
+    var m = String(d.getMinutes());
+    if ( m.length < 2 )
+      m = '0' + m;
+    var time = d.getHours() + ':' + m;
+    lbl.innerHTML = $lang.get('editor_msg_draft_saving');
+  }
+  
   var edit_summ = $('enano_editor_field_summary').object.value;
   if ( !edit_summ )
     edit_summ = '';
   var is_minor = ( $('enano_editor_field_minor').object.checked ) ? 1 : 0;
   var timestamp = $('ajaxEditArea').object._edTimestamp;
+  var used_draft = $('ajaxEditArea').object.used_draft;
   
   var json_packet = {
     src: ta_content,
     summary: edit_summ,
     minor_edit: is_minor,
-    time: timestamp
+    time: timestamp,
+    draft: ( is_draft == true ),
+    used_draft: used_draft
   };
   
   // Do we need to add captcha info?
@@ -462,7 +556,6 @@
     {
       if ( ajax.readyState == 4 && ajax.status == 200 )
       {
-        ajaxUnSetEditorLoading();
         var response = String(ajax.responseText + '');
         if ( response.substr(0, 1) != '{' )
         {
@@ -509,27 +602,44 @@
         }
         if ( response.mode == 'success' )
         {
-          // The save was successful; reset flags and make another request for the new page content
-          setAjaxLoading();
-          editor_open = false;
-          enableUnload();
-          changeOpac(0, 'ajaxEditContainer');
-          ajaxGet(stdAjaxPrefix + '&_mode=getpage&noheaders', function()
-            {
-              if ( ajax.readyState == 4 && ajax.status == 200 )
+          if ( response.is_draft )
+          {
+            document.getElementById('ajaxEditArea').used_draft = true;
+            document.getElementById('ajaxEditArea').needReset = true;
+            var img = $('ajax_edit_savedraft_btn').object.getElementsByTagName('img')[0];
+            var lbl = $('ajax_edit_savedraft_btn').object.getElementsByTagName('span')[0];
+            img.src = scriptPath + '/images/mini-info.png';
+            var d = new Date();
+            var m = String(d.getMinutes());
+            if ( m.length < 2 )
+              m = '0' + m;
+            var time = d.getHours() + ':' + m;
+            lbl.innerHTML = $lang.get('editor_msg_draft_saved', { time: time });
+          }
+          else
+          {
+            // The save was successful; reset flags and make another request for the new page content
+            ajaxUnSetEditorLoading();
+            setAjaxLoading();
+            editor_open = false;
+            enableUnload();
+            changeOpac(0, 'ajaxEditContainer');
+            ajaxGet(stdAjaxPrefix + '&_mode=getpage&noheaders', function()
               {
-                unsetAjaxLoading();
-                selectButtonMajor('article');
-                unselectAllButtonsMinor();
-                
-                document.getElementById('ajaxEditContainer').innerHTML = '<div class="usermessage">' + $lang.get('editor_msg_saved') + '</div>' + ajax.responseText;
-                opacity('ajaxEditContainer', 0, 100, 1000);
-              }
-            });
+                if ( ajax.readyState == 4 && ajax.status == 200 )
+                {
+                  unsetAjaxLoading();
+                  selectButtonMajor('article');
+                  unselectAllButtonsMinor();
+                  
+                  document.getElementById('ajaxEditContainer').innerHTML = '<div class="usermessage">' + $lang.get('editor_msg_saved') + '</div>' + ajax.responseText;
+                  opacity('ajaxEditContainer', 0, 100, 1000);
+                }
+              });
+          }
         }
       }
     }, true);
-  
 }
 
 function ajaxEditorGenPreview()
@@ -594,15 +704,7 @@
           return false;
         }
         
-        var ed = tinyMCE.get('ajaxEditArea');
-        if ( ed )
-        {
-          ed.setContent(response.src);
-        }
-        else
-        {
-          $('ajaxEditArea').object.value = response.src;
-        }
+        $('ajaxEditArea').setContent(response.src);
       }
     }, true);
 }
@@ -747,3 +849,70 @@
   }
 }
 
+function ajaxAutosaveDraft()
+{
+  var aed = document.getElementById('ajaxEditArea');
+  if ( !aed )
+    return false;
+  var last_save = aed.as_last_save;
+  var now = unix_time();
+  if ( ( last_save + 120 ) < now && aed.value != aed.content_orig )
+  {
+    ajaxPerformAutosave();
+  }
+}
+
+function ajaxPerformAutosave()
+{
+  var aed = document.getElementById('ajaxEditArea');
+  if ( !aed )
+    return false;
+  var now = unix_time();
+  aed.as_last_save = now;
+  
+  var ta_content = $('ajaxEditArea').getContent();
+  
+  if ( ta_content == '' || ta_content == '<p></p>' || ta_content == '<p>&nbsp;</p>' )
+  {
+    return false;
+  }
+  
+  ajaxEditorSave(true);
+}
+
+function ajaxEditorUseDraft()
+{
+  var aed = document.getElementById('ajaxEditArea');
+  if ( !aed )
+    return false;
+  ajaxSetEditorLoading();
+  ajaxGet(stdAjaxPrefix + '&_mode=getsource&get_draft=1', function()
+    {
+      if ( ajax.readyState == 4 && ajax.status == 200 )
+      {
+        ajaxUnSetEditorLoading();
+        
+        var response = String(ajax.responseText + '');
+        if ( response.substr(0, 1) != '{' )
+        {
+          handle_invalid_json(response);
+          return false;
+        }
+        
+        response = parseJSON(response);
+        if ( response.mode == 'error' )
+        {
+          unselectAllButtonsMinor();
+          new messagebox(MB_OK | MB_ICONSTOP, $lang.get('editor_err_server'), response.error);
+          return false;
+        }
+        
+        $('ajaxEditArea').setContent(response.src);
+        $('ajaxEditArea').object.used_draft = true;
+        
+        var dn = $('ajax_edit_draft_notice').object;
+        dn.parentNode.removeChild(dn);
+      }
+    }, true);
+}
+
--- a/includes/clientside/static/misc.js	Mon Feb 11 14:33:49 2008 -0500
+++ b/includes/clientside/static/misc.js	Tue Feb 12 00:37:46 2008 -0500
@@ -397,7 +397,7 @@
 {
   // let's hope this gets the image cached
   var _ = new Image(32, 32); 
-  _.src = scriptPath + "/images/good.gif";
+  _.src = scriptPath + "/images/check.png";
   
   ajaxGet(makeUrlNS('Special', 'Login', 'act=getkey'), function() {
       if ( ajax.readyState == 4 && ajax.status == 200 )
@@ -626,7 +626,7 @@
           case 'success':
             var success_win = '<div align="center" style="text-align: center;"> \
                   <p>' + $lang.get('user_login_success_short') + '</p> \
-                  <p><img alt=" " src="'+scriptPath+'/images/good.gif" /></p> \
+                  <p><img alt=" " src="'+scriptPath+'/images/check.png" /></p> \
                 </div>';
             ajax_auth_mb_cache.updateContent(success_win);
             if ( typeof(ajax_auth_prompt_cache) == 'function' )
--- a/includes/pageutils.php	Mon Feb 11 14:33:49 2008 -0500
+++ b/includes/pageutils.php	Tue Feb 12 00:37:46 2008 -0500
@@ -431,9 +431,9 @@
         echo '<td class="' . $cls . '" style="text-align: center;">'. (( $r['minor_edit'] ) ? 'M' : '' ) .'</td>'."\n";
         
         // Actions!
-        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'oldid=' . $r['time_id']) . '" onclick="ajaxHistView(\'' . $r['time_id'] . '\'); return false;">' . $lang->get('history_action_view') . '</a></td>'."\n";
-        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>'."\n";
-        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'do=edit&amp;revid=' . $r['time_id']) . '" onclick="ajaxEditor(\'' . $r['time_id'] . '\'); return false;">' . $lang->get('history_action_restore') . '</a></td>'."\n";
+        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'oldid=' . $r['time_id']) . '" onclick="ajaxHistView(\'' . $r['time_id'] . '\'); return false;">' . $lang->get('history_action_view') . '</a></td>'."\n";
+        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>'."\n";
+        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'do=edit&amp;revid=' . $r['time_id']) . '" onclick="ajaxEditor(\'' . $r['time_id'] . '\'); return false;">' . $lang->get('history_action_restore') . '</a></td>'."\n";
         
         echo '</tr>'."\n"."\n";
         
@@ -502,8 +502,8 @@
         echo '</td>';
         
         // Actions!
-        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>';
-        echo '<td class="' . $cls . '" style="text-align: center;"><a href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id=' . $r['time_id']) . '" onclick="ajaxRollback(\'' . $r['time_id'] . '\'); return false;">' . $lang->get('history_action_revert') . '</a></td>';
+        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrl($paths->nslist['Special'].'Contributions/' . $r['author']) . '">' . $lang->get('history_action_contrib') . '</a></td>';
+        echo '<td class="' . $cls . '" style="text-align: center;"><a rel="nofollow" href="'.makeUrlNS($namespace, $page_id, 'do=rollback&amp;id=' . $r['time_id']) . '" onclick="ajaxRollback(\'' . $r['time_id'] . '\'); return false;">' . $lang->get('history_action_revert') . '</a></td>';
         
         echo '</tr>';
       }
--- a/includes/template.php	Mon Feb 11 14:33:49 2008 -0500
+++ b/includes/template.php	Tue Feb 12 00:37:46 2008 -0500
@@ -874,7 +874,7 @@
       
       $f = microtime_float();
       $f = $f - $_starttime;
-      $f = round($f, 4);
+      $f = round($f, 2);
       
       $t_loc = $lang->get('page_msg_stats_gentime_short', array('time' => $f));
       $t_loc_long = $lang->get('page_msg_stats_gentime_long', array('time' => $f));
--- a/install/schemas/mysql_stage2.sql	Mon Feb 11 14:33:49 2008 -0500
+++ b/install/schemas/mysql_stage2.sql	Tue Feb 12 00:37:46 2008 -0500
@@ -43,6 +43,7 @@
   author varchar(63),
   edit_summary text,
   minor_edit tinyint(1),
+  is_draft tinyint(1) NOT NULL DEFAULT 0,
   PRIMARY KEY ( log_id )
 ) CHARACTER SET `utf8` COLLATE `utf8_bin`;
 
--- a/install/schemas/postgresql_stage2.sql	Mon Feb 11 14:33:49 2008 -0500
+++ b/install/schemas/postgresql_stage2.sql	Tue Feb 12 00:37:46 2008 -0500
@@ -23,7 +23,7 @@
   subject text,
   comment_data text,
   name text,
-  approved smallint default 1,
+  approved smallint DEFAULT 1,
   user_id int NOT NULL DEFAULT -1,
   time int NOT NULL DEFAULT 0,
   ip_address varchar(39),
@@ -34,7 +34,7 @@
   log_id SERIAL,
   log_type varchar(16),
   action varchar(16),
-  time_id int NOT NULL default '0',
+  time_id int NOT NULL DEFAULT '0',
   date_string varchar(63),
   page_id text,
   namespace text,
@@ -43,12 +43,13 @@
   author varchar(63),
   edit_summary text,
   minor_edit smallint,
+  is_draft smallint NOT NULL DEFAULT 0,
   PRIMARY KEY ( log_id )
 );
 
 CREATE TABLE {{TABLE_PREFIX}}page_text(
   page_id varchar(255),
-  namespace varchar(16) NOT NULL default 'Article',
+  namespace varchar(16) NOT NULL DEFAULT 'Article',
   page_text text,
   char_tag varchar(63)
 );
@@ -57,13 +58,13 @@
   page_order int,
   name varchar(255),
   urlname varchar(255),
-  namespace varchar(16) NOT NULL default 'Article',
-  special smallint default '0',
-  visible smallint default '1',
-  comments_on smallint default '1',
+  namespace varchar(16) NOT NULL DEFAULT 'Article',
+  special smallint DEFAULT '0',
+  visible smallint DEFAULT '1',
+  comments_on smallint DEFAULT '1',
   protected smallint NOT NULL DEFAULT 0,
   wiki_mode smallint NOT NULL DEFAULT 2,
-  delvotes int NOT NULL default 0,
+  delvotes int NOT NULL DEFAULT 0,
   password varchar(40) NOT NULL DEFAULT '',
   delvote_ips text DEFAULT NULL
 );
@@ -72,17 +73,17 @@
   session_key varchar(32),
   salt varchar(32),
   user_id int,
-  auth_level smallint NOT NULL default '0',
-  source_ip varchar(10) default '0x7f000001',
-  time bigint default '0'
+  auth_level smallint NOT NULL DEFAULT '0',
+  source_ip varchar(10) DEFAULT '0x7f000001',
+  time bigint DEFAULT '0'
 );
 
 CREATE TABLE {{TABLE_PREFIX}}themes(
   theme_id varchar(63),
   theme_name text,
-  theme_order smallint NOT NULL default '1',
-  default_style varchar(63) NOT NULL DEFAULT '',
-  enabled smallint NOT NULL default '1'
+  theme_order smallint NOT NULL DEFAULT '1',
+  DEFAULT_style varchar(63) NOT NULL DEFAULT '',
+  enabled smallint NOT NULL DEFAULT '1'
 );
 
 CREATE TABLE {{TABLE_PREFIX}}users(
@@ -91,9 +92,9 @@
   password varchar(255),
   email text,
   real_name text,
-  user_level smallint NOT NULL default 2,
-  theme varchar(64) NOT NULL default 'bleu.css',
-  style varchar(64) NOT NULL default 'default',
+  user_level smallint NOT NULL DEFAULT 2,
+  theme varchar(64) NOT NULL DEFAULT 'bleu.css',
+  style varchar(64) NOT NULL DEFAULT 'DEFAULT',
   signature text,
   reg_time int NOT NULL DEFAULT 0,
   account_active smallint NOT NULL DEFAULT 0,
@@ -139,10 +140,10 @@
   file_id SERIAL,
   time_id int NOT NULL,
   page_id varchar(63) NOT NULL,
-  filename varchar(127) default NULL,
+  filename varchar(127) DEFAULT NULL,
   size bigint NOT NULL,
-  mimetype varchar(63) default NULL,
-  file_extension varchar(8) default NULL,
+  mimetype varchar(63) DEFAULT NULL,
+  file_extension varchar(8) DEFAULT NULL,
   file_key varchar(32) NOT NULL,
   PRIMARY KEY (file_id) 
 );
@@ -151,7 +152,7 @@
   buddy_id SERIAL,
   user_id int,
   buddy_user_id int,
-  is_friend smallint NOT NULL default '1',
+  is_friend smallint NOT NULL DEFAULT '1',
   PRIMARY KEY  (buddy_id) 
 );
 
@@ -266,7 +267,7 @@
 CREATE TABLE {{TABLE_PREFIX}}language(
   lang_id SERIAL,
   lang_code varchar(16) NOT NULL,
-  lang_name_default varchar(64) NOT NULL,
+  lang_name_DEFAULT varchar(64) NOT NULL,
   lang_name_native varchar(64) NOT NULL,
   last_changed int NOT NULL DEFAULT 0
 );
@@ -337,7 +338,7 @@
 INSERT INTO {{TABLE_PREFIX}}pages(page_order, name, urlname, namespace, special, visible, comments_on, protected, delvotes, delvote_ips) VALUES
   (NULL, 'Main Page', 'Main_Page', 'Article', 0, 1, 1, 1, 0, '');
 
-INSERT INTO {{TABLE_PREFIX}}themes(theme_id, theme_name, theme_order, default_style, enabled) VALUES
+INSERT INTO {{TABLE_PREFIX}}themes(theme_id, theme_name, theme_order, DEFAULT_style, enabled) VALUES
   ('oxygen', 'Oxygen', 1, 'bleu.css', 1),
   ('stpatty', 'St. Patty', 2, 'shamrock.css', 1);
 
--- a/install/schemas/upgrade/1.1.1-1.1.2-mysql.sql	Mon Feb 11 14:33:49 2008 -0500
+++ b/install/schemas/upgrade/1.1.1-1.1.2-mysql.sql	Tue Feb 12 00:37:46 2008 -0500
@@ -2,6 +2,7 @@
 -- Upgrade schema - Enano 1.1.1 - 1.1.2
 
 ALTER TABLE {{TABLE_PREFIX}}logs ADD COLUMN log_id int(15) NOT NULL auto_increment, ADD PRIMARY KEY ( log_id );
+ALTER TABLE {{TABLE_PREFIX}}logs ADD COLUMN is_draft tinyint(1) NOT NULL DEFAULT 0;
 
 ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_rank int(12) UNSIGNED NOT NULL DEFAULT 1,
                                   ADD COLUMN user_timezone int(12) UNSIGNED NOT NULL DEFAULT 0;
--- a/install/schemas/upgrade/1.1.1-1.1.2-postgresql.sql	Mon Feb 11 14:33:49 2008 -0500
+++ b/install/schemas/upgrade/1.1.1-1.1.2-postgresql.sql	Tue Feb 12 00:37:46 2008 -0500
@@ -2,6 +2,7 @@
 -- Upgrade schema - Enano 1.1.1 - 1.1.2
 
 ALTER TABLE {{TABLE_PREFIX}}logs ADD COLUMN log_id SERIAL, ADD PRIMARY KEY ( log_id );
+ALTER TABLE {{TABLE_PREFIX}}logs ADD COLUMN is_draft smallint NOT NULL DEFAULT 0;
 
 ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_rank int NOT NULL DEFAULT 1,
                                   ADD COLUMN user_timezone int NOT NULL DEFAULT 0;
--- a/language/english/core.json	Mon Feb 11 14:33:49 2008 -0500
+++ b/language/english/core.json	Tue Feb 12 00:37:46 2008 -0500
@@ -319,12 +319,14 @@
       msg_confirm_ajax: 'Do you really want to do this?\nYour changes to this page have not been saved. If you continue, your changes will be lost.',
       msg_unload: 'If you do, any changes that you have made to this page will be lost.',
       msg_revert_confirm_title: 'Do you really want to revert your changes?',
-      msg_revert_confirm_body: 'If you choose to continue, all of the changes you have made to this page will be lost, and the editor will be set with the latest copy of the page.',
+      msg_revert_confirm_body: 'If you choose to continue, all of the changes you have made to this page will be lost, and the editor will be set with the latest published (not draft) copy of the page.',
       msg_cancel_confirm_title: 'Do you want to cancel your changes?',
       msg_cancel_confirm_body: 'Are you sure you want to discard the changes you made? If you continue, your changes cannot be recovered.',
       msg_diff: 'Below is a summarization of the differences between the current revision of the page (left), and your version (right).',
       msg_diff_empty: 'There are no differences between the text in the editor and the current revision of the page.',
       msg_editing_old_revision: 'You are editing an old revision of this page. If you click Save, newer revisions of this page will be undone.',
+      msg_have_draft_title: 'A draft copy of this page is available.',
+      msg_have_draft_body: '%author% saved a draft version of this page on %time%. You can <a href="#use_draft" onclick="ajaxEditorUseDraft(); return false;">use the draft copy</a>, or edit the current published version (below). If you edit the published version, the draft copy will remain available, but will not reflect your changes. It is recommended that you edit the draft version instead of editing the published version.',
       btn_graphical: 'graphical editor',
       btn_wikitext: 'wikitext editor',
       lbl_edit_summary: 'Brief summary of your changes:',
@@ -332,12 +334,15 @@
       lbl_minor_edit: 'Mark revision as trivial:',
       lbl_minor_edit_field: 'This is a minor edit',
       lbl_minor_edit_explain: 'Select this if you\'re only making a minor change to the page',
-      btn_save: 'Save changes',
-      btn_preview: 'Show preview',
-      btn_revert: 'Revert changes',
-      btn_cancel: 'Cancel and return to page',
+      btn_save: 'Save',
+      btn_savedraft: 'Save draft',
+      btn_preview: 'Preview',
+      btn_revert: 'Revert',
+      btn_cancel: 'Cancel',
+      btn_diff: 'Show changes',
       btn_closeviewer: 'Close viewer',
-      btn_diff: 'Show changes',
+      msg_draft_saving: 'Saving...',
+      msg_draft_saved: 'Saved at %time%',
       preview_blurb: '<b>Reminder:</b> This is only a preview - your changes to this page have not yet been saved.',
       msg_save_success_title: 'Changes saved',
       msg_save_success_body: 'Your changes to this page have been saved. Redirecting...',
--- a/plugins/admin/SecurityLog.php	Mon Feb 11 14:33:49 2008 -0500
+++ b/plugins/admin/SecurityLog.php	Tue Feb 12 00:37:46 2008 -0500
@@ -102,7 +102,7 @@
       $l = 'SELECT action,date_string,author,edit_summary,time_id,page_text FROM '.table_prefix.'logs WHERE log_type=\'security\' ORDER BY time_id DESC, action ASC;';
     }
     $q = $db->sql_query($l);
-    while($r = $db->fetchrow())
+    while($r = $db->fetchrow($q))
     {
       $return .= seclog_format_inner($r);
     }
--- a/themes/oxygen/css/bleu.css	Mon Feb 11 14:33:49 2008 -0500
+++ b/themes/oxygen/css/bleu.css	Tue Feb 12 00:37:46 2008 -0500
@@ -289,9 +289,10 @@
 
 /* toolbar */
 div.toolbar {
-  border-bottom: 1px solid #909090;
+  border: 1px solid #3b619c;
   background-color: #D0D0D0;
-  padding: 2px 0;
+  background-image: url(../images/tb-bkg.gif);
+  padding: 1px 0;
   height: 22px;
   font-family: arial, sans-serif;
   font-size: 8pt;
@@ -309,7 +310,7 @@
   opacity: 0.6;
   /*filter: alpha(opacity=60);*/
 }
-div.toolbar a:hover img {
+div.toolbar a:hover img, div.toolbar a:focus img {
   opacity: 1;
   /*filter: alpha(opacity=100);*/
 }
@@ -324,15 +325,16 @@
   max-height: 16px;
   text-decoration: none;
 }
-div.toolbar a:hover {
-  border: 1px solid #202090;
+div.toolbar a:hover, div.toolbar a:focus {
+  border: 1px solid #000080;
   background-color: #ceceed;
+  background-image: url(../images/tb-frg.gif);
   color: #000000;
   text-decoration: none;
 }
 div.toolbar a:active {
-  border: 1px solid #A0A0A0;
   background-color: #E0E0E0;
+  background-image: url(../images/tb-psh.gif);
 }
 div.toolbar img {
   margin: 0;
@@ -342,11 +344,20 @@
 }
 div.toolbar a span {
   position: relative;
-  top: -4px;
+  top: -3px !important;
+}
+div.toolbar a span.noimage {
+  position: relative;
+  top: 0px !important;
+  height: 16px !important;
+  display: block;
+  padding-left: 2px !important;
 }
 div.toolbar li span {
-  padding-left: 2px;
-  padding-right: 5px;
+  padding-left: 4px;
+  padding-right: 2px;
+  position: relative;
+  top: 4px;
 }
 
 /* vertical toolbar */
Binary file themes/oxygen/images/tb-bkg.gif has changed
Binary file themes/oxygen/images/tb-frg.gif has changed
Binary file themes/oxygen/images/tb-psh.gif has changed
--- a/themes/oxygen/toolbar.tpl	Mon Feb 11 14:33:49 2008 -0500
+++ b/themes/oxygen/toolbar.tpl	Tue Feb 12 00:37:46 2008 -0500
@@ -1,5 +1,6 @@
 <!-- Stuff related to toolbars and clickable buttons.
-     Used mostly in the page toolbar on most pages.
+     The plan was to use this on the toolbar for most pages. Never made it into the release,
+     but still provided as an otherwise-unused component for plugins to make use of.
      -->
 
 <!-- VAR toolbar_start -->
@@ -9,9 +10,15 @@
 <!-- VAR toolbar_button -->
   <li>
     <a title="{TITLE}" {FLAGS}>
+      <!-- BEGINNOT no_image -->
       <img alt="{TITLE}" src="{IMAGE}" />
+      <!-- END no_image -->
       <!-- BEGIN show_title -->
-      <span>{TITLE}</span>
+        <!-- BEGIN no_image -->
+        <span class="noimage">{TITLE}</span>
+        <!-- BEGINELSE no_image -->
+        <span>{TITLE}</span>
+        <!-- END no_image -->
       <!-- END show_title -->
     </a>
   </li>