]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1067 d1b88da0-ebda-0310...
authorwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Thu, 17 May 2007 23:16:40 +0000 (23:16 +0000)
committerwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Thu, 17 May 2007 23:16:40 +0000 (23:16 +0000)
34 files changed:
ChangeLog
SoObjects/Appointments/SOGoAppointmentFolder.m
SoObjects/Appointments/SOGoFreeBusyObject.m
SoObjects/Appointments/product.plist
SoObjects/SOGo/NSString+Utilities.m
SoObjects/SOGo/SOGoFolder.m
SoObjects/SOGo/SOGoObject.h
SoObjects/SOGo/SOGoObject.m
SoObjects/SOGo/SOGoPermissions.h
SoObjects/SOGo/SOGoPermissions.m
UI/Common/English.lproj/Localizable.strings
UI/Common/French.lproj/Localizable.strings
UI/Common/UIxAclEditor.m
UI/Common/UIxObjectActions.m
UI/Common/UIxUserRightsEditor.m
UI/Contacts/English.lproj/Localizable.strings
UI/Contacts/French.lproj/Localizable.strings
UI/MainUI/product.plist
UI/Scheduler/English.lproj/Localizable.strings
UI/Scheduler/French.lproj/Localizable.strings
UI/Templates/ContactsUI/UIxContactsListView.wox
UI/Templates/ContactsUI/UIxContactsUserRightsEditor.wox
UI/Templates/MailerUI/UIxMailView.wox
UI/Templates/SchedulerUI/UIxCalUserRightsEditor.wox
UI/Templates/UIxAclEditor.wox
UI/WebServerResources/ContactsUI.css
UI/WebServerResources/MailerUI.css
UI/WebServerResources/MailerUI.js
UI/WebServerResources/SchedulerUI.css
UI/WebServerResources/UIxAclEditor.css
UI/WebServerResources/UIxAclEditor.js
UI/WebServerResources/generic.css
UI/WebServerResources/generic.js
UI/WebServerResources/prototype.js

index 912c9c482e2006e63b9141d4be98aaf995b5cb73..06b9a6f5e15c2afae7a110f13c1f2978b5a4e95b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,40 @@
+2007-05-17  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * UI/Common/UIxObjectActions.m ([UIxObjectActions
+       -addUserInAclsAction]): save the previous roles of the user
+       (unlikely) or the default roles, or None instead of saving the
+       roles that should appear the first time in the role editor when no
+       default has been set...
+
+       * SoObjects/SOGo/SOGoObject.m: defined a new constant
+       "SOGoDefaultUserID".
+
+       * SoObjects/SOGo/SOGoFolder.m ([SOGoFolder
+       -setRoles:rolesforUser:uidforObjectAtPath:objectPathArray]):
+       ignore the "AuthorizedSubscriber" role and add the "None" role
+       when no other role is found.
+
+       * UI/Common/UIxAclEditor.m: added a button for the new default
+       user roles feature.
+
+       * UI/Common/UIxUserRightsEditor.m ([UIxUserRightsEditor
+       -_initRights]): don't take the "None" role into account when
+       initializing the list of current roles.
+
+       * SoObjects/SOGo/SOGoPermissions.m: added a new void role
+       "SOGoRole_None".
+
+2007-05-16  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * SoObjects/SOGo/NSString+Utilities.m ([NSString
+       -_rangeOfURLInRange:refRange]): urls cannot end with "&" nor "=".
+       There is no need to subscract the start from the length if a space
+       is not found since the range has not changed either. Finally, the
+       first character before the url might also be a tab or a cr, so we
+       match it against the "urlAfterEndingChars" custom NSCharacterSet.
+       ([NSString -stringByDetectingURLs]): modified to handle email
+       addresses and add a correct "mailto:" link.
+
 2007-05-15  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
 
        * SoObjects/Appointments/SOGoAppointmentFolder.m
index 2300c7e15033d2a15298072bf56dfe36d706a56f..5d708c38f4aafd9a5504d75c18d5b0991cd92bdc 100644 (file)
@@ -1268,9 +1268,9 @@ static NSNumber   *sharedYes = nil;
   today = [[NSCalendarDate calendarDate] beginOfDay];
   [today setTimeZone: timeZone];
 
-  startDate = [today dateByAddingYears: 0 months: 0 days: -14
+  startDate = [today dateByAddingYears: 0 months: 0 days: -1
                      hours: 0 minutes: 0 seconds: 0];
-  endDate = [startDate dateByAddingYears: 0 months: 1 days: 0
+  endDate = [startDate dateByAddingYears: 0 months: 0 days: 2
                        hours: 0 minutes: 0 seconds: 0];
   records = [self fetchFields: [NSArray arrayWithObject: @"c_name"]
                  from: startDate to: endDate
index 4476c0e9d256786a921b93608f11be8e0a2f7eb6..724821d56be39e4e0df2e40a2ae7f4dcddf57dfa 100644 (file)
                                   to: (NSCalendarDate *) _endDate
 {
   id calFolder;
-  SoSecurityManager *sm;
+//   SoSecurityManager *sm;
   NSArray *infos;
 
   calFolder = [container lookupName: @"Calendar" inContext: nil acquire: NO];
-  sm = [SoSecurityManager sharedSecurityManager];
-  if (![sm validatePermission: SOGoPerm_FreeBusyLookup
-           onObject: calFolder
-           inContext: context])
+//   sm = [SoSecurityManager sharedSecurityManager];
+//   if (![sm validatePermission: SOGoPerm_FreeBusyLookup
+//            onObject: calFolder
+//            inContext: context])
     infos = [calFolder fetchFreeBusyInfosFrom: _startDate
                        to: _endDate];
-  else
-    {
-      infos = [NSArray new];
-      [infos autorelease];
-    }
+//   else
+//     {
+//       infos = [NSArray new];
+//       [infos autorelease];
+//     }
 
   return infos;
 }
index 91f693d4a72affce79f2b29cb1b19a4e9b68d5f0..ff9831d06e94cb49db17dcf5c06c30290d84796d 100644 (file)
     };
     SOGoFreeBusyObject = {
       superclass    = "SOGoContentObject";
-      protectedBy = "View";
-      defaultRoles = {
-        "View" = ( "Authenticated", "FreeBusy" );
-        "WebDAV Access" = ( "Authenticated", "FreeBusy" );
-      };
+      protectedBy = "<public>";
     };
   };
 }
index 4d8acf863e7ee0269ad426a6a04f6c4239c9e656..987ad47c79ab44a41286dbcb7bafce119630d271 100644 (file)
@@ -108,63 +108,78 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil;
 - (NSRange) _rangeOfURLInRange: (NSRange) refRange
 {
   int start, length;
-  NSRange endRange;
+  NSRange workRange;
 
   if (!urlNonEndingChars)
     {
       urlNonEndingChars = [NSMutableCharacterSet new];
-      [urlNonEndingChars addCharactersInString: @",.:;\t \r\n"];
+      [urlNonEndingChars addCharactersInString: @">&=,.:;\t \r\n"];
     }
   if (!urlAfterEndingChars)
     {
       urlAfterEndingChars = [NSMutableCharacterSet new];
-      [urlAfterEndingChars addCharactersInString: @"\t \r\n"];
+      [urlAfterEndingChars addCharactersInString: @"&;<\t \r\n"];
     }
 
   start = refRange.location;
   while (start > -1
-        && [self characterAtIndex: start] != ' ')
+        && ![urlAfterEndingChars characterIsMember:
+                                   [self characterAtIndex: start]])
     start--;
   start++;
   length = [self length] - start;
-  endRange = NSMakeRange (start, length);
-  endRange = [self rangeOfCharacterFromSet: urlAfterEndingChars
-                     options: NSLiteralSearch range: endRange];
-  if (endRange.location != NSNotFound)
-    length = endRange.location;
-  length -= start;
+  workRange = NSMakeRange (start, length);
+  workRange = [self rangeOfCharacterFromSet: urlAfterEndingChars
+                   options: NSLiteralSearch range: workRange];
+  if (workRange.location != NSNotFound)
+    length = workRange.location - start;
   while
-    ([urlNonEndingChars characterIsMember:
-                         [self characterAtIndex: (start + length - 1)]])
+    (length > 0
+     && [urlNonEndingChars characterIsMember:
+                            [self characterAtIndex: (start + length - 1)]])
     length--;
 
   return NSMakeRange (start, length);
 }
 
-- (NSString *) stringByDetectingURLs
+- (void) _handleURLs: (NSMutableString *) selfCopy
+        textToMatch: (NSString *) match
+             prefix: (NSString *) prefix
 {
-  NSMutableString *selfCopy;
   NSRange httpRange, currentURL, rest;
   NSString *urlText, *newUrlText;
-  unsigned int length;
-
-  selfCopy = [NSMutableString stringWithString: self];
+  unsigned int length, matchLength;
 
-  httpRange = [selfCopy rangeOfString: @"://"];
+  matchLength = [match length];
+  httpRange = [selfCopy rangeOfString: match];
   while (httpRange.location != NSNotFound)
     {
       currentURL = [selfCopy _rangeOfURLInRange: httpRange];
       urlText = [selfCopy substringFromRange: currentURL];
-      newUrlText = [NSString stringWithFormat: @"<a href=\"%@\">%@</a>",
-                            urlText, urlText];
-      [selfCopy replaceCharactersInRange: currentURL
-               withString: newUrlText];
+      if ([urlText length] > matchLength)
+       {
+         newUrlText = [NSString stringWithFormat: @"<a href=\"%@%@\">%@</a>",
+                                prefix, urlText, urlText];
+         [selfCopy replaceCharactersInRange: currentURL
+                   withString: newUrlText];
+         rest.location = currentURL.location + [newUrlText length];
+       }
+      else
+       rest.location = currentURL.location + currentURL.length;
       length = [selfCopy length];
-      rest.location = currentURL.location + [newUrlText length];
       rest.length = length - rest.location;
-      httpRange = [selfCopy rangeOfString: @"://"
+      httpRange = [selfCopy rangeOfString: match
                            options: 0 range: rest];
     }
+}
+
+- (NSString *) stringByDetectingURLs
+{
+  NSMutableString *selfCopy;
+
+  selfCopy = [NSMutableString stringWithString: self];
+  [self _handleURLs: selfCopy textToMatch: @"://" prefix: @""];
+  [self _handleURLs: selfCopy textToMatch: @"@" prefix: @"mailto:"];
 
   return selfCopy;
 }
index 43bc8e1c13ab557efdd9c8c6b5ef8d68a454138b..a55c3dad258e513a0da273b17b15e06f806763b1 100644 (file)
@@ -31,8 +31,6 @@
 #import <unistd.h>
 #import <stdlib.h>
 
-static NSString *defaultUser = @"<default>";
-
 @implementation SOGoFolder
 
 + (int) version
@@ -341,8 +339,9 @@ static NSString *defaultUser = @"<default>";
       [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
     }
 
-  if (!([acls count] || [uid isEqualToString: defaultUser]))
-    acls = [self aclsForUser: defaultUser forObjectAtPath: objectPathArray];
+  if (!([acls count] || [uid isEqualToString: SOGoDefaultUserID]))
+    acls = [self aclsForUser: SOGoDefaultUserID
+                forObjectAtPath: objectPathArray];
 
   return acls;
 }
@@ -369,19 +368,14 @@ static NSString *defaultUser = @"<default>";
     }
 }
 
