]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1299 d1b88da0-ebda-0310...
authorwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Thu, 13 Dec 2007 00:02:21 +0000 (00:02 +0000)
committerwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Thu, 13 Dec 2007 00:02:21 +0000 (00:02 +0000)
17 files changed:
ChangeLog
SoObjects/Mailer/SOGoMailObject.h
SoObjects/Mailer/SOGoMailObject.m
SoObjects/SOGo/NSString+Utilities.m
UI/MailPartViewers/UIxMailPartHTMLViewer.m
UI/MailPartViewers/UIxMailPartTextViewer.m
UI/MailerUI/German.lproj/Localizable.strings
UI/Scheduler/UIxCalListingActions.m
UI/Scheduler/UIxComponentEditor.h
UI/Scheduler/UIxComponentEditor.m
UI/Templates/SchedulerUI/UIxComponentEditor.wox
UI/WebServerResources/SchedulerUI.css
UI/WebServerResources/SchedulerUI.js
UI/WebServerResources/UIxAttendeesEditor.css
UI/WebServerResources/UIxAttendeesEditor.js
UI/WebServerResources/UIxMailEditor.js
UI/WebServerResources/iefixes.css

index ab8ee20da4dc3733ac72d7569797be157ae32a46..c0cf8e7b62e9122ab0b0c877aae068e3f11e8e7e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,34 @@
+2007-12-12  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+
+       * SoObjects/SOGo/NSString+Utilities.m ([NSString
+       -stringByDetectingURLs]): we now go back until the real start of
+       the found url.
+
+       * SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject
+       -fetchAttachmentIds]): new method that wanders through the mail
+       structure to collect the attachment content ids and to associate
+       them with their url.
+
+       * UI/MailPartViewers/UIxMailPartHTMLViewer.m
+       ([_UIxHTMLMailContentHandler
+       -startElement:_localNamenamespace:_nsrawName:_rawNameattributes:_attributes]):
+       the content-ids are now enclosed between "<>" before retrieval
+       from the attachment dictionary.
+       ([UIxMailPartHTMLViewer -cssContent])
+       ([UIxMailPartHTMLViewer -flatContentAsString]): the content-ids
+       are now fetch from the clientobject (an instance of
+       SOGoMailObject) with the new "fetchAttachmentIds" method.
+
+2007-12-12  Francis Lachapelle  <flachapelle@inverse.ca>
+
+       * UI/Scheduler/UIxCalListingActions.m ([UIxCalListingActions
+       -eventsListAction]): added the state of the calendar's owner with
+       respect to the current event.
+
+       * UI/Scheduler/UIxComponentEditor.m ([UIxComponentEditor
+       -_loadAttendees]): added the retrieval of attendees state on
+       current event.
+
 2007-12-12  Ludovic Marcotte <ludovic@inverse.ca>
 
        * UI/MailPartViewers/UIxMailRenderingContext.{h,m} 
index fa0b5458448af027c7bd5f6170e122e83f9111dc..e4f873bfaec7ecb9a2b37ecafdc2dd35c6ffeaa1 100644 (file)
@@ -89,6 +89,8 @@
 - (NSDictionary *) fetchPlainTextParts;
 - (NSDictionary *) fetchPlainTextStrings:(NSArray *)_fetchKeys;
 
+- (NSDictionary *) fetchAttachmentIds;
+
 /* flags */
 
 - (NSException *) addFlags:(id)_f;
index a28d752e68c9c36caf38d96aa393faff4775461c..6c38725f0a65d8a3c5e32fdcd442c8570140810d 100644 (file)
@@ -24,6 +24,7 @@
 #import <Foundation/NSDictionary.h>
 #import <Foundation/NSEnumerator.h>
 #import <Foundation/NSString.h>
+#import <Foundation/NSURL.h>
 #import <Foundation/NSUserDefaults.h>
 #import <Foundation/NSValue.h>
 
@@ -644,6 +645,72 @@ static BOOL debugSoParts       = NO;
   return [self fetchPlainTextParts: [self plainTextContentFetchKeys]];
 }
 
