From e4db286736d7e7c82c82904db67fca92ec97e314 Mon Sep 17 00:00:00 2001 From: wolfgang Date: Thu, 13 Dec 2007 00:02:21 +0000 Subject: [PATCH] git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1299 d1b88da0-ebda-0310-925b-ed51d893ca5b --- ChangeLog | 31 +++ SoObjects/Mailer/SOGoMailObject.h | 2 + SoObjects/Mailer/SOGoMailObject.m | 67 +++++ SoObjects/SOGo/NSString+Utilities.m | 22 +- UI/MailPartViewers/UIxMailPartHTMLViewer.m | 76 +----- UI/MailPartViewers/UIxMailPartTextViewer.m | 3 +- UI/MailerUI/German.lproj/Localizable.strings | 2 +- UI/Scheduler/UIxCalListingActions.m | 40 ++- UI/Scheduler/UIxComponentEditor.h | 1 + UI/Scheduler/UIxComponentEditor.m | 17 +- .../SchedulerUI/UIxComponentEditor.wox | 2 + UI/WebServerResources/SchedulerUI.css | 20 ++ UI/WebServerResources/SchedulerUI.js | 2 + UI/WebServerResources/UIxAttendeesEditor.css | 24 +- UI/WebServerResources/UIxAttendeesEditor.js | 233 ++++++++++-------- UI/WebServerResources/UIxMailEditor.js | 5 +- UI/WebServerResources/iefixes.css | 8 +- 17 files changed, 369 insertions(+), 186 deletions(-) diff --git a/ChangeLog b/ChangeLog index ab8ee20d..c0cf8e7b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,34 @@ +2007-12-12 Wolfgang Sourdeau + + * 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 + + * 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 * UI/MailPartViewers/UIxMailRenderingContext.{h,m} diff --git a/SoObjects/Mailer/SOGoMailObject.h b/SoObjects/Mailer/SOGoMailObject.h index fa0b5458..e4f873bf 100644 --- a/SoObjects/Mailer/SOGoMailObject.h +++ b/SoObjects/Mailer/SOGoMailObject.h @@ -89,6 +89,8 @@ - (NSDictionary *) fetchPlainTextParts; - (NSDictionary *) fetchPlainTextStrings:(NSArray *)_fetchKeys; +- (NSDictionary *) fetchAttachmentIds; + /* flags */ - (NSException *) addFlags:(id)_f; diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index a28d752e..6c38725f 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -24,6 +24,7 @@ #import #import #import +#import #import #import @@ -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 diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index 1192ccd7..ff0c38d0 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -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]) diff --git a/UI/MailPartViewers/UIxMailPartHTMLViewer.m b/UI/MailPartViewers/UIxMailPartHTMLViewer.m index 6644e30f..94c7055d 100644 --- a/UI/MailPartViewers/UIxMailPartHTMLViewer.m +++ b/UI/MailPartViewers/UIxMailPartHTMLViewer.m @@ -35,6 +35,8 @@ #include +#import + #import "UIxMailPartHTMLViewer.h" #if 0 @@ -207,7 +209,7 @@ attributes: (id ) _attributes { unsigned int count, max; - NSString *name, *value; + NSString *name, *value, *cid; NSMutableString *resultPart; BOOL skipAttribute; @@ -237,8 +239,9 @@ 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 @@ -433,68 +436,6 @@ [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[] = { @@ -557,13 +498,16 @@ { NSObject *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]; diff --git a/UI/MailPartViewers/UIxMailPartTextViewer.m b/UI/MailPartViewers/UIxMailPartTextViewer.m index 27ecea50..e84ce944 100644 --- a/UI/MailPartViewers/UIxMailPartTextViewer.m +++ b/UI/MailPartViewers/UIxMailPartTextViewer.m @@ -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 */ diff --git a/UI/MailerUI/German.lproj/Localizable.strings b/UI/MailerUI/German.lproj/Localizable.strings index b0b8dcef..b89dc7ed 100644 --- a/UI/MailerUI/German.lproj/Localizable.strings +++ b/UI/MailerUI/German.lproj/Localizable.strings @@ -132,7 +132,7 @@ "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"; diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 5e8905a3..e41f23e0 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -33,6 +33,7 @@ #import #import #import +#import #import #import @@ -308,21 +309,23 @@ - (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) { @@ -340,6 +343,35 @@ 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]; diff --git a/UI/Scheduler/UIxComponentEditor.h b/UI/Scheduler/UIxComponentEditor.h index 34b2688d..e6172e00 100644 --- a/UI/Scheduler/UIxComponentEditor.h +++ b/UI/Scheduler/UIxComponentEditor.h @@ -63,6 +63,7 @@ NSString *attendeesNames; NSString *attendeesUIDs; NSString *attendeesEmails; + NSString *attendeesStates; } - (NSString *) toolbar; diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index 7384965f..4e61724d 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -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]; @@ -107,13 +109,14 @@ { 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]; @@ -127,6 +130,7 @@ [uids appendFormat: @"%@,", uid]; else [uids appendString: @","]; + [states appendFormat: @"%@,", [[currentAttendee partStat] lowercaseString]]; currentAttendee = [attendees nextObject]; } @@ -136,6 +140,7 @@ ASSIGN (attendeesUIDs, [uids substringToIndex: [uids length] - 1]); ASSIGN (attendeesEmails, [emails substringToIndex: [emails length] - 1]); + ASSIGN (attendeesStates, [states substringToIndex: [states length] - 1]); } [names release]; @@ -358,6 +363,16 @@ return attendeesEmails; } +- (void) setAttendeesStates: (NSString *) newAttendeesStates +{ + ASSIGN (attendeesStates, newAttendeesStates); +} + +- (NSString *) attendeesStates +{ + return attendeesStates; +} + - (void) setLocation: (NSString *) _value { ASSIGN (location, _value); diff --git a/UI/Templates/SchedulerUI/UIxComponentEditor.wox b/UI/Templates/SchedulerUI/UIxComponentEditor.wox index 84b1d032..044a1d0e 100644 --- a/UI/Templates/SchedulerUI/UIxComponentEditor.wox +++ b/UI/Templates/SchedulerUI/UIxComponentEditor.wox @@ -91,6 +91,8 @@ var:value="attendeesUIDs"/> + diff --git a/UI/WebServerResources/SchedulerUI.css b/UI/WebServerResources/SchedulerUI.css index 98a683c8..6f4c9f72 100644 --- a/UI/WebServerResources/SchedulerUI.css +++ b/UI/WebServerResources/SchedulerUI.css @@ -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%; } diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 8dd803d1..273a21e5 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -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; diff --git a/UI/WebServerResources/UIxAttendeesEditor.css b/UI/WebServerResources/UIxAttendeesEditor.css index 808d0012..f1868282 100644 --- a/UI/WebServerResources/UIxAttendeesEditor.css +++ b/UI/WebServerResources/UIxAttendeesEditor.css @@ -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; } diff --git a/UI/WebServerResources/UIxAttendeesEditor.js b/UI/WebServerResources/UIxAttendeesEditor.js index 7c242a3c..14aae456 100644 --- a/UI/WebServerResources/UIxAttendeesEditor.js +++ b/UI/WebServerResources/UIxAttendeesEditor.js @@ -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) { + ''); } 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"); diff --git a/UI/WebServerResources/UIxMailEditor.js b/UI/WebServerResources/UIxMailEditor.js index 0925b03e..e1eb173d 100644 --- a/UI/WebServerResources/UIxMailEditor.js +++ b/UI/WebServerResources/UIxMailEditor.js @@ -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)); } diff --git a/UI/WebServerResources/iefixes.css b/UI/WebServerResources/iefixes.css index b9ebf137..80702119 100644 --- a/UI/WebServerResources/iefixes.css +++ b/UI/WebServerResources/iefixes.css @@ -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; } -- 2.39.5