-- (void) setRoles: (NSArray *) roles
-          forUser: (NSString *) uid
-  forObjectAtPath: (NSArray *) objectPathArray
+- (void) _commitRoles: (NSArray *) roles
+              forUID: (NSString *) uid
+           forObject: (NSString *) objectPath
 {
   EOAdaptorChannel *channel;
   GCSFolder *folder;
   NSEnumerator *userRoles;
-  NSString *SQL, *currentRole, *objectPath;
-
-  [self removeAclsForUsers: [NSArray arrayWithObject: uid]
-        forObjectAtPath: objectPathArray];
-  objectPath = [objectPathArray componentsJoinedByString: @"/"];
-  [self _cacheRoles: roles forUser: uid forObjectAtPath: objectPath];
+  NSString *SQL, *currentRole;
 
   folder = [self ocsFolder];
   channel = [folder acquireAclChannel];
@@ -389,21 +383,40 @@ static NSString *defaultUser = @"<default>";
   currentRole = [userRoles nextObject];
   while (currentRole)
     {
-      if (![currentRole isEqualToString: SOGoRole_AuthorizedSubscriber])
-       {
-         SQL = [NSString stringWithFormat: @"INSERT INTO %@"
-                         @" (c_object, c_uid, c_role)"
-                         @" VALUES ('/%@', '%@', '%@')",
-                         [folder aclTableName],
-                         objectPath, uid, currentRole];
-         [channel evaluateExpressionX: SQL];
-       }
+      SQL = [NSString stringWithFormat: @"INSERT INTO %@"
+                     @" (c_object, c_uid, c_role)"
+                     @" VALUES ('/%@', '%@', '%@')",
+                     [folder aclTableName],
+                     objectPath, uid, currentRole];
+      [channel evaluateExpressionX: SQL];
       currentRole = [userRoles nextObject];
     }
 
   [folder releaseChannel: channel];
 }
 
+- (void) setRoles: (NSArray *) roles
+          forUser: (NSString *) uid
+  forObjectAtPath: (NSArray *) objectPathArray
+{
+  NSString *objectPath;
+  NSMutableArray *newRoles;
+
+  [self removeAclsForUsers: [NSArray arrayWithObject: uid]
+        forObjectAtPath: objectPathArray];
+
+  newRoles = [NSMutableArray arrayWithArray: roles];
+  [newRoles removeObject: SOGoRole_AuthorizedSubscriber];
+  [newRoles removeObject: SOGoRole_None];
+  objectPath = [objectPathArray componentsJoinedByString: @"/"];
+  [self _cacheRoles: newRoles forUser: uid
+       forObjectAtPath: objectPath];
+  if (![newRoles count])
+    [newRoles addObject: SOGoRole_None];
+
+  [self _commitRoles: newRoles forUID: uid forObject: objectPath];
+}
+
 /* acls */
 - (NSArray *) defaultAclRoles
 {
index 5b4eeccec2d98ca23e412ed19208e334bdc9b996..e5a77e73921c734ac7a93595fea0de4c53a44a9a 100644 (file)
@@ -52,6 +52,8 @@
 
 #define $(class) NSClassFromString(class)
 
+extern NSString *SOGoDefaultUserID;
+
 @interface SOGoObject : NSObject
 {
   WOContext *context;
index 5a0e50159b06a27d82f4648075d607a005a159a9..57b13cd78a0858d6b4a53609943ed59400b84fe9 100644 (file)
@@ -20,8 +20,8 @@
 */
 
 #if LIB_FOUNDATION_LIBRARY
-#error SOGo won't work properly with libFoundation. Please use gnustep-base \
-       instead.
+#error SOGo will not work properly with libFoundation. \
+       Please use gnustep-base instead.
 #endif
 
 #import <NGObjWeb/WEClientCapabilities.h>
@@ -47,6 +47,8 @@
 
 #import "SOGoObject.h"
 
+NSString *SOGoDefaultUserID = @"<default>";
+
 @interface SOGoObject(Content)
 - (NSString *)contentAsString;
 @end
index e6825554d48e6b57fe2afebc360a863429254bea..67ee64bee0b48ac0bf91b743846eba4bb7d87298 100644 (file)
@@ -32,6 +32,7 @@ extern NSString *SOGoRole_ObjectEraser;
 extern NSString *SOGoRole_ObjectViewer;
 extern NSString *SOGoRole_ObjectEditor;
 extern NSString *SOGoRole_AuthorizedSubscriber;
+extern NSString *SOGoRole_None;
 
 extern NSString *SOGoRole_FreeBusy;
 extern NSString *SOGoRole_FreeBusyLookup;
index a071f019f288e26e3e596cb7ac3deae71472870b..e750ef31b8c459306073f3837674d458c8d8720d 100644 (file)
@@ -28,6 +28,7 @@ NSString *SOGoRole_ObjectEraser = @"ObjectEraser";
 NSString *SOGoRole_ObjectViewer = @"ObjectViewer";
 NSString *SOGoRole_ObjectEditor = @"ObjectEditor";
 NSString *SOGoRole_AuthorizedSubscriber = @"AuthorizedSubscriber";
+NSString *SOGoRole_None = @"None";
 
 NSString *SOGoRole_FreeBusy = @"FreeBusy"; /* for the "freebusy" special user
                                             */
index 119c9c3282c5f8862c683ff5e578a70578b0292f..4ac925e1ea5d438972ff4cda1f22ea9da32d0e2d 100644 (file)
@@ -3,6 +3,7 @@
 /* toolbars */
 "Save" = "Save";
 "Close" = "Close";
+"Edit User Rights" = "Edit User Rights";
 
 "Home" = "Home";
 "Calendar" = "Calendar";
@@ -21,4 +22,6 @@
 
 "Publish the Free/Busy information" = "Publish the Free/Busy information";
 
+"Default Roles" = "Default Roles";
+
 "Sorry, the user rights can not be configured for that object." = "Sorry, the user rights can not be configured for that object.";
index dcf84ce14a2641e5a8ab3052d2b2171163d0f200..c18c40680d774ed27fb7826d63a915bd185aa00f 100644 (file)
@@ -1,8 +1,7 @@
-/* this file is in UTF-8 format! */
-
 /* toolbars */
 "Save" = "Sauver";
 "Close" = "Fermer";
+"Edit User Rights" = "Édition des droits";
 
 "Home" = "Accueil";
 "Calendar" = "Agenda";
@@ -24,4 +23,6 @@
 "(Unchecked = assistant, checked = delegate)" = "(Coché = assistant, décoché = délégué)";
 "Publish the Free/Busy information" = "Publier l'occupation du temps";
 
+"Default Roles" = "Rôles par défaut";
+
 "Sorry, the user rights can not be configured for that object." = "Sorry, the user rights can not be configured for that object.";
index 12a45f5031d0da07233b7eb9c5840de52339a9b1..e0328ace9530c3722d81972f65e8ef6522a3da6c 100644 (file)
   return [self _displayNameForUID: ownerLogin];
 }
 
+- (NSString *) defaultUserID
+{
+  return SOGoDefaultUserID;
+}
+
 - (void) _prepareUsers
 {
   NSEnumerator *aclsEnum;
     {
       currentUID = [currentAcl objectForKey: @"c_uid"];
       if (!([currentUID isEqualToString: ownerLogin]
+           || [currentUID isEqualToString: SOGoDefaultUserID]
            || [users containsObject: currentUID]))
          [users addObject: currentUID];
       currentAcl = [aclsEnum nextObject];
index 1d154a6654ff5d9f01abc9534756999cf4f38715..f3cf99306cf1f94fad58e8c34bc3286ef56c8577 100644 (file)
@@ -27,6 +27,7 @@
 #import <NGObjWeb/WOResponse.h>
 #import <SoObjects/SOGo/LDAPUserManager.h>
 #import <SoObjects/SOGo/SOGoObject.h>
+#import <SoObjects/SOGo/SOGoPermissions.h>
 
 #import "UIxObjectActions.h"
 
@@ -49,9 +50,9 @@
       um = [LDAPUserManager sharedUserManager];
       if ([um contactInfosForUserWithUIDorEmail: uid])
         {
-          clientObject = [self clientObject];
-          [clientObject setRoles: [clientObject defaultAclRoles]
-                        forUser: uid];
+         clientObject = [self clientObject];
+         [clientObject setRoles: [clientObject aclsForUser: uid]
+                       forUser: uid];
           code = 204;
         }
     }
@@ -78,7 +79,8 @@
       um = [LDAPUserManager sharedUserManager];
       if ([um contactInfosForUserWithUIDorEmail: uid])
        {
-         [[self clientObject] removeAclsForUsers: [NSArray arrayWithObject: uid]];
+         [[self clientObject] removeAclsForUsers:
+                                [NSArray arrayWithObject: uid]];
           code = 204;
         }
     }
index 35b697612dafb288a1520c3ac26c002bcff4ef67..8ec44dd463ccbcd140e8f3bd598badfd8563c3bf 100644 (file)
@@ -24,6 +24,7 @@
 #import <NGObjWeb/WOResponse.h>
 #import <NGObjWeb/WORequest.h>
 #import <SoObjects/SOGo/LDAPUserManager.h>
+#import <SoObjects/SOGo/SOGoPermissions.h>
 
 #import "UIxUserRightsEditor.h"
 
   return uid;
 }
 