+- (NSString *) _urlToPart: (NSDictionary *) infos
+              withPrefix: (NSString *) urlPrefix
+{
+  NSDictionary *parameters;
+  NSString *urlToPart, *filename;
+
+  parameters = [infos objectForKey: @"parameterList"];
+  filename = [parameters objectForKey: @"name"];
+  if (!filename)
+    {
+      parameters = [[infos objectForKey: @"disposition"]
+                    objectForKey: @"parameterList"];
+      filename = [parameters objectForKey: @"filename"];
+    }
+
+  if ([filename length])
+    urlToPart = [NSString stringWithFormat: @"%@/%@", urlPrefix, filename];
+  else
+    urlToPart = nil;
+
+  return urlToPart;
+}
+
+- (void) _feedAttachmentIds: (NSMutableDictionary *) attachmentIds
+                 withInfos: (NSDictionary *) infos
+                 andPrefix: (NSString *) prefix
+{
+  NSArray *parts;
+  NSDictionary *currentPart;
+  unsigned int count, max;
+  NSString *url, *cid;
+
+  cid = [infos objectForKey: @"bodyId"];
+  if ([cid length])
+    {
+      url = [self _urlToPart: infos withPrefix: prefix];
+      if (url)
+       [attachmentIds setObject: url forKey: cid];
+    }
+
+  parts = [infos objectForKey: @"parts"];
+  max = [parts count];
+  for (count = 0; count < max; count++)
+    {
+      currentPart = [parts objectAtIndex: count];
+      [self _feedAttachmentIds: attachmentIds
+           withInfos: currentPart
+           andPrefix: [NSString stringWithFormat: @"%@/%d",
+                                prefix, count + 1]];
+    }
+}
+
+- (NSDictionary *) fetchAttachmentIds
+{
+  NSMutableDictionary *attachmentIds;
+
+  attachmentIds = [NSMutableDictionary dictionary];
+
+  [self fetchCoreInfos];
+  [self _feedAttachmentIds: attachmentIds
+       withInfos: [coreInfos objectForKey: @"body"]
+       andPrefix: [[self soURL] absoluteString]];
+
+  return attachmentIds;
+}
+
 /* convert parts to strings */
 - (NSString *) stringForData: (NSData *) _data
                    partInfo: (NSDictionary *) _info
index 1192ccd70d20e3b517da61896445c4085e6603d6..ff0c38d036b5cd02aa537d0197d7ac1477a55df9 100644 (file)
@@ -33,6 +33,7 @@
 
 static NSMutableCharacterSet *urlNonEndingChars = nil;
 static NSMutableCharacterSet *urlAfterEndingChars = nil;
+static NSMutableCharacterSet *urlStartChars = nil;
 
 @implementation NSString (SOGoURLExtension)
 
@@ -150,12 +151,12 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil;
   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;
@@ -187,9 +188,26 @@ static NSMutableCharacterSet *urlAfterEndingChars = nil;
   NSRange httpRange, currentURL, rest;
   NSString *urlText, *newUrlText;
   unsigned int length, matchLength;
+  int startLocation;
 
+  if (!urlStartChars)
+    {
+      urlStartChars = [NSMutableCharacterSet new];
+      [urlStartChars addCharactersInString: @"abcdefghijklmnopqrstuvwxyz"
+                    @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                    @"0123456789:@"];
+    }
   matchLength = [match length];
   httpRange = [selfCopy rangeOfString: match];
+  if (httpRange.location != NSNotFound)
+    {
+      startLocation = httpRange.location;
+      while (startLocation > -1
+            && [urlStartChars characterIsMember:
+                                [selfCopy characterAtIndex: startLocation]])
+       startLocation--;
+      httpRange.location = startLocation + 1;
+    }
   while (httpRange.location != NSNotFound)
     {
       if ([ranges hasRangeIntersection: httpRange])
index 6644e30ff3d9f4773b84d61523bd8b6471eea74f..94c7055de4166741d238ccef1f19276eded4b4e2 100644 (file)
@@ -35,6 +35,8 @@
 
 #include <libxml/encoding.h>
 
+#import <SoObjects/Mailer/SOGoMailObject.h>
+
 #import "UIxMailPartHTMLViewer.h"
 
 #if 0
            attributes: (id <SaxAttributes>) _attributes
 {
   unsigned int count, max;
-  NSString *name, *value;
+  NSString *name, *value, *cid;
   NSMutableString *resultPart;
   BOOL skipAttribute;
 
               value = [_attributes valueAtIndex: count];
               if ([value hasPrefix: @"cid:"])
                 {
-                  value = [attachmentIds
-                            objectForKey: [value substringFromIndex: 4]];
+                 cid = [NSString stringWithFormat: @"<%@>",
+                                 [value substringFromIndex: 4]];
+                  value = [attachmentIds objectForKey: cid];
                   skipAttribute = (value == nil);
                 }
               else
   [super dealloc];
 }
 
-- (void) _convertReferencesForPart: (NSDictionary *) part
-                         withCount: (unsigned int) count
-                        andBaseURL: (NSString *) url
-                    intoDictionary: (NSMutableDictionary *) attachmentIds
-{
-  NSString *bodyId, *filename;
-  NSMutableString *attachmentURL;
-
-  bodyId = [part objectForKey: @"bodyId"];
-  if ([bodyId length] > 0)
-    {
-      filename = [[part objectForKey: @"parameterList"] objectForKey: @"name"];
-      if (!filename)
-       filename = [[[part objectForKey: @"disposition"]
-                     objectForKey: @"parameterList"]
-                    objectForKey: @"filename"];
-      if ([bodyId hasPrefix: @"<"])
-        bodyId = [bodyId substringFromIndex: 1];
-      if ([bodyId hasSuffix: @">"])
-        bodyId = [bodyId substringToIndex: [bodyId length] - 1];
-      attachmentURL = [NSMutableString stringWithString: url];
-      [attachmentURL appendFormat: @"/%d", count];
-      if ([filename length])
-       [attachmentURL appendFormat: @"/%@", filename];
-      [attachmentIds setObject: attachmentURL forKey: bodyId];
-    }
-}
-
-- (NSDictionary *) _attachmentIds
-{
-  NSMutableDictionary *attachmentIds;
-  UIxMailPartViewer *parent;
-  unsigned int count, max;
-//   NSMutableString *url;
-  NSString *baseURL;
-  NSArray *parts;
-
-  attachmentIds = [NSMutableDictionary new];
-  [attachmentIds autorelease];
-  
-  parent = [self parent];
-  if ([NSStringFromClass ([parent class])
-                         isEqualToString: @"UIxMailPartAlternativeViewer"])
-    {
-      baseURL = [[self clientObject] baseURLInContext: context];
-//       url = [NSMutableString new];
-//       [url appendString: baseURL];
-//       [url appendFormat: @"/%@", [partPath componentsJoinedByString: @"/"]];
-//       [url deleteCharactersInRange: NSMakeRange([url length] - 4, 4)];
-      parts = [[[parent parent] bodyInfo] objectForKey: @"parts"];
-      max = [parts count];
-      for (count = 0; count < max; count++)
-        [self _convertReferencesForPart: [parts objectAtIndex: count]
-              withCount: count + 1
-              andBaseURL: baseURL
-              intoDictionary: attachmentIds];
-//       [url release];
-    }
-
-  return attachmentIds;
-}
-
 - (xmlCharEncoding) _xmlCharsetForCharset: (NSString *) charset
 {
   struct { NSString *name; xmlCharEncoding encoding; } xmlEncodings[] = {
 {
   NSObject <SaxXMLReader> *parser;
   NSData *preparsedContent;
+  SOGoMailObject *mail;
+
+  mail = [self clientObject];
 
   preparsedContent = [super decodedFlatContent];
   parser = [[SaxXMLReaderFactory standardXMLReaderFactory]
              createXMLReaderForMimeType: @"text/html"];
 
   handler = [_UIxHTMLMailContentHandler new];
-  [handler setAttachmentIds: [self _attachmentIds]];
+  [handler setAttachmentIds: [mail fetchAttachmentIds]];
   [handler setContentEncoding: [self _xmlCharEncoding]];
   [parser setContentHandler: handler];
   [parser parseFromSource: preparsedContent];
index 27ecea50761b9fe3c3d3afe8ed6fd996d5985a37..e84ce944b335eed8a6a9590acad516a7f11005e2 100644 (file)
@@ -127,7 +127,8 @@ convertChars (const char *oldString, unsigned int oldLength,
 
   superContent = [[super flatContentAsString] stringByEscapingHTMLString];
 
-  return [[superContent stringByDetectingURLs] stringByConvertingCRLNToHTML];
+  return [[superContent stringByDetectingURLs]
+          stringByConvertingCRLNToHTML];
 }
 
 @end /* UIxMailPartTextViewer */
index b0b8dcef603820873f19beb75ac0b067dec27a75..b89dc7ed671954c88c8a1be46388227fdff0249f 100644 (file)
 "Create Filter From Message..." = "Filter aus Nachricht erstellen...";
 
 /* Image Popup menu */
-"Save Image" = "Save Image";
+"Save Image" = "Bild speichern";
 
 /* Mailbox popup menus */
 "Open in New Mail Window" = "In neuem Fenster öffnen";
index 5e8905a355ba5a8f826e7f6ce58cd00144764c56..e41f23e0a8ed57530933218edbe6ce530ed43162 100644 (file)
@@ -33,6 +33,7 @@
 #import <NGObjWeb/WORequest.h>
 #import <NGObjWeb/WOResponse.h>
 #import <NGExtensions/NSCalendarDate+misc.h>
+#import <NGCards/iCalPerson.h>
 
 #import <SoObjects/SOGo/SOGoUser.h>
 #import <SoObjects/SOGo/SOGoDateFormatter.h>
 
 - (WOResponse *) eventsListAction
 {
-  NSArray *fields, *oldEvent;
+  NSArray *fields, *oldEvent, *participants, *states;
   NSEnumerator *events;
   NSMutableArray *newEvents, *newEvent;
-  unsigned int interval;
+  unsigned int interval, i;
   BOOL isAllDay;
-  NSString *sort, *ascending;
+  NSString *sort, *ascending, *participant, *state;
+  SOGoUser *user;
 
   [self _setupContext];
 
   newEvents = [NSMutableArray array];
   fields = [NSArray arrayWithObjects: @"c_name", @"c_folder", @"c_status",
                    @"c_title", @"c_startdate", @"c_enddate", @"c_location",
-                   @"c_isallday", @"c_classification", nil];
+                   @"c_isallday", @"c_classification", @"c_partmails", @"c_partstates", nil];
   events = [[self _fetchFields: fields
                  forComponentOfType: @"vevent"] objectEnumerator];
+  user = [[self context] activeUser];
   oldEvent = [events nextObject];
   while (oldEvent)
     {
                                  forAllDay: isAllDay]];
       [newEvent addObject: [self _formattedDateForSeconds: interval
                                 forAllDay: isAllDay]];
+
+      participants = state = nil;
+      if ([[oldEvent objectAtIndex: 9] length] > 0 &&
+         [[oldEvent objectAtIndex: 10] length] > 0) {
+       participants = [[oldEvent objectAtIndex: 9] componentsSeparatedByString: @"\n"];
+       states = [[oldEvent objectAtIndex: 10] componentsSeparatedByString: @"\n"];
+       for (i = 0; i < [participants count]; i++) {
+         participant = [participants objectAtIndex: i];
+         if ([user hasEmail: participant]) {
+           switch ([[states objectAtIndex: i] intValue]) {
+           case iCalPersonPartStatNeedsAction:
+             state = @"needs-action";
+             break;
+           case iCalPersonPartStatAccepted:
+             state = @"accepted";
+             break;
+           case iCalPersonPartStatDeclined:
+             state = @"declined";
+             break;
+           }
+           [newEvent replaceObjectAtIndex: 9 withObject: state];
+           break;
+         }
+       }
+      }
+      if (participants == nil || i == [participants count])
+       [newEvent replaceObjectAtIndex: 9 withObject: @""];
+      [newEvent removeObjectAtIndex: 10];
+
       [newEvents addObject: newEvent];
       
       oldEvent = [events nextObject];
index 34b2688dae280e2739cdd39fd24cd6e6c0fc8aa9..e6172e002e97fc7e0fdd1ce2ef81ee7f77aa1058 100644 (file)
@@ -63,6 +63,7 @@
   NSString *attendeesNames;
   NSString *attendeesUIDs;
   NSString *attendeesEmails;
+  NSString *attendeesStates;
 }
 
 - (NSString *) toolbar;
index 7384965f01274739eb7a429f3f5eade05af13778..4e61724dacdfd20ed41b64eedf645892ae4c3ac7 100644 (file)
@@ -73,6 +73,7 @@
       attendeesNames = nil;
       attendeesUIDs = nil;
       attendeesEmails = nil;
+      attendeesStates = nil;
       calendarList = nil;
     }
 
@@ -96,6 +97,7 @@
   [attendeesNames release];
   [attendeesUIDs release];
   [attendeesEmails release];
+  [attendeesStates release];
   [calendarList release];
 
   [component release];
 {
   NSEnumerator *attendees;
   iCalPerson *currentAttendee;
-  NSMutableString *names, *uids, *emails;
+  NSMutableString *names, *uids, *emails, *states;
   NSString *uid;
   LDAPUserManager *um;
 
   names = [NSMutableString new];
   uids = [NSMutableString new];
   emails = [NSMutableString new];
+  states = [NSMutableString new];
   um = [LDAPUserManager sharedUserManager];
 
   attendees = [[component attendees] objectEnumerator];
        [uids appendFormat: @"%@,", uid];
       else
        [uids appendString: @","];
+      [states appendFormat: @"%@,", [[currentAttendee partStat] lowercaseString]];
       currentAttendee = [attendees nextObject];
     }
 
       ASSIGN (attendeesUIDs, [uids substringToIndex: [uids length] - 1]);
       ASSIGN (attendeesEmails,
              [emails substringToIndex: [emails length] - 1]);
+      ASSIGN (attendeesStates, [states substringToIndex: [states length] - 1]);
     }
 
   [names release];
   return attendeesEmails;
 }
 