+- (BOOL) userIsDefaultUser
+{
+  return [uid isEqualToString: SOGoDefaultUserID];
+}
+
 - (NSString *) userDisplayName
 {
   LDAPUserManager *um;
-  
+
   um = [LDAPUserManager sharedUserManager];
 
   return [NSString stringWithFormat: @"%@ <%@>",
 - (BOOL) _initRights
 {
   BOOL response;
-  NSString *newUID, *email;
+  NSString *newUID;
   LDAPUserManager *um;
   SOGoObject *clientObject;
+  unsigned int count;
 
   response = NO;
 
   if ([newUID length] > 0)
     {
       um = [LDAPUserManager sharedUserManager];
-      email = [um getEmailForUID: newUID];
-      if ([email length] > 0)
+      if ([newUID isEqualToString: SOGoDefaultUserID]
+         || [[um getEmailForUID: newUID] length] > 0)
        {
          ASSIGN (uid, newUID);
          clientObject = [self clientObject];
          [userRights addObjectsFromArray: [clientObject aclsForUser: uid]];
-         if (![userRights count])
-           [userRights addObjectsFromArray: [clientObject defaultAclRoles]];
-         
+         count = [userRights count];
+         if (!count || (count == 1 && [[userRights objectAtIndex: 0]
+                                        isEqualToString: SOGoRole_None]))
+           [userRights setArray: [clientObject defaultAclRoles]];
+
          response = YES;
        }
     }
   else
     {
       [self updateRights];
-      [[self clientObject] setRoles: userRights
-                          forUser: uid];
+      [[self clientObject] setRoles: userRights forUser: uid];
       response = [self jsCloseWithRefreshMethod: nil];
     }
 
index 4233453d5c793664a0df95563cca5bd2afad9b84..cca322a0baec29402d194aa8f7435bd3e1bce279 100644 (file)
 "Unable to subscribe to that folder!"
 = "Unable to subscribe to that folder!";
 
+"Default Roles" = "Default Roles";
 "User rights for:" = "User rights for:";
 
 "This person can add cards to this addressbook."
index 46f665719de0f5b308ba5ce267a628136fe1fbe3..aaf6e207537368c02b5e57d2b5b6028186d9aeb7 100644 (file)
 "Unable to subscribe to that folder!"
 = "Impossible de vous inscrire Ã  ce dossier!";
 
+"Default Roles" = "Rôles par défaut";
 "User rights for:" = "Autorisations pour :";
 
 "This person can add cards to this addressbook."
index 44b3503d116af710d9129ed0eb82a825142a0f99..2ce04921d92900785dca79529103e217f7cb8ca8 100644 (file)
@@ -74,7 +74,7 @@
     SOGoFreeBusyObject = {
       methods = {
         ajaxRead = { 
-          protectedBy = "View";
+          protectedBy = "<public>";
           pageName = "SOGoUserHomePage"; 
           actionName = "readFreeBusy";
         };
index 1a370a3f012c49d6c2a3a90363b2aa3b8d39ed88..050f12defa6d0862db02ff97a7975b84cc5e9f66 100644 (file)
@@ -82,6 +82,7 @@
 "Forbidden" = "Forbidden";
 
 /* acls */
+"Default Roles" = "Default Roles";
 "User rights for:" = "User rights for:";
 "label_Public" = "Public";
 "label_Private" = "Private";
index 266d9dfec0da2814091b6d51410db168d819fc05..46b02d33ac7b2d3005f54e8454263856be776135 100644 (file)
@@ -83,6 +83,7 @@
 "Forbidden" = "Accès non autorisée";
 
 /* acls */
+"Default Roles" = "Rôles par défaut";
 "User rights for:" = "Autorisations pour :";
 "label_Public" = "Public";
 "label_Private" = "Privé";
index 2b007b12766554280de86150a97ca2259ea4964f..49349d8cefd91f4ba89beb4b799dbf0b74bb647b 100644 (file)
@@ -13,7 +13,7 @@
       <tbody>
         <tr class="tableview">
           <!-- localize -->
-          <td class="tbtv_headercell">
+          <td class="tbtv_headercell headerName">
             <var:component className="UIxSortableTableHeader"
               label:label="Name"
               const:sortKey="cn"
@@ -21,7 +21,7 @@
               var:queryDictionary="context.request.formValues"
               />
           </td>
-          <td class="tbtv_headercell">
+          <td class="tbtv_headercell headerEmail">
             <var:component className="UIxSortableTableHeader"
               label:label="Email"
               const:sortKey="mail"
index e14ea785fb308baba0cc012e0def33747374b2ac..39b25787d2e4a3c996849ffb32c94f473aed66ac 100644 (file)
   <form id="userRightsForm" const:href="saveUserRights">
     <input type="hidden" name="uid" var:value="uid"/>
     <div class="title">
-      <label><var:string label:value="User rights for:"/><br/>
-       <span class="value"><var:string value="userDisplayName"/></span></label>
+      <var:if condition="userIsDefaultUser">
+       <label><span class="value"><var:string label:value="Default Roles"
+             /></span></label>
+      </var:if><var:if condition="userIsDefaultUser" const:negate="YES">
+       <label><var:string label:value="User rights for:"/><br/>
+         <span class="value"><var:string value="userDisplayName"
+             /></span></label></var:if>
     </div>
     <div class="calendarUserRights">
       <label><input type="checkbox" class="checkBox"
index 07966734ca9ab3e42df8ecf99e8c2246f342d76a..965cc197516d9db5776b73bee4e20465a4302124 100644 (file)
@@ -18,7 +18,7 @@
         <td class="mailer_fieldvalue">
           <var:foreach list="clientObject.fromEnvelopeAddresses"
             item="currentAddress">
-            <a var:href="currentAddressLink" onclick="return onMenuClick(event, 'addressMenu');" oncontextmenu="onMenuClick(event, 'addressMenu');">
+            <a var:href="currentAddressLink">
               <var:string value="currentAddress"
                 formatter="context.mailEnvelopeFullAddressFormatter" /></a>
           </var:foreach>
         <td class="mailer_fieldvalue">
           <var:foreach list="clientObject.toEnvelopeAddresses"
             item="currentAddress"
-            ><a var:href="currentAddressLink"
-              onmousedown="return false;"
-              onclick="return onMenuClick(event, 'addressMenu');"
-              oncontextmenu="onMenuClick(event, 'addressMenu');">
+            ><a var:href="currentAddressLink">
               <var:string value="currentAddress"
                 formatter="context.mailEnvelopeFullAddressFormatter"
                 /></a>
             <var:foreach list="clientObject.ccEnvelopeAddresses" 
               item="currentAddress">
               <a var:href="currentAddressLink"
-                onclick="return onMenuClick(event, 'addressMenu');"
-                oncontextmenu="onMenuClick(event, 'addressMenu');"
-                onmousedown="return false;"
-                ><var:string value="currentAddress"
+               ><var:string value="currentAddress"
                   formatter="context.mailEnvelopeFullAddressFormatter"
                   /></a>
               <!-- TODO: better to use li+CSS -->
@@ -71,8 +65,7 @@
       </var:if>
     </table>
 
-    <div class="mailer_mailcontent"
-      oncontextmenu="onMenuClick(event, 'messageContentMenu');">
+    <div class="mailer_mailcontent">
       <var:component value="contentViewerComponent"
         bodyInfo="clientObject.bodyStructure" />
     </div>
index 14ba7d5bdeb261f15fdb245bff0e6d5889feae59..85b6d0c384cff7de3950d1765b541bcc6a032e09 100644 (file)
   <form id="userRightsForm" const:href="saveUserRights">
     <input type="hidden" name="uid" var:value="uid"/>
     <div class="title">
-      <label><var:string label:value="User rights for:"/><br/>
-       <span class="value"><var:string value="userDisplayName"/></span></label>
+      <var:if condition="userIsDefaultUser">
+       <label><span class="value"><var:string label:value="Default Roles"
+             /></span></label>
+      </var:if><var:if condition="userIsDefaultUser" const:negate="YES">
+       <label><var:string label:value="User rights for:"/><br/>
+         <span class="value"><var:string value="userDisplayName"
+             /></span></label></var:if>
     </div>
     <div class="calendarUserRights">
       <table>
index 20d933583e9c2a13c6578a68205ae12376306338..58ba94c90077a8bac3a85ade4e051d2e13380f5d 100644 (file)
   <form id="aclForm" const:href="saveAcls">
     <div class="acls">
       <div id="userSelectorHeader">
+       <input type="hidden" name="defaultUserID" id="defaultUserID"
+         var:value="defaultUserID"/>
        <input type="hidden" name="action" value="saveAcls"/>
        <input type="hidden" id="userUIDS" name="userUIDS"
          var:value="userUIDS"/>
        <label><var:string label:value="Owner:"/><br/>
          <span class="value"><strong><var:string value="ownerName"/></strong></span></label><br/>
       </div>
+      <input id="defaultRolesBtn" type="button"
+       class="button" label:value="Default Roles"/>
       <div class="userSelector" id="userRoles">
        <var:if condition="currentUserIsOwner">
          <span id="userSelectorButtons">
index e9e3ff35d88b87be7f970924c3646736c2fa9f86..89eefdd8b417819bc14453fc8cc630058101655a 100644 (file)
@@ -31,13 +31,13 @@ DIV#rightPanel
 }
 
 DIV#contactsListContent
-{ 
-  position: absolute;
+{ position: absolute;
   background: #fff;
   width: 100%;
   height: 16em;
   top: 2.5em;
   overflow: auto;
+  overflow-x: hidden;
   left: 0px;
 }
 
@@ -190,7 +190,12 @@ TABLE#contactsList
 }
 
 TABLE#contactsList TD
-{ white-space: nowrap; }
+{ white-space: nowrap;
+  width: 20%; }
+
+TABLE#contactsList TD.headerName,
+TABLE#contactsList TD.headerEmail
+{ width: 30%; }
 
 TABLE#contactsList TD IMG
 {
index 777636278773802ac7e6ddae8a4c9257d409fbfb..0e7f60d30d3d04f791b6c0d3990b060e8cd16929 100644 (file)
@@ -475,6 +475,7 @@ TABLE#messageList
   top: 2em;
   bottom: 0px;
   overflow: auto;
+  overflow-x: hidden;
 }
 
 TABLE#messageList TBODY
@@ -489,9 +490,12 @@ TABLE#messageList TBODY
 
 TABLE#messageList TD
 { 
-  height: 1.2em;
+  height: 1.2em;  
 }
 
+TABLE#messageList TD.tbtv_subject_headercell
+{ width: 30%; }
+
 TABLE#messageList TR._selected TD
 { 
   background: #4b6983;
index d2861de977c168ee616f5a37188ad6272974726d..20739d874a14bef6eda291ba80600baf5210518a 100644 (file)
@@ -625,14 +625,34 @@ function loadMessage(idx) {
     div.innerHTML = cachedMessage['text'];
     cachedMessage['time'] = (new Date()).getTime();
     document.messageAjaxRequest = null;
-    configureLinksInMessageDIV(div);
+    configureLinksInMessage();
   }
 }
 
-function configureLinksInMessageDIV(div) {
-   var anchors = div.getElementsByTagName('a');
+function configureLinksInMessage() {
+   var messageDiv = $('messageContent');
+   var mailContentDiv = document.getElementsByClassName('mailer_mailcontent',
+                                                       messageDiv)[0];
+   mailContentDiv.addEventListener("contextmenu", onMessageContentMenu, false);
+   var anchors = messageDiv.getElementsByTagName('a');
    for (var i = 0; i < anchors.length; i++)
-      anchors[i].addEventListener("click", onMessageAnchorClick, false);
+      if (anchors[i].href.substring(0,7) == "mailto:") {
+        anchors[i].addEventListener("click", onEmailAddressClick, false);
+        anchors[i].addEventListener("contextmenu", onEmailAddressClick,
+                                    false);
+      }
+      else
+        anchors[i].addEventListener("click", onMessageAnchorClick, false);
+}
+
+function onMessageContentMenu(event) {
+   onMenuClick(event, 'messageContentMenu');
+   event.preventDefault();
+};
+
+function onEmailAddressClick(event) {
+   onMenuClick(event, 'addressMenu');
+   event.preventDefault();
 }
 
 function onMessageAnchorClick (event) {
@@ -647,7 +667,7 @@ function messageCallback(http) {
       && http.status == 200) {
     document.messageAjaxRequest = null;
     div.innerHTML = http.responseText;
-    configureLinksInMessageDIV(div);
+    configureLinksInMessage();
 
     if (http.callbackData) {
       var cachedMessage = new Array();
@@ -738,21 +758,22 @@ function onMenuForwardMessage(event) {
 function newContactFromEmail(sender) {
   var mailto = sender.parentNode.parentNode.menuTarget.innerHTML;
 
-  var emailre
-    = /([a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z])/g;
-  emailre.exec(mailto);
-  email = RegExp.$1;
+//   var emailre
+//     = /([a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z])/g;
+//   emailre.exec(mailto);
+//   email = RegExp.$1;
 
-  var namere = /(\w[\w\ _-]+)\ (&lt;|<)/;
-  var c_name = '';
-  if (namere.test(mailto)) {
-    namere.exec(mailto);
-    c_name += RegExp.$1;
-  }
+//   var namere = /(\w[\w\ _-]+)\ (&lt;|<)/;
+//   var c_name = '';
+//   if (namere.test(mailto)) {
+//     namere.exec(mailto);
+//     c_name += RegExp.$1;
+//   }
 
+  var email = extractEmailAddress(mailto);
+  var c_name = extractEmailName(mailto);
   if (email.length > 0)
     {
-      emailre.exec("");
       var url = UserFolderURL + "Contacts/new?contactEmail=" + email;
       if (c_name)
         url += "&contactFN=" + c_name;
index 13d11feb55004cb4ec31fa295375bc3748b53ccf..ade37a731b7f7e1e0139aaac74c1c7072716c074 100644 (file)
@@ -141,12 +141,15 @@ DIV#rightPanel
   overflow: hidden; }
 
 DIV#appointmentsListView
-{ 
+{ display: block;
   position: absolute;
-  display: block;
   background: #fff;
+  top: 2.5em;
+  left: 0px;
+  right: 0px;
+  overflow: auto;
+  overflow-x: hidden;
   height: 15.5em;
-  margin: 0.5em 0px 0px 0px;
   min-width: 600px; }
 
 DIV#calendarView
@@ -292,16 +295,14 @@ DIV#calendarView A
   border: 1px solid #deebf7; }
 
 TABLE#appointmentsList
-{ ddisplay: table; }
-
-TABLE#appointmentsList td.tbtv_subject_headercell
- { width: 35%; }
-
-TABLE#appointmentsList td.headerDateTime
-{ width: 17em; }
+{ display: block;
+  position: relative;
+  height: 100%;
+  width: 100%; }
 
+TABLE#appointmentsList td.tbtv_subject_headercell,
 TABLE#appointmentsList td.headerLocation
-{ width: 20%; }
+{ width: 35%; }
 
 #dateSelector TD._selected,
 UL > LI._selected,
index c2f375a2a91883568ce7e76d0dc7ed54319ccc07..4358f979edd91db0ccdaf08bc9e39ca840b33106 100644 (file)
@@ -17,12 +17,17 @@ DIV.acls LABEL
 DIV#userSelectorHeader
 { margin: 1em; }
 
+INPUT#defaultRolesBtn
+{ position: absolute;
+  top: 5em;
+  left: 1em; }
+
 DIV#userRoles
 { position: absolute;
   padding: 0px;
   margin: 0px;
   left: 1em;
-  top: 5em;
+  top: 7em;
   right: 1em;
   bottom: 0px; }
 
index d7a3c6d09cfacc02fbe5c8f99e9725127d756ddb..74845b325c2fdfddbb2c63669ab9fc27151e05de 100644 (file)
@@ -1,6 +1,7 @@
 /* test */
 
 var contactSelectorAction = 'acls-contacts';
+var defaultUserID = '';
 
 function addUser(userName, userID) {
    if (!$(userID)) {
@@ -17,12 +18,17 @@ function addUser(userName, userID) {
 function addUserCallback(http) {
 }
 
+function setEventsOnUserNode(node) {
+   Event.observe(node, "mousedown", listRowMouseDownHandler);
+   Event.observe(node, "dblclick", onOpenUserRights);
+   Event.observe(node, "click", onRowClick);
+}
+
 function nodeForUser(userName, userId) {
    var node = document.createElement("li");
    node.setAttribute("id", userId);
    node.setAttribute("class", "");
-   node.addEventListener("mousedown", listRowMouseDownHandler, true);
-   node.addEventListener("click", onRowClick, true);
+   setEventsOnUserNode(node);
 
    var image = document.createElement("img");
    image.setAttribute("src", ResourcesURL + "/abcard.gif");
@@ -81,44 +87,50 @@ function subscribeToFolder(refreshCallback, refreshCallbackData) {
           refreshCallbackData["folder"]);
 }
 
+function openRightsForUserID(userID) {
+   var url = window.location.href;
+   var elements = url.split("/");
+   elements[elements.length-1] = "userRights?uid=" + userID;
+
+   window.open(elements.join("/"), "",
+              "width=" + this.userRightsWidth
+              + ",height=" + this.userRightsHeight
+              + ",resizable=0,scrollbars=0,toolbar=0,"
+              + "location=0,directories=0,status=0,menubar=0,copyhistory=0");
+}
+
 function openRightsForUser(button) {
   var nodes = $("userList").getSelectedRows();
-  if (nodes.length > 0) {
-    var url = window.location.href;
-    var elements = url.split("/");
-    elements[elements.length-1] = ("userRights?uid="
-                                   + nodes[0].getAttribute("id"));
-
-    window.open(elements.join("/"), "",
-               "width=" + this.userRightsWidth
-               + ",height=" + this.userRightsHeight
-               + ",resizable=0,scrollbars=0,toolbar=0,"
-               + "location=0,directories=0,status=0,menubar=0,copyhistory=0");
-  }
+  if (nodes.length > 0)
+     openRightsForUserID(nodes[0].getAttribute("id"));
 
   return false;
 }
 
+function openRightsForDefaultUser(event) {
+   openRightsForUserID(defaultUserID);
+   event.preventDefault();
+}
+
 function onOpenUserRights(event) {
    openRightsForUser();
    event.preventDefault();
 }
 
 function onAclLoadHandler() {
-  var ul = $("userList");
-  var lis = ul.childNodesWithTag("li");
-  for (var i = 0; i < lis.length; i++) {
-     lis[i].addEventListener("mousedown", listRowMouseDownHandler, false);
-     lis[i].addEventListener("dblclick", onOpenUserRights, false);
-     lis[i].addEventListener("click", onRowClick, false);
-  }
-
-  var buttons = $("userSelectorButtons").childNodesWithTag("a");
-  buttons[0].addEventListener("click", onUserAdd, false);
-  buttons[1].addEventListener("click", onUserRemove, false);
-
-  this.userRightsHeight = window.opener.getUsersRightsWindowHeight();
-  this.userRightsWidth = window.opener.getUsersRightsWindowWidth();
+   defaultUserID = $("defaultUserID").value;
+   Event.observe($("defaultRolesBtn"), "click", openRightsForDefaultUser);
+   var ul = $("userList");
+   var lis = ul.childNodesWithTag("li");
+   for (var i = 0; i < lis.length; i++)
+      setEventsOnUserNode(lis[i]);
+
+   var buttons = $("userSelectorButtons").childNodesWithTag("a");
+   Event.observe(buttons[0], "click", onUserAdd);
+   Event.observe(buttons[1], "click", onUserRemove);
+
+   this.userRightsHeight = window.opener.getUsersRightsWindowHeight();
+   this.userRightsWidth = window.opener.getUsersRightsWindowWidth();
 }
 
-window.addEventListener("load", onAclLoadHandler, false);
+Event.observe(window, "load", onAclLoadHandler);
index 420c635778cc3a467f9f6a44c2dd20af01915357..a15911de11f075bedcd1c8e79afdc02b68afdc9e 100644 (file)
@@ -218,6 +218,8 @@ DIV#toolbar
   white-space: nowrap;
   overflow: auto; }
 
+BODY.popup { min-width: 400px; }
+
 BODY.popup DIV#toolbar
 { border-top: 0px; }
 
@@ -432,7 +434,7 @@ TR.tableview TD
   padding-right: .3em; }
 
 TR.tableview TD.headerDateTime
-{ width: 14em; }
+{ width: 18em; }
 
 TR.tableview TD.headerLocation
 { width: 10em; }
index e343942892c9e29496509922763b4932fd6789b3..97385980d9073ac7b8cd3a164903930434fe399c 100644 (file)
@@ -941,7 +941,7 @@ function openExternalLink(anchor) {
 
 function openAclWindow(url) {
   var w = window.open(url, "aclWindow",
-                      "width=300,height=300,resizable=1,scrollbars=1,toolbar=0,"
+                      "width=420,height=300,resizable=1,scrollbars=1,toolbar=0,"
                       + "location=0,directories=0,status=0,menubar=0"
                       + ",copyhistory=0");
   w.opener = window;
index 0e85338bab9307f15331ae3fc1bc8e099c59bfb1..5d2100fac2dd69383a5d601d49181090cf106171 100644 (file)
@@ -1,17 +1,34 @@
-/*  Prototype JavaScript framework, version 1.4.0
- *  (c) 2005 Sam Stephenson <sam@conio.net>
+/*  Prototype JavaScript framework, version 1.5.1
+ *  (c) 2005-2007 Sam Stephenson
  *
  *  Prototype is freely distributable under the terms of an MIT-style license.
- *  For details, see the Prototype web site: http://prototype.conio.net/
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
  *
 /*--------------------------------------------------------------------------*/
 
 var Prototype = {
-  Version: '1.4.0',
-  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
+  Version: '1.5.1',
 
-  emptyFunction: function() {},
-  K: function(x) {return x}
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      (document.createElement('div').__proto__ !==
+       document.createElement('form').__proto__)
+  },
+
+  ScriptFragment: '<script[^>]*>([\u0001-\uFFFF]*?)</script>',
+  JSONFilter: /^\/\*-secure-\s*(.*)\s*\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
 }
 
 var Class = {
@@ -25,22 +42,62 @@ var Class = {
 var Abstract = new Object();
 
 Object.extend = function(destination, source) {
-  for (property in source) {
+  for (var property in source) {
     destination[property] = source[property];
   }
   return destination;
 }
 
-Object.inspect = function(object) {
-  try {
-    if (object == undefined) return 'undefined';
-    if (object == null) return 'null';
-    return object.inspect ? object.inspect() : object.toString();
-  } catch (e) {
-    if (e instanceof RangeError) return '...';
-    throw e;
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (object === undefined) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : object.toString();
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch(type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (object.ownerDocument === document) return;
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (value !== undefined)
+        results.push(property.toJSON() + ': ' + value);
+    }
+    return '{' + results.join(', ') + '}';
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({}, object);
   }
-}
+});
 
 Function.prototype.bind = function() {
   var __method = this, args = $A(arguments), object = args.shift();
@@ -50,17 +107,15 @@ Function.prototype.bind = function() {
 }
 
 Function.prototype.bindAsEventListener = function(object) {
-  var __method = this;
+  var __method = this, args = $A(arguments), object = args.shift();
   return function(event) {
-    return __method.call(object, event || window.event);
+    return __method.apply(object, [event || window.event].concat(args));
   }
 }
 
 Object.extend(Number.prototype, {
   toColorPart: function() {
-    var digits = this.toString(16);
-    if (this < 16) return '0' + digits;
-    return digits;
+    return this.toPaddedString(2, 16);
   },
 
   succ: function() {
@@ -70,14 +125,32 @@ Object.extend(Number.prototype, {
   times: function(iterator) {
     $R(0, this, true).each(iterator);
     return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
   }
 });
 
+Date.prototype.toJSON = function() {
+  return '"' + this.getFullYear() + '-' +
+    (this.getMonth() + 1).toPaddedString(2) + '-' +
+    this.getDate().toPaddedString(2) + 'T' +
+    this.getHours().toPaddedString(2) + ':' +
+    this.getMinutes().toPaddedString(2) + ':' +
+    this.getSeconds().toPaddedString(2) + '"';
+};
+
 var Try = {
   these: function() {
     var returnValue;
 
-    for (var i = 0; i < arguments.length; i++) {
+    for (var i = 0, length = arguments.length; i < length; i++) {
       var lambda = arguments[i];
       try {
         returnValue = lambda();
@@ -102,40 +175,83 @@ PeriodicalExecuter.prototype = {
   },
 
   registerCallback: function() {
-    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
   },
 
   onTimerEvent: function() {
     if (!this.currentlyExecuting) {
       try {
         this.currentlyExecuting = true;
-        this.callback();
+        this.callback(this);
       } finally {
         this.currentlyExecuting = false;
       }
     }
   }
 }
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
 
-/*--------------------------------------------------------------------------*/
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
 
-function $() {
-  var elements = new Array();
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = count === undefined ? 1 : count;
 
-  for (var i = 0; i < arguments.length; i++) {
-    var element = arguments[i];
-    if (typeof element == 'string')
-      element = document.getElementById(element);
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
 
-    if (arguments.length == 1)
-      return element;
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return this;
+  },
 
-    elements.push(element);
-  }
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = truncation === undefined ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : this;
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
 
-  return elements;
-}
-Object.extend(String.prototype, {
   stripTags: function() {
     return this.replace(/<\/?[^>]+>/gi, '');
   },
@@ -153,28 +269,40 @@ Object.extend(String.prototype, {
   },
 
   evalScripts: function() {
-    return this.extractScripts().map(eval);
+    return this.extractScripts().map(function(script) { return eval(script) });
   },
 
   escapeHTML: function() {
-    var div = document.createElement('div');
-    var text = document.createTextNode(this);
-    div.appendChild(text);
-    return div.innerHTML;
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
   },
 
   unescapeHTML: function() {
     var div = document.createElement('div');
     div.innerHTML = this.stripTags();
-    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
   },
 
-  toQueryParams: function() {
-    var pairs = this.match(/^\??(.*)$/)[1].split('&');
-    return pairs.inject({}, function(params, pairString) {
-      var pair = pairString.split('=');
-      params[pair[0]] = pair[1];
-      return params;
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return {};
+
+    return match[1].split(separator || '&').inject({}, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (hash[key].constructor != Array) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
     });
   },
 
@@ -182,46 +310,152 @@ Object.extend(String.prototype, {
     return this.split('');
   },
 
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    var result = '';
+    for (var i = 0; i < count; i++) result += this;
+    return result;
+  },
+
   camelize: function() {
-    var oStringList = this.split('-');
-    if (oStringList.length == 1) return oStringList[0];
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
 
-    var camelizedString = this.indexOf('-') == 0
-      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
-      : oStringList[0];
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
 
-    for (var i = 1, len = oStringList.length; i < len; i++) {
-      var s = oStringList[i];
-      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
-    }
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
 
-    return camelizedString;
+    return camelized;
   },
 
-  inspect: function() {
-    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(json)))
+        return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
   }
 });
 
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (typeof replacement == 'function') return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+}
+
 String.prototype.parseQuery = String.prototype.toQueryParams;
 
-var $break    = new Object();
-var $continue = new Object();
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create();
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+Template.prototype = {
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern  = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    return this.template.gsub(this.pattern, function(match) {
+      var before = match[1];
+      if (before == '\\') return match[2];
+      return before + String.interpret(object[match[3]]);
+    });
+  }
+}
+
+var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead');
 
 var Enumerable = {
   each: function(iterator) {
     var index = 0;
     try {
       this._each(function(value) {
-        try {
-          iterator(value, index++);
-        } catch (e) {
-          if (e != $continue) throw e;
-        }
+        iterator(value, index++);
       });
     } catch (e) {
       if (e != $break) throw e;
     }
+    return this;
+  },
+
+  eachSlice: function(number, iterator) {
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.map(iterator);
   },
 
   all: function(iterator) {
@@ -234,7 +468,7 @@ var Enumerable = {
   },
 
   any: function(iterator) {
-    var result = true;
+    var result = false;
     this.each(function(value, index) {
       if (result = !!(iterator || Prototype.K)(value, index))
         throw $break;
@@ -245,12 +479,12 @@ var Enumerable = {
   collect: function(iterator) {
     var results = [];
     this.each(function(value, index) {
-      results.push(iterator(value, index));
+      results.push((iterator || Prototype.K)(value, index));
     });
     return results;
   },
 
-  detect: function (iterator) {
+  detect: function(iterator) {
     var result;
     this.each(function(value, index) {
       if (iterator(value, index)) {
@@ -291,6 +525,14 @@ var Enumerable = {
     return found;
   },
 
+  inGroupsOf: function(number, fillWith) {
+    fillWith = fillWith === undefined ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
   inject: function(memo, iterator) {
     this.each(function(value, index) {
       memo = iterator(memo, value, index);
@@ -300,7 +542,7 @@ var Enumerable = {
 
   invoke: function(method) {
     var args = $A(arguments).slice(1);
-    return this.collect(function(value) {
+    return this.map(function(value) {
       return value[method].apply(value, args);
     });
   },
@@ -309,7 +551,7 @@ var Enumerable = {
     var result;
     this.each(function(value, index) {
       value = (iterator || Prototype.K)(value, index);
-      if (value >= (result || value))
+      if (result == undefined || value >= result)
         result = value;
     });
     return result;
@@ -319,7 +561,7 @@ var Enumerable = {
     var result;
     this.each(function(value, index) {
       value = (iterator || Prototype.K)(value, index);
-      if (value <= (result || value))
+      if (result == undefined || value < result)
         result = value;
     });
     return result;
@@ -352,7 +594,7 @@ var Enumerable = {
   },
 
   sortBy: function(iterator) {
-    return this.collect(function(value, index) {
+    return this.map(function(value, index) {
       return {value: value, criteria: iterator(value, index)};
     }).sort(function(left, right) {
       var a = left.criteria, b = right.criteria;
@@ -361,7 +603,7 @@ var Enumerable = {
   },
 
   toArray: function() {
-    return this.collect(Prototype.K);
+    return this.map();
   },
 
   zip: function() {
@@ -371,11 +613,14 @@ var Enumerable = {
 
     var collections = [this].concat(args).map($A);
     return this.map(function(value, index) {
-      iterator(value = collections.pluck(index));
-      return value;
+      return iterator(collections.pluck(index));
     });
   },
 
+  size: function() {
+    return this.toArray().length;
+  },
+
   inspect: function() {
     return '#<Enumerable:' + this.toArray().inspect() + '>';
   }
@@ -394,19 +639,35 @@ var $A = Array.from = function(iterable) {
     return iterable.toArray();
   } else {
     var results = [];
-    for (var i = 0; i < iterable.length; i++)
+    for (var i = 0, length = iterable.length; i < length; i++)
       results.push(iterable[i]);
     return results;
   }
 }
 
+if (Prototype.Browser.WebKit) {
+  $A = Array.from = function(iterable) {
+    if (!iterable) return [];
+    if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
+      iterable.toArray) {
+      return iterable.toArray();
+    } else {
+      var results = [];
+      for (var i = 0, length = iterable.length; i < length; i++)
+        results.push(iterable[i]);
+      return results;
+    }
+  }
+}
+
 Object.extend(Array.prototype, Enumerable);
 
-Array.prototype._reverse = Array.prototype.reverse;
+if (!Array.prototype._reverse)
+  Array.prototype._reverse = Array.prototype.reverse;
 
 Object.extend(Array.prototype, {
   _each: function(iterator) {
-    for (var i = 0; i < this.length; i++)
+    for (var i = 0, length = this.length; i < length; i++)
       iterator(this[i]);
   },
 
@@ -425,13 +686,13 @@ Object.extend(Array.prototype, {
 
   compact: function() {
     return this.select(function(value) {
-      return value != undefined || value != null;
+      return value != null;
     });
   },
 
   flatten: function() {
     return this.inject([], function(array, value) {
-      return array.concat(value.constructor == Array ?
+      return array.concat(value && value.constructor == Array ?
         value.flatten() : [value]);
     });
   },
@@ -444,7 +705,7 @@ Object.extend(Array.prototype, {
   },
 
   indexOf: function(object) {
-    for (var i = 0; i < this.length; i++)
+    for (var i = 0, length = this.length; i < length; i++)
       if (this[i] == object) return i;
     return -1;
   },
@@ -453,23 +714,110 @@ Object.extend(Array.prototype, {
     return (inline !== false ? this : this.toArray())._reverse();
   },
 
-  shift: function() {
-    var result = this[0];
-    for (var i = 0; i < this.length - 1; i++)
-      this[i] = this[i + 1];
-    this.length--;
-    return result;
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
   },
 
   inspect: function() {
     return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (value !== undefined) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
   }
 });
-var Hash = {
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (arguments[i].constructor == Array) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  }
+}
+var Hash = function(object) {
+  if (object instanceof Hash) this.merge(object);
+  else Object.extend(this, object || {});
+};
+
+Object.extend(Hash, {
+  toQueryString: function(obj) {
+    var parts = [];
+    parts.add = arguments.callee.addPair;
+
+    this.prototype._each.call(obj, function(pair) {
+      if (!pair.key) return;
+      var value = pair.value;
+
+      if (value && typeof value == 'object') {
+        if (value.constructor == Array) value.each(function(value) {
+          parts.add(pair.key, value);
+        });
+        return;
+      }
+      parts.add(pair.key, value);
+    });
+
+    return parts.join('&');
+  },
+
+  toJSON: function(object) {
+    var results = [];
+    this.prototype._each.call(object, function(pair) {
+      var value = Object.toJSON(pair.value);
+      if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
+    });
+    return '{' + results.join(', ') + '}';
+  }
+});
+
+Hash.toQueryString.addPair = function(key, value, prefix) {
+  key = encodeURIComponent(key);
+  if (value === undefined) this.push(key);
+  else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
+}
+
+Object.extend(Hash.prototype, Enumerable);
+Object.extend(Hash.prototype, {
   _each: function(iterator) {
-    for (key in this) {
+    for (var key in this) {
       var value = this[key];
-      if (typeof value == 'function') continue;
+      if (value && value == Hash.prototype[key]) continue;
 
       var pair = [key, value];
       pair.key = key;
@@ -487,31 +835,66 @@ var Hash = {
   },
 
   merge: function(hash) {
-    return $H(hash).inject($H(this), function(mergedHash, pair) {
+    return $H(hash).inject(this, function(mergedHash, pair) {
       mergedHash[pair.key] = pair.value;
       return mergedHash;
     });
   },
 
+  remove: function() {
+    var result;
+    for(var i = 0, length = arguments.length; i < length; i++) {
+      var value = this[arguments[i]];
+      if (value !== undefined){
+        if (result === undefined) result = value;
+        else {
+          if (result.constructor != Array) result = [result];
+          result.push(value)
+        }
+      }
+      delete this[arguments[i]];
+    }
+    return result;
+  },
+
   toQueryString: function() {
-    return this.map(function(pair) {
-      return pair.map(encodeURIComponent).join('=');
-    }).join('&');
+    return Hash.toQueryString(this);
   },
 
   inspect: function() {
     return '#<Hash:{' + this.map(function(pair) {
       return pair.map(Object.inspect).join(': ');
     }).join(', ') + '}>';
+  },
+
+  toJSON: function() {
+    return Hash.toJSON(this);
   }
-}
+});
 
 function $H(object) {
-  var hash = Object.extend({}, object || {});
-  Object.extend(hash, Enumerable);
-  Object.extend(hash, Hash);
-  return hash;
-}
+  if (object instanceof Hash) return object;
+  return new Hash(object);
+};
+
+// Safari iterates over shadowed properties
+if (function() {
+  var i = 0, Test = function(value) { this.key = value };
+  Test.prototype.key = 'foo';
+  for (var property in new Test('bar')) i++;
+  return i > 1;
+}()) Hash.prototype._each = function(iterator) {
+  var cache = [];
+  for (var key in this) {
+    var value = this[key];
+    if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
+    cache.push(key);
+    var pair = [key, value];
+    pair.key = key;
+    pair.value = value;
+    iterator(pair);
+  }
+};
 ObjectRange = Class.create();
 Object.extend(ObjectRange.prototype, Enumerable);
 Object.extend(ObjectRange.prototype, {
@@ -523,10 +906,10 @@ Object.extend(ObjectRange.prototype, {
 
   _each: function(iterator) {
     var value = this.start;
-    do {
+    while (this.include(value)) {
       iterator(value);
       value = value.succ();
-    } while (this.include(value));
+    }
   },
 
   include: function(value) {
@@ -545,9 +928,9 @@ var $R = function(start, end, exclusive) {
 var Ajax = {
   getTransport: function() {
     return Try.these(
+      function() {return new XMLHttpRequest()},
       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
-      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
-      function() {return new XMLHttpRequest()}
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
     ) || false;
   },
 
@@ -561,18 +944,18 @@ Ajax.Responders = {
     this.responders._each(iterator);
   },
 
-  register: function(responderToAdd) {
-    if (!this.include(responderToAdd))
-      this.responders.push(responderToAdd);
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
   },
 
-  unregister: function(responderToRemove) {
-    this.responders = this.responders.without(responderToRemove);
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
   },
 
   dispatch: function(callback, request, transport, json) {
     this.each(function(responder) {
-      if (responder[callback] && typeof responder[callback] == 'function') {
+      if (typeof responder[callback] == 'function') {
         try {
           responder[callback].apply(responder, [request, transport, json]);
         } catch (e) {}
@@ -587,7 +970,6 @@ Ajax.Responders.register({
   onCreate: function() {
     Ajax.activeRequestCount++;
   },
-
   onComplete: function() {
     Ajax.activeRequestCount--;
   }
@@ -599,19 +981,15 @@ Ajax.Base.prototype = {
     this.options = {
       method:       'post',
       asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
       parameters:   ''
     }
     Object.extend(this.options, options || {});
-  },
 
-  responseIsSuccess: function() {
-    return this.transport.status == undefined
-        || this.transport.status == 0
-        || (this.transport.status >= 200 && this.transport.status < 300);
-  },
-
-  responseIsFailure: function() {
-    return !this.responseIsSuccess();
+    this.options.method = this.options.method.toLowerCase();
+    if (typeof this.options.parameters == 'string')
+      this.options.parameters = this.options.parameters.toQueryParams();
   }
 }
 
@@ -620,6 +998,8 @@ Ajax.Request.Events =
   ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
 
 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
+  _complete: false,
+
   initialize: function(url, options) {
     this.transport = Ajax.getTransport();
     this.setOptions(options);
@@ -627,111 +1007,150 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
   },
 
   request: function(url) {
-    var parameters = this.options.parameters || '';
-    if (parameters.length > 0) parameters += '&_=';
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
 
-    try {
-      this.url = url;
-      if (this.options.method == 'get' && parameters.length > 0)
-        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
 
+    if (params = Hash.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      if (this.options.onCreate) this.options.onCreate(this.transport);
       Ajax.Responders.dispatch('onCreate', this, this.transport);
 
-      this.transport.open(this.options.method, this.url,
+      this.transport.open(this.method.toUpperCase(), this.url,
         this.options.asynchronous);
 
-      if (this.options.asynchronous) {
-        this.transport.onreadystatechange = this.onStateChange.bind(this);
-        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
-      }
+      if (this.options.asynchronous)
+        setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
 
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
       this.setRequestHeaders();
 
-      var body = this.options.postBody ? this.options.postBody : parameters;
-      this.transport.send(this.options.method == 'post' ? body : null);
-
-    } catch (e) {
-      this.dispatchException(e);
-    }
-  },
-
-  setRequestHeaders: function() {
-    var requestHeaders =
-      ['X-Requested-With', 'XMLHttpRequest',
-       'X-Prototype-Version', Prototype.Version];
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
 
-    if (this.options.method == 'post') {
-      requestHeaders.push('Content-type',
-        'application/x-www-form-urlencoded');
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
 
-      /* Force "Connection: close" for Mozilla browsers to work around
-       * a bug where XMLHttpReqeuest sends an incorrect Content-length
-       * header. See Mozilla Bugzilla #246651.
-       */
-      if (this.transport.overrideMimeType)
-        requestHeaders.push('Connection', 'close');
     }
-
-    if (this.options.requestHeaders)
-      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
-
-    for (var i = 0; i < requestHeaders.length; i += 2)
-      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
+    catch (e) {
+      this.dispatchException(e);
+    }
   },
 
   onStateChange: function() {
     var readyState = this.transport.readyState;
-    if (readyState != 1)
+    if (readyState > 1 && !((readyState == 4) && this._complete))
       this.respondToReadyState(this.transport.readyState);
   },
 
-  header: function(name) {
-    try {
-      return this.transport.getResponseHeader(name);
-    } catch (e) {}
-  },
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
 
-  evalJSON: function() {
-    try {
-      return eval(this.header('X-JSON'));
-    } catch (e) {}
-  },
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
 
-  evalResponse: function() {
-    try {
-      return eval(this.transport.responseText);
-    } catch (e) {
-      this.dispatchException(e);
+      if (typeof extras.push == 'function')
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
     }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    return !this.transport.status
+        || (this.transport.status >= 200 && this.transport.status < 300);
   },
 
   respondToReadyState: function(readyState) {
-    var event = Ajax.Request.Events[readyState];
+    var state = Ajax.Request.Events[readyState];
     var transport = this.transport, json = this.evalJSON();
 
-    if (event == 'Complete') {
+    if (state == 'Complete') {
       try {
+        this._complete = true;
         (this.options['on' + this.transport.status]
-         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
          || Prototype.emptyFunction)(transport, json);
       } catch (e) {
         this.dispatchException(e);
       }
 
-      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
-        this.evalResponse();
+      var contentType = this.getHeader('Content-type');
+      if (contentType && contentType.strip().
+        match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
+          this.evalResponse();
     }
 
     try {
-      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
-      Ajax.Responders.dispatch('on' + event, this, transport, json);
+      (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
+      Ajax.Responders.dispatch('on' + state, this, transport, json);
     } catch (e) {
       this.dispatchException(e);
     }
 
-    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
-    if (event == 'Complete')
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
       this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name);
+    } catch (e) { return null }
+  },
+
+  evalJSON: function() {
+    try {
+      var json = this.getHeader('X-JSON');
+      return json ? json.evalJSON() : null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
   },
 
   dispatchException: function(exception) {
@@ -744,41 +1163,37 @@ Ajax.Updater = Class.create();
 
 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
   initialize: function(container, url, options) {
-    this.containers = {
-      success: container.success ? $(container.success) : $(container),
-      failure: container.failure ? $(container.failure) :
-        (container.success ? null : $(container))
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
     }
 
     this.transport = Ajax.getTransport();
     this.setOptions(options);
 
     var onComplete = this.options.onComplete || Prototype.emptyFunction;
-    this.options.onComplete = (function(transport, object) {
+    this.options.onComplete = (function(transport, param) {
       this.updateContent();
-      onComplete(transport, object);
+      onComplete(transport, param);
     }).bind(this);
 
     this.request(url);
   },
 
   updateContent: function() {
-    var receiver = this.responseIsSuccess() ?
-      this.containers.success : this.containers.failure;
+    var receiver = this.container[this.success() ? 'success' : 'failure'];
     var response = this.transport.responseText;
 
-    if (!this.options.evalScripts)
-      response = response.stripScripts();
+    if (!this.options.evalScripts) response = response.stripScripts();
 
-    if (receiver) {
-      if (this.options.insertion) {
+    if (receiver = $(receiver)) {
+      if (this.options.insertion)
         new this.options.insertion(receiver, response);
-      } else {
-        Element.update(receiver, response);
-      }
+      else
+        receiver.update(response);
     }
 
-    if (this.responseIsSuccess()) {
+    if (this.success()) {
       if (this.onComplete)
         setTimeout(this.onComplete.bind(this), 10);
     }
@@ -807,7 +1222,7 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
   },
 
   stop: function() {
-    this.updater.onComplete = undefined;
+    this.updater.options.onComplete = undefined;
     clearTimeout(this.timer);
     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
   },
@@ -827,60 +1242,252 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
     this.updater = new Ajax.Updater(this.container, this.url, this.options);
   }
 });
-document.getElementsByClassName = function(className, parentElement) {
-  var children = ($(parentElement) || document.body).getElementsByTagName('*');
-  return $A(children).inject([], function(elements, child) {
-    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
-      elements.push(child);
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
     return elements;
-  });
+  }
+  if (typeof element == 'string')
+    element = document.getElementById(element);
+  return Element.extend(element);
 }
 
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(query.snapshotItem(i));
+    return results;
+  };
+
+  document.getElementsByClassName = function(className, parentElement) {
+    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
+    return document._getElementsByXPath(q, parentElement);
+  }
+
+} else document.getElementsByClassName = function(className, parentElement) {
+  var children = ($(parentElement) || document.body).getElementsByTagName('*');
+  var elements = [], child;
+  for (var i = 0, length = children.length; i < length; i++) {
+    child = children[i];
+    if (Element.hasClassName(child, className))
+      elements.push(Element.extend(child));
+  }
+  return elements;
+};
+
 /*--------------------------------------------------------------------------*/
 
-if (!window.Element) {
-  var Element = new Object();
-}
+if (!window.Element) var Element = {};
+
+Element.extend = function(element) {
+  var F = Prototype.BrowserFeatures;
+  if (!element || !element.tagName || element.nodeType == 3 ||
+   element._extended || F.SpecificElementExtensions || element == window)
+    return element;
+
+  var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
+   T = Element.Methods.ByTag;
+
+  // extend methods for all tags (Safari doesn't need this)
+  if (!F.ElementExtensions) {
+    Object.extend(methods, Element.Methods),
+    Object.extend(methods, Element.Methods.Simulated);
+  }
+
+  // extend methods for specific tags
+  if (T[tagName]) Object.extend(methods, T[tagName]);
 
-Object.extend(Element, {
+  for (var property in methods) {
+    var value = methods[property];
+    if (typeof value == 'function' && !(property in element))
+      element[property] = cache.findOrStore(value);
+  }
+
+  element._extended = Prototype.emptyFunction;
+  return element;
+};
+
+Element.extend.cache = {
+  findOrStore: function(value) {
+    return this[value] = this[value] || function() {
+      return value.apply(null, [this].concat($A(arguments)));
+    }
+  }
+};
+
+Element.Methods = {
   visible: function(element) {
     return $(element).style.display != 'none';
   },
 
-  toggle: function() {
-    for (var i = 0; i < arguments.length; i++) {
-      var element = $(arguments[i]);
-      Element[Element.visible(element) ? 'hide' : 'show'](element);
-    }
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
   },
 
-  hide: function() {
-    for (var i = 0; i < arguments.length; i++) {
-      var element = $(arguments[i]);
-      element.style.display = 'none';
-    }
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
   },
 
-  show: function() {
-    for (var i = 0; i < arguments.length; i++) {
-      var element = $(arguments[i]);
-      element.style.display = '';
-    }
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
   },
 
   remove: function(element) {
     element = $(element);
     element.parentNode.removeChild(element);
+    return element;
   },
 
   update: function(element, html) {
+    html = typeof html == 'undefined' ? '' : html.toString();
     $(element).innerHTML = html.stripScripts();
     setTimeout(function() {html.evalScripts()}, 10);
+    return element;
   },
 
-  getHeight: function(element) {
+  replace: function(element, html) {
+    element = $(element);
+    html = typeof html == 'undefined' ? '' : html.toString();
+    if (element.outerHTML) {
+      element.outerHTML = html.stripScripts();
+    } else {
+      var range = element.ownerDocument.createRange();
+      range.selectNodeContents(element);
+      element.parentNode.replaceChild(
+        range.createContextualFragment(html.stripScripts()), element);
+    }
+    setTimeout(function() {html.evalScripts()}, 10);
+    return element;
+  },
+
+  inspect: function(element) {
     element = $(element);
-    return element.offsetHeight;
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $A($(element).getElementsByTagName('*')).each(Element.extend);
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (typeof selector == 'string')
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return expression ? Selector.findElement(ancestors, expression, index) :
+      ancestors[index || 0];
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    var descendants = element.descendants();
+    return expression ? Selector.findElement(descendants, expression, index) :
+      descendants[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return expression ? Selector.findElement(previousSiblings, expression, index) :
+      previousSiblings[index || 0];
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return expression ? Selector.findElement(nextSiblings, expression, index) :
+      nextSiblings[index || 0];
+  },
+
+  getElementsBySelector: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  getElementsByClassName: function(element, className) {
+    return document.getElementsByClassName(className, element);
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      if (!element.attributes) return null;
+      var t = Element._attributeTranslations;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name])  name = t.names[name];
+      var attribute = element.attributes[name];
+      return attribute ? attribute.nodeValue : null;
+    }
+    return element.getAttribute(name);
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
   },
 
   classNames: function(element) {
@@ -889,67 +1496,114 @@ Object.extend(Element, {
 
   hasClassName: function(element, className) {
     if (!(element = $(element))) return;
-    return Element.classNames(element).include(className);
+    var elementClassName = element.className;
+    if (elementClassName.length == 0) return false;
+    if (elementClassName == className ||
+        elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
+      return true;
+    return false;
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    Element.classNames(element).add(className);
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    Element.classNames(element).remove(className);
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
+    return element;
   },
 
-  addClassName: function(element, className) {
-    if (!(element = $(element))) return;
-    return Element.classNames(element).add(className);
+  observe: function() {
+    Event.observe.apply(Event, arguments);
+    return $A(arguments).first();
   },
 
-  removeClassName: function(element, className) {
-    if (!(element = $(element))) return;
-    return Element.classNames(element).remove(className);
+  stopObserving: function() {
+    Event.stopObserving.apply(Event, arguments);
+    return $A(arguments).first();
   },
 
   // removes whitespace-only text node children
   cleanWhitespace: function(element) {
     element = $(element);
-    for (var i = 0; i < element.childNodes.length; i++) {
-      var node = element.childNodes[i];
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
       if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
-        Element.remove(node);
+        element.removeChild(node);
+      node = nextNode;
     }
+    return element;
   },
 
   empty: function(element) {
-    return $(element).innerHTML.match(/^\s*$/);
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    while (element = element.parentNode)
+      if (element == ancestor) return true;
+    return false;
   },
 
   scrollTo: function(element) {
     element = $(element);
-    var x = element.x ? element.x : element.offsetLeft,
-        y = element.y ? element.y : element.offsetTop;
-    window.scrollTo(x, y);
+    var pos = Position.cumulativeOffset(element);
+    window.scrollTo(pos[0], pos[1]);
+    return element;
   },
 
   getStyle: function(element, style) {
     element = $(element);
-    var value = element.style[style.camelize()];
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
     if (!value) {
-      if (document.defaultView && document.defaultView.getComputedStyle) {
-        var css = document.defaultView.getComputedStyle(element, null);
-        value = css ? css.getPropertyValue(style) : null;
-      } else if (element.currentStyle) {
-        value = element.currentStyle[style.camelize()];
-      }
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
     }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
 
-    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
-      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
 
-    return value == 'auto' ? null : value;
+  setStyle: function(element, styles, camelized) {
+    element = $(element);
+    var elementStyle = element.style;
+
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property])
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
+          (camelized ? property : property.camelize())] = styles[property];
+
+    return element;
   },
 
-  setStyle: function(element, style) {
+  setOpacity: function(element, value) {
     element = $(element);
-    for (name in style)
-      element.style[name.camelize()] = style[name];
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
   },
 
   getDimensions: function(element) {
     element = $(element);
-    if (Element.getStyle(element, 'display') != 'none')
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
       return {width: element.offsetWidth, height: element.offsetHeight};
 
     // All *Width and *Height properties give 0 on elements with display none,
@@ -957,12 +1611,13 @@ Object.extend(Element, {
     var els = element.style;
     var originalVisibility = els.visibility;
     var originalPosition = els.position;
+    var originalDisplay = els.display;
     els.visibility = 'hidden';
     els.position = 'absolute';
-    els.display = '';
+    els.display = 'block';
     var originalWidth = element.clientWidth;
     var originalHeight = element.clientHeight;
-    els.display = 'none';
+    els.display = originalDisplay;
     els.position = originalPosition;
     els.visibility = originalVisibility;
     return {width: originalWidth, height: originalHeight};
@@ -981,6 +1636,7 @@ Object.extend(Element, {
         element.style.left = 0;
       }
     }
+    return element;
   },
 
   undoPositioned: function(element) {
@@ -993,26 +1649,270 @@ Object.extend(Element, {
         element.style.bottom =
         element.style.right = '';
     }
+    return element;
   },
 
   makeClipping: function(element) {
     element = $(element);
-    if (element._overflow) return;
-    element._overflow = element.style.overflow;
+    if (element._overflow) return element;
+    element._overflow = element.style.overflow || 'auto';
     if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
       element.style.overflow = 'hidden';
+    return element;
   },
 
   undoClipping: function(element) {
     element = $(element);
-    if (element._overflow) return;
-    element.style.overflow = element._overflow;
-    element._overflow = undefined;
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
   }
+};
+
+Object.extend(Element.Methods, {
+  childOf: Element.Methods.descendantOf,
+  childElements: Element.Methods.immediateDescendants
 });
 
-var Toggle = new Object();
-Toggle.display = Element.toggle;
+if (Prototype.Browser.Opera) {
+  Element.Methods._getStyle = Element.Methods.getStyle;
+  Element.Methods.getStyle = function(element, style) {
+    switch(style) {
+      case 'left':
+      case 'top':
+      case 'right':
+      case 'bottom':
+        if (Element._getStyle(element, 'position') == 'static') return null;
+      default: return Element._getStyle(element, style);
+    }
+  };
+}
+else if (Prototype.Browser.IE) {
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset'+style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  // IE is missing .innerHTML support for TABLE-related elements
+  Element.Methods.update = function(element, html) {
+    element = $(element);
+    html = typeof html == 'undefined' ? '' : html.toString();
+    var tagName = element.tagName.toUpperCase();
+    if (['THEAD','TBODY','TR','TD'].include(tagName)) {
+      var div = document.createElement('div');
+      switch (tagName) {
+        case 'THEAD':
+        case 'TBODY':
+          div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
+          depth = 2;
+          break;
+        case 'TR':
+          div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
+          depth = 3;
+          break;
+        case 'TD':
+          div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
+          depth = 4;
+      }
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      depth.times(function() { div = div.firstChild });
+      $A(div.childNodes).each(function(node) { element.appendChild(node) });
+    } else {
+      element.innerHTML = html.stripScripts();
+    }
+    setTimeout(function() { html.evalScripts() }, 10);
+    return element;
+  }
+}
+else if (Prototype.Browser.Gecko) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+Element._attributeTranslations = {
+  names: {
+    colspan:   "colSpan",
+    rowspan:   "rowSpan",
+    valign:    "vAlign",
+    datetime:  "dateTime",
+    accesskey: "accessKey",
+    tabindex:  "tabIndex",
+    enctype:   "encType",
+    maxlength: "maxLength",
+    readonly:  "readOnly",
+    longdesc:  "longDesc"
+  },
+  values: {
+    _getAttr: function(element, attribute) {
+      return element.getAttribute(attribute, 2);
+    },
+    _flag: function(element, attribute) {
+      return $(element).hasAttribute(attribute) ? attribute : null;
+    },
+    style: function(element) {
+      return element.style.cssText.toLowerCase();
+    },
+    title: function(element) {
+      var node = element.getAttributeNode('title');
+      return node.specified ? node.nodeValue : null;
+    }
+  }
+};
+
+(function() {
+  Object.extend(this, {
+    href: this._getAttr,
+    src:  this._getAttr,
+    type: this._getAttr,
+    disabled: this._flag,
+    checked:  this._flag,
+    readonly: this._flag,
+    multiple: this._flag
+  });
+}).call(Element._attributeTranslations.values);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    var t = Element._attributeTranslations, node;
+    attribute = t.names[attribute] || attribute;
+    node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = {};
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+ document.createElement('div').__proto__) {
+  window.HTMLElement = {};
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || {});
+  else {
+    if (tagName.constructor == Array) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = {};
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    var cache = Element.extend.cache;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = cache.findOrStore(value);
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = {};
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (typeof klass == "undefined") continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+};
+
+var Toggle = { display: Element.toggle };
 
 /*--------------------------------------------------------------------------*/
 
@@ -1029,7 +1929,8 @@ Abstract.Insertion.prototype = {
       try {
         this.element.insertAdjacentHTML(this.adjacency, this.content);
       } catch (e) {
-        if (this.element.tagName.toLowerCase() == 'tbody') {
+        var tagName = this.element.tagName.toUpperCase();
+        if (['TBODY', 'TR'].include(tagName)) {
           this.insertContent(this.contentFromAnonymousTable());
         } else {
           throw e;
@@ -1128,220 +2029,814 @@ Element.ClassNames.prototype = {
 
   add: function(classNameToAdd) {
     if (this.include(classNameToAdd)) return;
-    this.set(this.toArray().concat(classNameToAdd).join(' '));
+    this.set($A(this).concat(classNameToAdd).join(' '));
   },
 
   remove: function(classNameToRemove) {
     if (!this.include(classNameToRemove)) return;
-    this.set(this.select(function(className) {
-      return className != classNameToRemove;
-    }).join(' '));
+    this.set($A(this).without(classNameToRemove).join(' '));
   },
 
   toString: function() {
-    return this.toArray().join(' ');
+    return $A(this).join(' ');
   }
-}
+};
 
 Object.extend(Element.ClassNames.prototype, Enumerable);
-var Field = {
-  clear: function() {
-    for (var i = 0; i < arguments.length; i++)
-      $(arguments[i]).value = '';
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create();
+
+Selector.prototype = {
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
   },
 
-  focus: function(element) {
-    $(element).focus();
+  compileMatcher: function() {
+    // Selectors with namespaced attributes can't use the XPath version
+    if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e]; return;
+    }
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
+             new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
   },
 
-  present: function() {
-    for (var i = 0; i < arguments.length; i++)
-      if ($(arguments[i]).value == '') return false;
-    return true;
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le,  m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(typeof x[i] == 'function' ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
   },
 
-  select: function(element) {
-    $(element).select();
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
   },
 
-  activate: function(element) {
-    element = $(element);
-    element.focus();
-    if (element.select)
-      element.select();
+  match: function(element) {
+    return this.findElements(document).include(element);
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
   }
-}
+};
 
-/*--------------------------------------------------------------------------*/
+Object.extend(Selector, {
+  _cache: {},
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: "[@#{1}]",
+    attr: function(m) {
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (typeof h === 'function') return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, m, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
 
-var Form = {
-  serialize: function(form) {
-    var elements = Form.getElements($(form));
-    var queryComponents = new Array();
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
+    className:    'n = h.className(n, r, "#{1}", c); c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+    },
+    pseudo:       function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._counted = true;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._counted) {
+          n._counted = true;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+             if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      tagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() == tagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!nodes && root == document) return targetNode ? [targetNode] : [];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr) {
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
 
-    for (var i = 0; i < elements.length; i++) {
-      var queryComponent = Form.Element.serialize(elements[i]);
-      if (queryComponent)
-        queryComponents.push(queryComponent);
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._counted) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._counted) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
     }
+  },
 
-    return queryComponents.join('&');
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
   },
 
-  getElements: function(form) {
-    form = $(form);
-    var elements = new Array();
+  matchElements: function(elements, expression) {
+    var matches = new Selector(expression).findElements(), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._counted) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
 
-    for (tagName in Form.Element.Serializers) {
-      var tagElements = form.getElementsByTagName(tagName);
-      for (var j = 0; j < tagElements.length; j++)
-        elements.push(tagElements[j]);
+  findElement: function(elements, expression, index) {
+    if (typeof expression == 'number') {
+      index = expression; expression = false;
     }
-    return elements;
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    var exprs = expressions.join(','), expressions = [];
+    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, getHash) {
+    var data = elements.inject({}, function(result, element) {
+      if (!element.disabled && element.name) {
+        var key = element.name, value = $(element).getValue();
+        if (value != null) {
+               if (key in result) {
+            if (result[key].constructor != Array) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return getHash ? data : Hash.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, getHash) {
+    return Form.serializeElements(Form.getElements(form), getHash);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
   },
 
   getInputs: function(form, typeName, name) {
     form = $(form);
     var inputs = form.getElementsByTagName('input');
 
-    if (!typeName && !name)
-      return inputs;
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
 
-    var matchingInputs = new Array();
-    for (var i = 0; i < inputs.length; i++) {
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
       var input = inputs[i];
-      if ((typeName && input.type != typeName) ||
-          (name && input.name != name))
+      if ((typeName && input.type != typeName) || (name && input.name != name))
         continue;
-      matchingInputs.push(input);
+      matchingInputs.push(Element.extend(input));
     }
 
     return matchingInputs;
   },
 
   disable: function(form) {
-    var elements = Form.getElements(form);
-    for (var i = 0; i < elements.length; i++) {
-      var element = elements[i];
-      element.blur();
-      element.disabled = 'true';
-    }
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
   },
 
   enable: function(form) {
-    var elements = Form.getElements(form);
-    for (var i = 0; i < elements.length; i++) {
-      var element = elements[i];
-      element.disabled = '';
-    }
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
   },
 
   findFirstElement: function(form) {
-    return Form.getElements(form).find(function(element) {
+    return $(form).getElements().find(function(element) {
       return element.type != 'hidden' && !element.disabled &&
         ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
     });
   },
 
   focusFirstElement: function(form) {
-    Field.activate(Form.findFirstElement(form));
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
   },
 
-  reset: function(form) {
-    $(form).reset();
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || {});
+
+    var params = options.parameters;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (typeof params == 'string') params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(form.readAttribute('action'), options);
   }
 }
 
+/*--------------------------------------------------------------------------*/
+
 Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+}
+
+Form.Element.Methods = {
   serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = {};
+        pair[element.name] = value;
+        return Hash.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
     element = $(element);
     var method = element.tagName.toLowerCase();
-    var parameter = Form.Element.Serializers[method](element);
+    return Form.Element.Serializers[method](element);
+  },
 
-    if (parameter) {
-      var key = encodeURIComponent(parameter[0]);
-      if (key.length == 0) return;
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
 
-      if (parameter[1].constructor != Array)
-        parameter[1] = [parameter[1]];
+  present: function(element) {
+    return $(element).value != '';
+  },
 
-      return parameter[1].map(function(value) {
-        return key + '=' + encodeURIComponent(value);
-      }).join('&');
-    }
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+        !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) {}
+    return element;
   },
 
-  getValue: function(element) {
+  disable: function(element) {
     element = $(element);
-    var method = element.tagName.toLowerCase();
-    var parameter = Form.Element.Serializers[method](element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
 
-    if (parameter)
-      return parameter[1];
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
   }
 }
 
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
 Form.Element.Serializers = {
   input: function(element) {
     switch (element.type.toLowerCase()) {
-      case 'submit':
-      case 'hidden':
-      case 'password':
-      case 'text':
-        return Form.Element.Serializers.textarea(element);
       case 'checkbox':
       case 'radio':
         return Form.Element.Serializers.inputSelector(element);
+      default:
+        return Form.Element.Serializers.textarea(element);
     }
-    return false;
   },
 
   inputSelector: function(element) {
-    if (element.checked)
-      return [element.name, element.value];
+    return element.checked ? element.value : null;
   },
 
   textarea: function(element) {
-    return [element.name, element.value];
+    return element.value;
   },
 
   select: function(element) {
-    return Form.Element.Serializers[element.type == 'select-one' ?
+    return this[element.type == 'select-one' ?
       'selectOne' : 'selectMany'](element);
   },
 
   selectOne: function(element) {
-    var value = '', opt, index = element.selectedIndex;
-    if (index >= 0) {
-      opt = element.options[index];
-      value = opt.value;
-      if (!value && !('value' in opt))
-        value = opt.text;
-    }
-    return [element.name, value];
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
   },
 
   selectMany: function(element) {
-    var value = new Array();
-    for (var i = 0; i < element.length; i++) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
       var opt = element.options[i];
-      if (opt.selected) {
-        var optValue = opt.value;
-        if (!optValue && !('value' in opt))
-          optValue = opt.text;
-        value.push(optValue);
-      }
+      if (opt.selected) values.push(this.optionValue(opt));
     }
-    return [element.name, value];
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
   }
 }
 
 /*--------------------------------------------------------------------------*/
 
-var $F = Form.Element.getValue;
-
-/*--------------------------------------------------------------------------*/
-
 Abstract.TimedObserver = function() {}
 Abstract.TimedObserver.prototype = {
   initialize: function(element, frequency, callback) {
@@ -1359,7 +2854,9 @@ Abstract.TimedObserver.prototype = {
 
   onTimerEvent: function() {
     var value = this.getValue();
-    if (this.lastValue != value) {
+    var changed = ('string' == typeof this.lastValue && 'string' == typeof value
+      ? this.lastValue != value : String(this.lastValue) != String(value));
+    if (changed) {
       this.callback(this.element, value);
       this.lastValue = value;
     }
@@ -1404,9 +2901,7 @@ Abstract.EventObserver.prototype = {
   },
 
   registerFormCallbacks: function() {
-    var elements = Form.getElements(this.element);
-    for (var i = 0; i < elements.length; i++)
-      this.registerCallback(elements[i]);
+    Form.getElements(this.element).each(this.registerCallback.bind(this));
   },
 
   registerCallback: function(element) {
@@ -1416,11 +2911,7 @@ Abstract.EventObserver.prototype = {
         case 'radio':
           Event.observe(element, 'click', this.onElementEvent.bind(this));
           break;
-        case 'password':
-        case 'text':
-        case 'textarea':
-        case 'select-one':
-        case 'select-multiple':
+        default:
           Event.observe(element, 'change', this.onElementEvent.bind(this));
           break;
       }
@@ -1455,9 +2946,13 @@ Object.extend(Event, {
   KEY_RIGHT:    39,
   KEY_DOWN:     40,
   KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
 
   element: function(event) {
-    return event.target || event.srcElement;
+    return $(event.target || event.srcElement);
   },
 
   isLeftClick: function(event) {
@@ -1510,7 +3005,7 @@ Object.extend(Event, {
 
   unloadCache: function() {
     if (!Event.observers) return;
-    for (var i = 0; i < Event.observers.length; i++) {
+    for (var i = 0, length = Event.observers.length; i < length; i++) {
       Event.stopObserving.apply(this, Event.observers[i]);
       Event.observers[i][0] = null;
     }
@@ -1518,36 +3013,37 @@ Object.extend(Event, {
   },
 
   observe: function(element, name, observer, useCapture) {
-    var element = $(element);
+    element = $(element);
     useCapture = useCapture || false;
 
     if (name == 'keypress' &&
-        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
-        || element.attachEvent))
+      (Prototype.Browser.WebKit || element.attachEvent))
       name = 'keydown';
 
-    this._observeAndCache(element, name, observer, useCapture);
+    Event._observeAndCache(element, name, observer, useCapture);
   },
 
   stopObserving: function(element, name, observer, useCapture) {
-    var element = $(element);
+    element = $(element);
     useCapture = useCapture || false;
 
     if (name == 'keypress' &&
-        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
-        || element.detachEvent))
+        (Prototype.Browser.WebKit || element.attachEvent))
       name = 'keydown';
 
     if (element.removeEventListener) {
       element.removeEventListener(name, observer, useCapture);
     } else if (element.detachEvent) {
-      element.detachEvent('on' + name, observer);
+      try {
+        element.detachEvent('on' + name, observer);
+      } catch (e) {}
     }
   }
 });
 
 /* prevent memory leaks in IE */
-Event.observe(window, 'unload', Event.unloadCache, false);
+if (Prototype.Browser.IE)
+  Event.observe(window, 'unload', Event.unloadCache, false);
 var Position = {
   // set to true if needed, warning: firefox performance problems
   // NOT neeeded for page scrolling, only if draggable contained in
@@ -1594,7 +3090,8 @@ var Position = {
       valueL += element.offsetLeft || 0;
       element = element.offsetParent;
       if (element) {
-        p = Element.getStyle(element, 'position');
+        if(element.tagName=='BODY') break;
+        var p = Element.getStyle(element, 'position');
         if (p == 'relative' || p == 'absolute') break;
       }
     } while (element);
@@ -1650,17 +3147,6 @@ var Position = {
         element.offsetWidth;
   },
 
-  clone: function(source, target) {
-    source = $(source);
-    target = $(target);
-    target.style.position = 'absolute';
-    var offsets = this.cumulativeOffset(source);
-    target.style.top    = offsets[1] + 'px';
-    target.style.left   = offsets[0] + 'px';
-    target.style.width  = source.offsetWidth + 'px';
-    target.style.height = source.offsetHeight + 'px';
-  },
-
   page: function(forElement) {
     var valueT = 0, valueL = 0;
 
@@ -1670,15 +3156,17 @@ var Position = {
       valueL += element.offsetLeft || 0;
 
       // Safari fix
-      if (element.offsetParent==document.body)
+      if (element.offsetParent == document.body)
         if (Element.getStyle(element,'position')=='absolute') break;
 
     } while (element = element.offsetParent);
 
     element = forElement;
     do {
-      valueT -= element.scrollTop  || 0;
-      valueL -= element.scrollLeft || 0;
+      if (!window.opera || element.tagName=='BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
     } while (element = element.parentNode);
 
     return [valueL, valueT];
@@ -1739,10 +3227,10 @@ var Position = {
     element._originalHeight = element.style.height;
 
     element.style.position = 'absolute';
-    element.style.top    = top + 'px';;
-    element.style.left   = left + 'px';;
-    element.style.width  = width + 'px';;
-    element.style.height = height + 'px';;
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
   },
 
   relativize: function(element) {
@@ -1764,7 +3252,7 @@ var Position = {
 // Safari returns margins on body which is incorrect if the child is absolutely
 // positioned.  For performance reasons, redefine Position.cumulativeOffset for
 // KHTML/WebKit only.
-if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+if (Prototype.Browser.WebKit) {
   Position.cumulativeOffset = function(element) {
     var valueT = 0, valueL = 0;
     do {
@@ -1778,4 +3266,6 @@ if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
 
     return [valueL, valueT];
   }
-}
\ No newline at end of file
+}
+
+Element.addMethods();
\ No newline at end of file