+- (void) setAttendeesStates: (NSString *) newAttendeesStates
+{
+  ASSIGN (attendeesStates, newAttendeesStates);
+}
+
+- (NSString *) attendeesStates
+{
+  return attendeesStates;
+}
+
 - (void) setLocation: (NSString *) _value
 {
   ASSIGN (location, _value);
index 84b1d03246db495466f206227e66b33af2a057f2..044a1d0e9a19cfae043127a04c42bdabfe0e9dfd 100644 (file)
@@ -91,6 +91,8 @@
        var:value="attendeesUIDs"/>
       <input type="hidden" name="attendeesEmails" id="attendeesEmails"
        var:value="attendeesEmails"/>
+      <input type="hidden" name="attendeesStates" id="attendeesStates"
+       var:value="attendeesStates"/>
       <input type="hidden" name="calendarFoldersList"
        id="calendarFoldersList"
        var:value="calendarsFoldersList"/>
index 98a683c855b5c13080f85bfb4919abfd95a29ae8..6f4c9f7227902565dcc22227fc667adae53c329e 100644 (file)
@@ -902,6 +902,26 @@ DIV.monthView DIV.event
 DIV.event DIV.text
 { font-size: 92%; }
 
+DIV.event.needs-action DIV.text
+{ background-image: url("needs-action.png");
+  background-repeat: no-repeat;
+  background-position: 98% 95%; }
+
+DIV.event.accepted DIV.text
+{ background-image: url("accepted.png"); 
+  background-repeat: no-repeat;
+  background-position: 98% 95%; }
+
+DIV.event.declined DIV.text
+{ background-image: url("declined.png"); 
+  background-repeat: no-repeat;
+  background-position: 98% 95%; }
+
+DIV.event.tentative DIV.text
+{ background-image: url("tentative.png"); 
+  background-repeat: no-repeat;
+  background-position: 98% 95%; }
+
 DIV#daysView DIV[class~="event"].starts0
 { top: 0.000000%; }
 
index 8dd803d1062cce318802928183e1ca329c034d6e..273a21e5fc2fa618bb009baae7f5dcacd26f0bea 100644 (file)
@@ -760,6 +760,8 @@ function drawCalendarEvent(eventData, sd, ed) {
                                    null, null, title);
         siblings.push(eventDiv);
         eventDiv.siblings = siblings;
+        if (eventData[9].length > 0)
+          eventDiv.addClassName(eventData[9]);
         var dayString = days[i].getDayString();
 //      log("day: " + dayString);
         var parentDiv = null;
index 808d0012a7cf66972c2de66d39c00b0195fcabbf..f18682828dbe9d7d2553d9581b50d57a772cc891 100644 (file)
@@ -19,6 +19,7 @@ DIV#freeBusyView
   top: 2em;
   bottom: 14.5em;
   left: 13em;
+  right: 0px;
   overflow: auto;
   border-top: 2px solid #222;
   border-left: 2px solid #222;
@@ -58,7 +59,7 @@ TABLE#freeBusy TD.attendees IMG
   top: 0.5em; }
 
 TABLE#freeBusy TD.attendees INPUT
-{ background-image: url('/SOGo.woa/WebServerResources/abcard.gif');
+{ background-image: url("abcard.gif");
   background-repeat: no-repeat;
   background-position: 2px center;
   width: 11.5em;
@@ -66,6 +67,27 @@ TABLE#freeBusy TD.attendees INPUT
   padding-left: 24px;
   margin-left: 5px; }
 
+TABLE#freeBusy TR.needs-action TD.attendees
+{ background-image: url("needs-action.png");
+  background-repeat: no-repeat;
+  background-position: 5px center; }
+
+TABLE#freeBusy TR.declined TD.attendees
+{ background-image: url("declined.png");
+  background-repeat: no-repeat;
+  background-position: 5px center; }
+
+TABLE#freeBusy TR.accepted TD.attendees
+{ background-image: url("accepted.png");
+  background-repeat: no-repeat;
+  background-position: 5px center; }
+
+TABLE#freeBusy TR.needs-action INPUT,
+TABLE#freeBusy TR.accepted INPUT,
+TABLE#freeBusy TR.declined INPUT
+{ margin-left: 1.5em;
+  width: 10em; }
+
 TABLE#freeBusy TR.futureAttendee INPUT
 { background-image: none; }
 
index 7c242a3cca67e7cd1ecfc04cccbab94e67ee1d48..14aae456e4826992029a630a51d9ba6741ee2704 100644 (file)
@@ -1,18 +1,20 @@
 var resultsDiv;
-var running = false;
 var address;
-var delay = 500;
-var requestField;
-var searchField;
+var delayedSearch = false;
+var currentField;
 var awaitingFreeBusyRequests = new Array();
 var additionalDays = 2;
 
 var dayStartHour = 8;
 var dayEndHour = 18;
 
-var attendeesNames;
-var attendeesUIDs;
-var attendeesEmails;
+var attendeesEditor = {
+ delay: 500,
+ names: null,
+ UIDs: null,
+ emails: null,
+ states: null
+};
 
 function onContactKeydown(event) {
   if (event.ctrlKey || event.metaKey) {
@@ -23,11 +25,10 @@ function onContactKeydown(event) {
     preventDefault(event);
     if (this.confirmedValue)
       this.value = this.confirmedValue;
-    var row = this.parentNode.parentNode.nextSibling;
-    while (row && row.tagName != 'TR')
-      row = row.nextSibling;
-    this.blur();
-    var input = $(row.cells[0]).childNodesWithTag("input")[0];
+    this.hasfreebusy = false;
+    var row = $(this).up("tr").next();
+    this.blur(); // triggers checkAttendee function call
+    var input = row.down("input");
     if (input.readOnly)
       newAttendee(null);
     else {
@@ -35,53 +36,51 @@ function onContactKeydown(event) {
       input.activate();
     }
   }
-  else if (!running) {
-    if (event.keyCode == 0
+  else if (event.keyCode == 0
        || event.keyCode == 8 // Backspace
         || event.keyCode == 32  // Space
         || event.keyCode > 47) {
-      running = true;
-      requestField = this;
-      requestField.setAttribute("modified", "1");
-      if (searchField) {
-       searchField.confirmedValue = null;
-       searchField.uid = null;
+      this.setAttribute("modified", "1");
+      this.confirmedValue = null;
+      this.uid = null;
+      this.hasfreebusy = false;
+      currentField = this;
+      if (this.value.length > 0 && !delayedSearch) {
+       delayedSearch = true;
+       setTimeout("performSearch()", attendeesEditor.delay);
       }
-      setTimeout("triggerRequest()", delay);
-    }
-    else if (this.confirmedValue) {
-      if (event.keyCode == 13) { // Enter
-       $(this).setCaretTo(this.value.length);
-      }
-    }
   }
+  else if (this.confirmedValue)
+    if (event.keyCode == 13) // Enter
+      $(this).setCaretTo(this.value.length);
 }
 
-function triggerRequest() {
-  if (requestField) {
+function performSearch() {
+  if (currentField) {
     if (document.contactLookupAjaxRequest) {
-      document.contactLookupAjaxRequest.aborted = yes;
+      // Abort any pending request
+      document.contactLookupAjaxRequest.aborted = true;
       document.contactLookupAjaxRequest.abort();
     }
-    var urlstr = ( UserFolderURL + "Contacts/contactSearch?search="
-                  + escape(requestField.value) );
-    document.contactLookupAjaxRequest = triggerAjaxRequest(urlstr,
-                                                          updateResults,
-                                                          requestField);
+    if (currentField.value.trim().length > 0) {
+      var urlstr = ( UserFolderURL + "Contacts/contactSearch?search="
+                    + escape(currentField.value) ); log (urlstr);
+      document.contactLookupAjaxRequest =
+       triggerAjaxRequest(urlstr, performSearchCallback, currentField);
+    }
   }
+  delayedSearch = false;
 }
 
-function updateResults(http) {
+function performSearchCallback(http) {
   if (http.readyState == 4) {
     var menu = $('attendeesMenu');
     var list = menu.down("ul");
-
-    searchField = http.callbackData; // requestField
-    searchField.hasfreebusy = false;
-    searchField.setAttribute("uid", null);
+    
+    var input = http.callbackData;
 
     if (http.status == 200) {
-      var start = searchField.value.length;
+      var start = input.value.length;
       var data = http.responseText.evalJSON(true);
       if (data.length > 1) {
        $(list.childNodesWithTag("li")).each(function(item) {
@@ -94,7 +93,7 @@ function updateResults(http) {
          var completeEmail = contact["name"] + " <" + contact["email"] + ">";
          var node = document.createElement("li");
          list.appendChild(node);
-         node.setAttribute("uid", contact["uid"]);
+         node.uid = contact["uid"];
          node.appendChild(document.createTextNode(completeEmail));
          $(node).observe("mousedown", onAttendeeResultClick);
        }
@@ -102,9 +101,9 @@ function updateResults(http) {
        // Show popup menu
        var offset;
        if (isSafari())
-         offset = Position.positionedOffset(searchField);
+         offset = Position.positionedOffset(currentField);
        else
-         offset = Position.cumulativeOffset(searchField);
+         offset = Position.cumulativeOffset(currentField);
        var top = offset[1] + node.offsetHeight + 3;
        var height = 'auto';
        if (data.length > 5) {
@@ -113,7 +112,7 @@ function updateResults(http) {
        menu.setStyle({ top: top + "px",
              left: offset[0] + "px",
              height: height,
-             visibility: "visible"  });
+             visibility: "visible" });
        menu.scrollTop = 0;
 
        document.currentPopupMenu = menu;
@@ -124,40 +123,40 @@ function updateResults(http) {
          hideMenu(document.currentPopupMenu);
 
        if (data.length == 1) {
+         // Single result
          var contact = data[0];
          if (contact["uid"].length > 0)
-           searchField.setAttribute("uid", contact["uid"]);
+           input.uid = contact["uid"];
          var completeEmail = contact["name"] + " <" + contact["email"] + ">";
-         if (contact["name"].substring(0, searchField.value.length).toUpperCase()
-             == searchField.value.toUpperCase())
-           searchField.value = completeEmail;
-         else {
-           searchField.value += ' >> ' + completeEmail;
-         }
-         searchField.confirmedValue = completeEmail;
-         if (searchField.focussed) {
-           var end = searchField.value.length;
-           $(searchField).selectText(start, end);
+         if (contact["name"].substring(0, input.value.length).toUpperCase()
+             == input.value.toUpperCase())
+           input.value = completeEmail;
+         else
+           // The result matches email address, not user name
+           input.value += ' >> ' + completeEmail;
+         input.confirmedValue = completeEmail;
+         if (input.focussed) {
+           var end = input.value.length;
+           $(input).selectText(start, end);
          }
          else
-           searchField.value = contact["name"];
+           input.value = contact["name"];
        }
       }
     }
     else
       if (document.currentPopupMenu)
        hideMenu(document.currentPopupMenu);
-    running = false;
     document.contactLookupAjaxRequest = null;
   }
 }
 
 function onAttendeeResultClick(event) {
-  if (searchField) {
-    searchField.setAttribute("uid", this.getAttribute("uid"));
-    searchField.value = this.firstChild.nodeValue.trim();
-    searchField.confirmedValue = searchField.value;
-    searchField.blur(); // triggers checkAttendee function call
+  if (currentField) {
+    currentField.uid = this.uid;
+    currentField.value = this.firstChild.nodeValue.trim();
+    currentField.confirmedValue = currentField.value;
+    currentField.blur(); // triggers checkAttendee function call
   }
 }
 
@@ -243,12 +242,11 @@ function newAttendee(event) {
    var newRow = model.cloneNode(true);
    tbody.insertBefore(newRow, newAttendeeRow);
   
-   $(newRow).className = "";
-  
-   var input = $(newRow.cells[0]).childNodesWithTag("input")[0];
-   input.setAttribute("autocomplete", "off");
-   Event.observe(input, "keydown", onContactKeydown.bindAsEventListener(input));
-   Event.observe(input, "blur", checkAttendee.bindAsEventListener(input));
+   $(newRow).removeClassName("attendeeModel");
+   var input = $(newRow).down("input");
+   input.observe("keydown", onContactKeydown);
+   input.observe("blur", checkAttendee);
 
    input.focussed = true;
    input.activate();
@@ -265,26 +263,33 @@ function checkAttendee() {
     if (visible)
       return;
   }
-
+  
   this.focussed = false;
   var row = this.parentNode.parentNode;
   var tbody = row.parentNode;
   if (tbody && this.value.trim().length == 0)
     tbody.removeChild(row);
-  else if (!this.hasfreebusy) {
-    if (this.confirmedValue)
-      this.value = this.confirmedValue;
-    displayFreeBusyForNode(this);
-    this.hasfreebusy = true;
+  else if (this.readAttribute("modified") == "1") {
+    if (!$(row).hasClassName("needs-action")) {
+      $(row).addClassName("needs-action");
+      $(row).removeClassName("declined");
+      $(row).removeClassName("accepted");    
+    }
+    if (!this.hasfreebusy) {
+      if (this.uid && this.confirmedValue)
+       this.value = this.confirmedValue;
+      displayFreeBusyForNode(this);
+      this.hasfreebusy = true;
+    }
+    this.setAttribute("modified", "0");
   }
-
-  requestField = null;
-  searchField = null;
+  
+  currentField = null;
 }
 
 function displayFreeBusyForNode(node) {
   var nodes = node.parentNode.parentNode.cells;
-  if (node.getAttribute("uid")) {
+  if (node.uid) {
     if (document.contactFreeBusyAjaxRequest)
       awaitingFreeBusyRequests.push(node);
     else {
@@ -296,17 +301,18 @@ function displayFreeBusyForNode(node) {
                                 + '<span class="freeBusyZoneElement"></span>');
       }
       if (document.contactFreeBusyAjaxRequest) {
+       // Abort any pending request
        document.contactFreeBusyAjaxRequest.aborted = true;
        document.contactFreeBusyAjaxRequest.abort();
       }
       var sd = $('startTime_date').valueAsShortDateString();
       var ed = $('endTime_date').valueAsShortDateString();
-      var urlstr = ( UserFolderURL + "../" + node.getAttribute("uid") + "/freebusy.ifb/ajaxRead?"
+      var urlstr = ( UserFolderURL + "../" + node.uid + "/freebusy.ifb/ajaxRead?"
                     + "sday=" + sd + "&eday=" + ed + "&additional=" +
                     additionalDays );
       document.contactFreeBusyAjaxRequest
        = triggerAjaxRequest(urlstr,
-                            updateFreeBusyData,
+                            updateFreeBusyDataCallback,
                             node);
     }
   } else {
@@ -336,7 +342,7 @@ function setSlot(tds, nbr, status) {
   }
 }
 
-function updateFreeBusyData(http) {
+function updateFreeBusyDataCallback(http) {
   if (http.readyState == 4) {
     if (http.status == 200) {
       var node = http.callbackData;
@@ -385,33 +391,42 @@ function initializeWindowButtons() {
 function onEditorOkClick(event) {
    preventDefault(event);
    
-   attendeesNames = new Array();
-   attendeesUIDs = new Array();
-   attendeesEmails = new Array();
-   
+   attendeesEditor.names = new Array();
+   attendeesEditor.UIDs = new Array();
+   attendeesEditor.emails = new Array();
+   attendeesEditor.states = new Array();
+
    var table = $("freeBusy");
    var inputs = table.getElementsByTagName("input");
    for (var i = 0; i < inputs.length - 2; i++) {
+     var row = $(inputs[i]).up("tr");
      var name = extractEmailName(inputs[i].value);
      var email = extractEmailAddress(inputs[i].value);
      var uid = "";
-     if (inputs[i].getAttribute("uid"))
-       uid = inputs[i].getAttribute("uid");
+     if (inputs[i].uid)
+       uid = inputs[i].uid;
      if (!(name && name.length > 0))
        if (inputs[i].uid)
         name = inputs[i].uid;
        else
         name = email;
-     var pos = attendeesEmails.indexOf(email);
+     var state = "needs-action";
+     if (row.hasClassName("accepted"))
+       state = "accepted";
+     else if (row.hasClassName("declined"))
+       state = "declined";
+     var pos = attendeesEditor.emails.indexOf(email);
      if (pos == -1)
-       pos = attendeesEmails.length;
-     attendeesNames[pos] = name;
-     attendeesUIDs[pos] = uid;
-     attendeesEmails[pos] = email;
+       pos = attendeesEditor.emails.length;
+     attendeesEditor.names[pos] = name;
+     attendeesEditor.UIDs[pos] = uid;
+     attendeesEditor.emails[pos] = email;
+     attendeesEditor.states[pos] = state;
    }
-   parent$("attendeesNames").value = attendeesNames.join(",");
-   parent$("attendeesUIDs").value = attendeesUIDs.join(",");
-   parent$("attendeesEmails").value = attendeesEmails.join(",");
+   parent$("attendeesNames").value = attendeesEditor.names.join(",");
+   parent$("attendeesUIDs").value = attendeesEditor.UIDs.join(",");
+   parent$("attendeesEmails").value = attendeesEditor.emails.join(",");
+   parent$("attendeesStates").value = attendeesEditor.states.join(",");
    window.opener.refreshAttendees();
 
    updateParentDateFields("startTime", "startTime");
@@ -547,25 +562,27 @@ function prepareAttendees() {
    var value = parent$("attendeesNames").value;
    var table = $("freeBusy");
    if (value.length > 0) {
-      attendeesNames = parent$("attendeesNames").value.split(",");
-      attendeesUIDs = parent$("attendeesUIDs").value.split(",");
-      attendeesEmails = parent$("attendeesEmails").value.split(",");
+      attendeesEditor.names = parent$("attendeesNames").value.split(",");
+      attendeesEditor.UIDs = parent$("attendeesUIDs").value.split(",");
+      attendeesEditor.emails = parent$("attendeesEmails").value.split(",");
+      attendeesEditor.states = parent$("attendeesStates").value.split(",");
 
       var tbody = table.tBodies[0];
       var model = tbody.rows[tbody.rows.length - 1];
       var newAttendeeRow = tbody.rows[tbody.rows.length - 2];
-      for (var i = 0; i < attendeesNames.length; i++) {
+      for (var i = 0; i < attendeesEditor.names.length; i++) {
         var row = model.cloneNode(true);
         tbody.insertBefore(row, newAttendeeRow);
         $(row).removeClassName("attendeeModel");
+        $(row).addClassName(attendeesEditor.states[i]);
         var input = $(row).down("input");
         var value = "";
-        if (attendeesNames[i].length > 0 && attendeesNames[i] != attendeesEmails[i])
-           value += attendeesNames[i] + " ";
-        value += "<" + attendeesEmails[i] + ">";
+        if (attendeesEditor.names[i].length > 0 && attendeesEditor.names[i] != attendeesEditor.emails[i])
+           value += attendeesEditor.names[i] + " ";
+        value += "<" + attendeesEditor.emails[i] + ">";
         input.value = value;
-        if (attendeesUIDs[i].length > 0)
-          input.setAttribute("uid", attendeesUIDs[i]);
+        if (attendeesEditor.UIDs[i].length > 0)
+          input.uid = attendeesEditor.UIDs[i];
         input.setAttribute("name", "");
         input.setAttribute("modified", "0");
         input.observe("blur", checkAttendee);
@@ -574,9 +591,9 @@ function prepareAttendees() {
       }
    }
    else {
-      attendeesNames = new Array();
-      attendeesUIDs = new Array();
-      attendeesEmails = new Array();
+      attendeesEditor.names = new Array();
+      attendeesEditor.UIDs = new Array();
+      attendeesEditor.emails = new Array();
    }
 
    var inputs = table.getElementsByTagName("input");
index 0925b03e6448c78d75a4ba915fb30011bd203c9b..e1eb173d9f479b07e4080fd0cd6517d22254a896 100644 (file)
@@ -1,5 +1,7 @@
 var contactSelectorAction = 'mailer-contacts';
 var signatureLength = 0;
+
+var attachmentCount = 0;
 var MailEditor = {
  addressBook: null
 };
@@ -189,12 +191,13 @@ function clickedEditorAttach(sender) {
       onWindowResize(null);
     }
     var inputs = area.getElementsByTagName("input");
-    var attachmentName = "attachment" + inputs.length;
+    var attachmentName = "attachment" + attachmentCount;
     var newAttachment = createElement("input", attachmentName,
                                      "currentAttachment", null,
                                      { type: "file",
                                        name: attachmentName },
                                      area);
+    attachmentCount++;
     Event.observe(newAttachment, "change",
                  onAttachmentChange.bindAsEventListener(newAttachment));
   }
index b9ebf1373de87fecf5c8115d3b3bc6544f23b9db..80702119ce9de9e8d4a18d35c74bf6f6257a921e 100644 (file)
@@ -93,4 +93,10 @@ TABLE#freeBusy TD.attendees
 
 TABLE#freeBusy TD.attendees INPUT
 { border-bottom: 1px solid #ccc;
-  border-right: 1px solid #ccc; }
\ No newline at end of file
+  border-right: 1px solid #ccc; }
+
+TABLE#freeBusy TR.needs-action INPUT,
+TABLE#freeBusy TR.accepted INPUT,
+TABLE#freeBusy TR.declined INPUT
+{ margin-left: 2.0em;
+  width: 10.0em; }