merge of '29dc700a0a33827e16bd46a8d97f89d5f11630e9'

and 'b539ed81e839a831ce07b7618690feb8ac799176'

Monotone-Parent: 29dc700a0a33827e16bd46a8d97f89d5f11630e9
Monotone-Parent: b539ed81e839a831ce07b7618690feb8ac799176
Monotone-Revision: a322b26bc82b5d04dfdd3997d676eec971639aec

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2010-05-05T18:52:51
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Wolfgang Sourdeau 2010-05-05 18:52:51 +00:00
commit cb8c1f6522
61 changed files with 1150 additions and 749 deletions

View file

@ -4,6 +4,86 @@
refresh the tasks list when receiving the AJAX response instead of refresh the tasks list when receiving the AJAX response instead of
emptying the list before sending the AJAX request. emptying the list before sending the AJAX request.
2010-05-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/WebServerResources/UIxAttendeesEditor.js (onContactKeydown):
the code for the "enter" and "tab" has been merged, since it was
similar. We also resolve individual contacts from list entries.
(resolveListAttendees): new function that fetches the indivual
entries from contacts lists.
(resolveListAttendeesCallback): callback for the above, that
create rows and input fields corresponding to the returned
entries, and trigger a contact search on each of them.
(performSearch): take an "input" field as argument since
"attendeesEditor.currentField" is no longer used.
(performSearchCallbacks): roles and partstats are now stored in
individual attributes rather than in the element class. We also
now behave differently depending on whether the event owner is
returned or another type of user, in order to match Lightning's
behaviour.
(newAttendee): no longer a callback, and now takes an optional
argument that indicates with attendee row precedes the one being
created. Also the new row is returned.
(checkAttendee): no longer a callback. Now takes an "input"
argument.
(onInputBlur): new callback that invokes "checkAttendee" and
handle the cleanup of the entries menu.
(displayFreeBusyForNode): use DOM methods rather than "innerHTML"
to modify the cell contents.
(updateFreeBusyDataCallback): freebusy requests are no longer
sequential.
(prepareAttendees): we now modify the DOM attributes rather than
the HTML attributes.
* UI/Contacts/UIxListView.m (-propertiesAction): we now invoke
-[self responseWithStatus:andJSONRepresentation:] to build the
resulting WOResponse.
* UI/Contacts/UIxContactFoldersView.m (-allContactSearchAction):
the type of returned component can now be deduced from the value
of "c_component" rather than from the c_name extension... Also, we
avoid autoreleasing variables where it's not needed.
(-contactSearchAction): removed obsolete method.
* SoObjects/Contacts/SOGoContactGCSFolder.m (+initialize): new
method initializing "folderListingFields" as a static NSArray
rather than as macro. Added "c_component" to the list of fields to
retrieve.
* UI/Scheduler/UIxComponentEditor.m (-ownerLogin): new accessor
the differenciate between the type of users in the list of
attendees.
* UI/WebServerResources/UIxAttendeesEditor.js
(performSearchCallback): differenciate between the organizer user
and other attendees, as in Lightning.
* UI/WebServerResources/UIxAppointmentEditor.js
(setupAttendeeNode): removed useless calls to $().
* UI/Scheduler/UIxComponentEditor.m
(-ownerIsAttendee:andClientObject:)
(delegateIsAttendee:andClientObject:): handle the case where the
user is found but has no RSVP or one with "FALSE" as value.
(-userHasRSVP): renamed from "userIsAttendee", since we don't
enable users without a RSVP set as "TRUE" to repond to
invitations.
(-currentAttendeeClasses): new method.
* UI/MailPartViewers/UIxMailPartICalActions.m (-tentativeAction):
new action method for the "TENTATIVE" partstat.
* SoObjects/Appointments/iCalEntityObject+SOGo.m
(-userIsParticipant:): renamed to "userIsAttendee:". We now
request the list of attendees rather than the list of
participants.
(-userAsParticipant:): renamed to "userAsAttendee:". Again, we
return the attendee matching the user, whether he/she is a
participant or not.
* SoObjects/SOGo/iCalEntityObject+Utilities.[hm]: removed useless
module, since it implemented methods already found elsewhere.
2010-05-04 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2010-05-04 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/WebServerResources/ContactsUI.js (initContacts): we must * UI/WebServerResources/ContactsUI.js (initContacts): we must

View file

@ -1,3 +1,16 @@
2010-05-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* iCalPerson.m (-setParticipationStatus:, -participationStatus):
added handling of the new partstat value
"iCalPersonPartStatUndefined".
* iCalEntityObject.m (-resources): removed useless method.
(-nonParticipants): new method returning ATTENDEE objects having
their ROLE attribute set to "NON-PARTICIPANT".
(-isAttendee:): new method.
(-findAttendeeWithEmail): new method replacing
"findParticipantWithEmail:".
2010-04-28 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2010-04-28 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* iCalRecurrenceRule.m (-iCalRepresentationForWeekDay:): made * iCalRecurrenceRule.m (-iCalRepresentationForWeekDay:): made
@ -34,7 +47,7 @@
* iCalWeeklyRecurrenceCalculator.m * iCalWeeklyRecurrenceCalculator.m
(-recurrenceRangesWithinCalendarDateRange:): make use of the new (-recurrenceRangesWithinCalendarDateRange:): make use of the new
iCalByDayMask class. iCalByDayMask class.
(-lastInstanceStartDate): make use of the previous method when a (-lastInstanceStartDate): make use of the previous method when a
BYxxx mask is defined in the rule. BYxxx mask is defined in the rule.

View file

@ -109,15 +109,16 @@ typedef enum
- (void) addToAttendees: (iCalPerson *) _person; - (void) addToAttendees: (iCalPerson *) _person;
- (NSArray *) attendees; - (NSArray *) attendees;
- (void) setAttendees: (NSArray *) attendees; - (void) setAttendees: (NSArray *) attendees;
- (BOOL) isAttendee: (id) _email;
- (void) removeFromAttendees: (iCalPerson *) oldAttendee; - (void) removeFromAttendees: (iCalPerson *) oldAttendee;
- (void) removeAllAttendees; - (void) removeAllAttendees;
/* categorize attendees into participants and resources */ /* categorize attendees into participants and non-participants */
- (NSArray *) participants; - (NSArray *) participants;
- (NSArray *) resources; - (NSArray *) nonParticipants;
- (BOOL) isParticipant: (id) _email; - (BOOL) isParticipant: (id) _email;
- (iCalPerson *) findParticipantWithEmail: (id) _email; - (iCalPerson *) findAttendeeWithEmail: (id) email;
- (void) removeAllAlarms; - (void) removeAllAlarms;
- (void) addToAlarms: (id) _alarm; - (void) addToAlarms: (id) _alarm;

View file

@ -330,6 +330,40 @@
return [self childrenWithTag: @"attendee"]; return [self childrenWithTag: @"attendee"];
} }
- (BOOL) isAttendee: (id) _email
{
NSArray *attEmails;
_email = [_email lowercaseString];
attEmails = [[self attendees] valueForKey:@"rfc822Email"];
attEmails = [attEmails valueForKey: @"lowercaseString"];
return [attEmails containsObject:_email];
}
- (iCalPerson *) findAttendeeWithEmail: (id) email
{
NSArray *attendees;
unsigned int count, max;
NSString *lowerEmail, *currentEmail;
iCalPerson *attendee, *currentAttendee;
attendee = nil;
lowerEmail = [email lowercaseString];
attendees = [self attendees];
max = [attendees count];
for (count = 0; attendee == nil && count < max; count++)
{
currentAttendee = [attendees objectAtIndex: count];
currentEmail = [[currentAttendee rfc822Email] lowercaseString];
if ([currentEmail isEqualToString: lowerEmail])
attendee = currentAttendee;
}
return attendee;
}
- (void) removeAllAlarms - (void) removeAllAlarms
{ {
[children removeObjectsInArray: [self alarms]]; [children removeObjectsInArray: [self alarms]];
@ -406,48 +440,6 @@
} }
/* stuff */ /* stuff */
- (NSArray *) participants
{
return [self _filteredAttendeesThinkingOfPersons: YES];
}
- (NSArray *) resources
{
return [self _filteredAttendeesThinkingOfPersons: NO];
}
- (NSArray *) _filteredAttendeesThinkingOfPersons: (BOOL) _persons
{
NSArray *list;
NSMutableArray *filtered;
unsigned count, max;
iCalPerson *person;
NSString *role;
if (_persons)
{
list = [self attendees];
max = [list count];
filtered = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
person = (iCalPerson *) [list objectAtIndex: count];
role = [[person role] uppercaseString];
if (![role hasPrefix: @"NON-PART"])
[filtered addObject: person];
}
list = filtered;
}
else
list = [self childrenWithTag: @"attendee"
andAttribute: @"role"
havingValue: @"non-part"];
return list;
}
- (BOOL) isOrganizer: (id) _email - (BOOL) isOrganizer: (id) _email
{ {
NSString *organizerMail; NSString *organizerMail;
@ -458,6 +450,35 @@
isEqualToString: [_email lowercaseString]]; isEqualToString: [_email lowercaseString]];
} }
- (NSArray *) participants
{
NSArray *list;
NSMutableArray *filtered;
unsigned count, max;
iCalPerson *person;
NSString *role;
list = [self attendees];
max = [list count];
filtered = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
person = [list objectAtIndex: count];
role = [[person role] uppercaseString];
if (![role hasPrefix: @"NON-PARTICIPANT"])
[filtered addObject: person];
}
return filtered;
}
- (NSArray *) nonParticipants
{
return [self childrenWithTag: @"attendee"
andAttribute: @"role"
havingValue: @"non-participant"];
}
- (BOOL) isParticipant: (id) _email - (BOOL) isParticipant: (id) _email
{ {
NSArray *partEmails; NSArray *partEmails;
@ -468,26 +489,6 @@
return [partEmails containsObject:_email]; return [partEmails containsObject:_email];
} }
- (iCalPerson *) findParticipantWithEmail: (id) _email
{
NSArray *ps;
unsigned i, count;
_email = [_email lowercaseString];
ps = [self participants];
count = [ps count];
for (i = 0; i < count; i++) {
iCalPerson *p;
p = [ps objectAtIndex:i];
if ([[[p rfc822Email] lowercaseString] isEqualToString:_email])
return p;
}
return nil; /* not found */
}
- (NSComparisonResult) _compareValue: (id) selfValue - (NSComparisonResult) _compareValue: (id) selfValue
withValue: (id) otherValue withValue: (id) otherValue
{ {

View file

@ -25,6 +25,7 @@
#import "CardElement.h" #import "CardElement.h"
typedef enum { typedef enum {
iCalPersonPartStatUndefined = -1, /* empty/undefined */
iCalPersonPartStatNeedsAction = 0, /* NEEDS-ACTION (DEFAULT) */ iCalPersonPartStatNeedsAction = 0, /* NEEDS-ACTION (DEFAULT) */
iCalPersonPartStatAccepted = 1, /* ACCEPTED */ iCalPersonPartStatAccepted = 1, /* ACCEPTED */
iCalPersonPartStatDeclined = 2, /* DECLINED */ iCalPersonPartStatDeclined = 2, /* DECLINED */

View file

@ -133,6 +133,9 @@
NSString *stat; NSString *stat;
switch (_status) { switch (_status) {
case iCalPersonPartStatUndefined:
stat = @"";
break;
case iCalPersonPartStatAccepted: case iCalPersonPartStatAccepted:
stat = @"ACCEPTED"; stat = @"ACCEPTED";
break; break;
@ -170,7 +173,9 @@
NSString *stat; NSString *stat;
stat = [[self partStat] uppercaseString]; stat = [[self partStat] uppercaseString];
if (![stat length] || [stat isEqualToString:@"NEEDS-ACTION"]) if (![stat length])
return iCalPersonPartStatUndefined;
else if ([stat isEqualToString:@"NEEDS-ACTION"])
return iCalPersonPartStatNeedsAction; return iCalPersonPartStatNeedsAction;
else if ([stat isEqualToString:@"ACCEPTED"]) else if ([stat isEqualToString:@"ACCEPTED"])
return iCalPersonPartStatAccepted; return iCalPersonPartStatAccepted;

View file

@ -1,3 +1,8 @@
2010-05-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* VSSaxDriver.m (_endComponent:value:): avoid a crash occurring
when an inconsistency is found in the stack of containers.
2009-12-22 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2009-12-22 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* VSSaxDriver.m (_endComponent:value:): worked-around the lameness * VSSaxDriver.m (_endComponent:value:): worked-around the lameness

View file

@ -31,6 +31,7 @@
#import "VSSaxDriver.h" #import "VSSaxDriver.h"
#import <SaxObjC/SaxException.h> #import <SaxObjC/SaxException.h>
#import <NGExtensions/NGQuotedPrintableCoding.h> #import <NGExtensions/NGQuotedPrintableCoding.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+Encoding.h> #import <NGExtensions/NSString+Encoding.h>
#import <NGCards/NSString+NGCards.h> #import <NGCards/NSString+NGCards.h>
#import "common.h" #import "common.h"
@ -653,6 +654,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
value: (NSString *) tagValue value: (NSString *) tagValue
{ {
NSString *mtName; NSString *mtName;
int max;
mtName = [[self _mapTagName: tagValue] uppercaseString]; mtName = [[self _mapTagName: tagValue] uppercaseString];
if ([cardStack count] > 0) if ([cardStack count] > 0)
@ -692,11 +694,19 @@ static NSCharacterSet *whitespaceCharSet = nil;
stringByAppendingString: mtName]]; stringByAppendingString: mtName]];
} }
[self _endGroupElementTag: mtName]; [self _endGroupElementTag: mtName];
[cardStack removeLastObject];
max = [cardStack count];
if (max > 0)
{
[cardStack removeLastObject];
max--;
}
else
[self errorWithFormat: @"serious inconsistency among begin/end tags"];
/* report parsed elements */ /* report parsed elements */
if ([cardStack count] == 0) if (max == 0)
[self reportQueuedTags]; [self reportQueuedTags];
} }

View file

@ -39,7 +39,6 @@
#import <SOPE/NGCards/NSString+NGCards.h> #import <SOPE/NGCards/NSString+NGCards.h>
#import <SOGo/iCalEntityObject+Utilities.h>
#import <SOGo/SOGoUserManager.h> #import <SOGo/SOGoUserManager.h>
#import <SOGo/NSArray+Utilities.h> #import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSObject+DAV.h> #import <SOGo/NSObject+DAV.h>
@ -591,13 +590,13 @@
SOGoUser *currentUser; SOGoUser *currentUser;
currentUser = [context activeUser]; currentUser = [context activeUser];
otherAttendee = [event findParticipant: theOwnerUser]; otherAttendee = [event userAsAttendee: theOwnerUser];
delegateEmail = [otherAttendee delegatedTo]; delegateEmail = [otherAttendee delegatedTo];
if ([delegateEmail length]) if ([delegateEmail length])
delegateEmail = [delegateEmail rfc822Email]; delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length]) if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail]; otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else else
otherDelegate = NO; otherDelegate = NO;
@ -635,7 +634,7 @@
delegateEmail = [delegateEmail rfc822Email]; delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length]) if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail]; otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else else
otherDelegate = NO; otherDelegate = NO;
} }
@ -708,7 +707,7 @@
delegateEmail = [delegateEmail rfc822Email]; delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length]) if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail]; otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else else
otherDelegate = NO; otherDelegate = NO;
@ -792,7 +791,7 @@
delegateEmail = [delegateEmail rfc822Email]; delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length]) if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail]; otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else else
otherDelegate = NO; otherDelegate = NO;
} }
@ -860,7 +859,7 @@
shouldAddSentBy: YES]; shouldAddSentBy: YES];
} }
// We update the calendar of all participants that are // We update the calendar of all attendees that are
// local to the system. This is useful in case user A accepts // local to the system. This is useful in case user A accepts
// invitation from organizer B and users C, D, E who are also // invitation from organizer B and users C, D, E who are also
// attendees need to verify if A has accepted. // attendees need to verify if A has accepted.
@ -1075,7 +1074,7 @@
[self sendReceiptEmailUsingTemplateNamed: (isUpdate [self sendReceiptEmailUsingTemplateNamed: (isUpdate
? @"Update" : @"Invitation") ? @"Update" : @"Invitation")
forObject: emailEvent forObject: emailEvent
to: [newEvent participants]]; to: [newEvent attendees]];
return elements; return elements;
} }
@ -1120,7 +1119,7 @@
[self sendReceiptEmailUsingTemplateNamed: @"Deletion" [self sendReceiptEmailUsingTemplateNamed: @"Deletion"
forObject: event forObject: event
to: [event participants]]; to: [event attendees]];
return elements; return elements;
} }
@ -1161,7 +1160,7 @@
} }
// Find attendee within event // Find attendee within event
localAttendee = [event findParticipantWithEmail: [attendee rfc822Email]]; localAttendee = [event findAttendeeWithEmail: [attendee rfc822Email]];
if (localAttendee) if (localAttendee)
{ {
// Update the attendee's status // Update the attendee's status
@ -1230,7 +1229,7 @@
[[event parent] setMethod: @""]; [[event parent] setMethod: @""];
ownerUser = [SOGoUser userWithLogin: [[SOGoUserManager sharedUserManager] ownerUser = [SOGoUser userWithLogin: [[SOGoUserManager sharedUserManager]
getUIDForEmail: originator]]; getUIDForEmail: originator]];
attendee = [event findParticipant: ownerUser]; attendee = [event userAsAttendee: ownerUser];
eventUID = [event uid]; eventUID = [event uid];
delegate = nil; delegate = nil;
@ -1240,7 +1239,7 @@
delegateEmail = [delegateEmail substringFromIndex: 7]; delegateEmail = [delegateEmail substringFromIndex: 7];
if ([delegateEmail length]) if ([delegateEmail length])
delegate delegate
= [event findParticipantWithEmail: delegateEmail]; = [event findAttendeeWithEmail: delegateEmail];
} }
recipientsEnum = [recipients objectEnumerator]; recipientsEnum = [recipients objectEnumerator];
@ -1326,7 +1325,7 @@
// change will be on the attendee corresponding to the ownerUser. // change will be on the attendee corresponding to the ownerUser.
ownerUser = [SOGoUser userWithLogin: owner]; ownerUser = [SOGoUser userWithLogin: owner];
attendee = [event findParticipant: ownerUser]; attendee = [event userAsAttendee: ownerUser];
if (attendee) if (attendee)
{ {
if (delegate if (delegate
@ -1338,9 +1337,9 @@
if (delegatedUser != nil && [event userIsOrganizer: delegatedUser]) if (delegatedUser != nil && [event userIsOrganizer: delegatedUser])
ex = [NSException exceptionWithHTTPStatus: 403 ex = [NSException exceptionWithHTTPStatus: 403
reason: @"delegate is organizer"]; reason: @"delegate is organizer"];
if ([event isParticipant: [[delegate email] rfc822Email]]) if ([event isAttendee: [[delegate email] rfc822Email]])
ex = [NSException exceptionWithHTTPStatus: 403 ex = [NSException exceptionWithHTTPStatus: 403
reason: @"delegate is a participant"]; reason: @"delegate is a attendee"];
else if ([SOGoGroup groupWithEmail: [[delegate email] rfc822Email] else if ([SOGoGroup groupWithEmail: [[delegate email] rfc822Email]
inDomain: [ownerUser domain]]) inDomain: [ownerUser domain]])
ex = [NSException exceptionWithHTTPStatus: 403 ex = [NSException exceptionWithHTTPStatus: 403
@ -1411,7 +1410,7 @@
to: attendees]; to: attendees];
} }
} }
else if ([occurence userIsParticipant: ownerUser]) else if ([occurence userIsAttendee: ownerUser])
// The current user deletes the occurence; let the organizer know that // The current user deletes the occurence; let the organizer know that
// the user has declined this occurence. // the user has declined this occurence.
[self changeParticipationStatus: @"DECLINED" withDelegate: nil [self changeParticipationStatus: @"DECLINED" withDelegate: nil
@ -1439,7 +1438,7 @@
NSArray *allEvents; NSArray *allEvents;
int count, max; int count, max;
iCalEvent *currentEvent; iCalEvent *currentEvent;
iCalPerson *ownerParticipant; iCalPerson *ownerAttendee;
NSString *key; NSString *key;
SOGoUser *ownerUser; SOGoUser *ownerUser;
@ -1452,14 +1451,14 @@
for (count = 0; count < max; count++) for (count = 0; count < max; count++)
{ {
currentEvent = [allEvents objectAtIndex: count]; currentEvent = [allEvents objectAtIndex: count];
ownerParticipant = [currentEvent userAsParticipant: ownerUser]; ownerAttendee = [currentEvent userAsAttendee: ownerUser];
if (ownerParticipant) if (ownerAttendee)
{ {
if (count == 0) if (count == 0)
key = @"master"; key = @"master";
else else
key = [[currentEvent recurrenceId] iCalFormattedDateTimeString]; key = [[currentEvent recurrenceId] iCalFormattedDateTimeString];
[partStats setObject: ownerParticipant forKey: key]; [partStats setObject: ownerAttendee forKey: key];
} }
} }

View file

@ -41,7 +41,6 @@
#import <NGMime/NGMimeMultipartBody.h> #import <NGMime/NGMimeMultipartBody.h>
#import <NGMail/NGMimeMessage.h> #import <NGMail/NGMimeMessage.h>
#import <SOGo/iCalEntityObject+Utilities.h>
#import <SOGo/NSCalendarDate+SOGo.h> #import <SOGo/NSCalendarDate+SOGo.h>
#import <SOGo/NSDictionary+Utilities.h> #import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+DAV.h> #import <SOGo/NSObject+DAV.h>
@ -825,7 +824,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
p = [app pageWithName: pageName inContext: context]; p = [app pageWithName: pageName inContext: context];
[p setApt: (iCalEvent *) event]; [p setApt: (iCalEvent *) event];
attendee = [event findParticipant: from]; attendee = [event userAsAttendee: from];
[p setAttendee: attendee]; [p setAttendee: attendee];
/* construct message */ /* construct message */
@ -897,7 +896,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
if (![event userIsOrganizer: ownerUser]) if (![event userIsOrganizer: ownerUser])
{ {
organizer = [event organizer]; organizer = [event organizer];
attendee = [event findParticipant: ownerUser]; attendee = [event userAsAttendee: ownerUser];
[event setAttendees: [NSArray arrayWithObject: attendee]]; [event setAttendees: [NSArray arrayWithObject: attendee]];
[self sendIMIPReplyForEvent: event from: from to: organizer]; [self sendIMIPReplyForEvent: event from: from to: organizer];
} }
@ -981,7 +980,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
user = [SOGoUser userWithLogin: uid]; user = [SOGoUser userWithLogin: uid];
component = [self component: NO secure: NO]; component = [self component: NO secure: NO];
return [component findParticipant: user]; return [component userAsAttendee: user];
} }
- (iCalPerson *) iCalPersonWithUID: (NSString *) uid - (iCalPerson *) iCalPersonWithUID: (NSString *) uid
@ -1061,7 +1060,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
ownerUser = [SOGoUser userWithLogin: owner]; ownerUser = [SOGoUser userWithLogin: owner];
if ([component userIsOrganizer: ownerUser]) if ([component userIsOrganizer: ownerUser])
role = SOGoCalendarRole_Organizer; role = SOGoCalendarRole_Organizer;
else if ([component userIsParticipant: ownerUser]) else if ([component userIsAttendee: ownerUser])
role = SOGoCalendarRole_Participant; role = SOGoCalendarRole_Participant;
else else
role = SOGoRole_None; role = SOGoRole_None;
@ -1119,7 +1118,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
aclUser = [SOGoUser userWithLogin: uid]; aclUser = [SOGoUser userWithLogin: uid];
if ([component userIsOrganizer: aclUser]) if ([component userIsOrganizer: aclUser])
[roles addObject: SOGoCalendarRole_Organizer]; [roles addObject: SOGoCalendarRole_Organizer];
else if ([component userIsParticipant: aclUser]) else if ([component userIsAttendee: aclUser])
[roles addObject: SOGoCalendarRole_Participant]; [roles addObject: SOGoCalendarRole_Participant];
accessRole accessRole
= [container roleForComponentsWithAccessClass: [component symbolicAccessClass] = [container roleForComponentsWithAccessClass: [component symbolicAccessClass]

View file

@ -34,10 +34,10 @@ extern NSNumber *iCalDistantFutureNumber;
+ (void) initializeSOGoExtensions; + (void) initializeSOGoExtensions;
- (BOOL) userIsParticipant: (SOGoUser *) user; - (BOOL) userIsAttendee: (SOGoUser *) user;
- (BOOL) userIsOrganizer: (SOGoUser *) user; - (BOOL) userIsOrganizer: (SOGoUser *) user;
- (iCalPerson *) userAsParticipant: (SOGoUser *) user; - (iCalPerson *) userAsAttendee: (SOGoUser *) user;
- (NSArray *) attendeeUIDs; - (NSArray *) attendeeUIDs;
- (BOOL) isStillRelevant; - (BOOL) isStillRelevant;

View file

@ -57,40 +57,40 @@ NSNumber *iCalDistantFutureNumber = nil;
} }
} }
- (BOOL) userIsParticipant: (SOGoUser *) user - (BOOL) userIsAttendee: (SOGoUser *) user
{ {
NSEnumerator *participants; NSEnumerator *attendees;
iCalPerson *currentParticipant; iCalPerson *currentAttendee;
BOOL isParticipant; BOOL isAttendee;
isParticipant = NO; isAttendee = NO;
participants = [[self participants] objectEnumerator]; attendees = [[self attendees] objectEnumerator];
currentParticipant = [participants nextObject]; currentAttendee = [attendees nextObject];
while (!isParticipant while (!isAttendee
&& currentParticipant) && currentAttendee)
if ([user hasEmail: [currentParticipant rfc822Email]]) if ([user hasEmail: [currentAttendee rfc822Email]])
isParticipant = YES; isAttendee = YES;
else else
currentParticipant = [participants nextObject]; currentAttendee = [attendees nextObject];
return isParticipant; return isAttendee;
} }
- (iCalPerson *) userAsParticipant: (SOGoUser *) user - (iCalPerson *) userAsAttendee: (SOGoUser *) user
{ {
NSEnumerator *participants; NSEnumerator *attendees;
iCalPerson *currentParticipant, *userParticipant; iCalPerson *currentAttendee, *userAttendee;
userParticipant = nil; userAttendee = nil;
participants = [[self participants] objectEnumerator]; attendees = [[self attendees] objectEnumerator];
while (!userParticipant while (!userAttendee
&& (currentParticipant = [participants nextObject])) && (currentAttendee = [attendees nextObject]))
if ([user hasEmail: [currentParticipant rfc822Email]]) if ([user hasEmail: [currentAttendee rfc822Email]])
userParticipant = currentParticipant; userAttendee = currentAttendee;
return userParticipant; return userAttendee;
} }
- (BOOL) userIsOrganizer: (SOGoUser *) user - (BOOL) userIsOrganizer: (SOGoUser *) user

View file

@ -46,13 +46,20 @@
#import "SOGoContactGCSFolder.h" #import "SOGoContactGCSFolder.h"
#define folderListingFields [NSArray arrayWithObjects: @"c_name", @"c_cn", \ static NSArray *folderListingFields = nil;
@"c_givenname", @"c_sn", @"c_screenname", \
@"c_o", @"c_mail", @"c_telephonenumber", \
nil]
@implementation SOGoContactGCSFolder @implementation SOGoContactGCSFolder
+ (void) initialize
{
if (!folderListingFields)
folderListingFields = [[NSArray alloc] initWithObjects: @"c_name",
@"c_cn", @"c_givenname", @"c_sn",
@"c_screenname", @"c_o",
@"c_mail", @"c_telephonenumber",
@"c_component", nil];
}
- (Class) objectClassForContent: (NSString *) content - (Class) objectClassForContent: (NSString *) content
{ {
CardGroup *cardEntry; CardGroup *cardEntry;

View file

@ -34,7 +34,6 @@ SOGo_HEADER_FILES = \
SOGoDateFormatter.h \ SOGoDateFormatter.h \
SOGoPermissions.h \ SOGoPermissions.h \
SOGoStartupLogger.h \ SOGoStartupLogger.h \
iCalEntityObject+Utilities.h \
NSArray+DAV.h \ NSArray+DAV.h \
NSArray+Utilities.h \ NSArray+Utilities.h \
NSCalendarDate+SOGo.h \ NSCalendarDate+SOGo.h \
@ -103,7 +102,6 @@ SOGo_OBJC_FILES = \
SQLSource.m \ SQLSource.m \
SOGoUserProfile.m \ SOGoUserProfile.m \
SOGoSQLUserProfile.m \ SOGoSQLUserProfile.m \
iCalEntityObject+Utilities.m \
NSArray+DAV.m \ NSArray+DAV.m \
NSArray+Utilities.m \ NSArray+Utilities.m \
NSCalendarDate+SOGo.m \ NSCalendarDate+SOGo.m \

View file

@ -1,37 +0,0 @@
/* iCalEntityObject+Utilities.h - this file is part of SOGo
*
* Copyright (C) 2007 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef ICALENTITYOBJECT_UTILITIES_H
#define ICALENTITYOBJECT_UTILITIES_H
#import <NGCards/iCalEntityObject.h>
@class iCalPerson;
@class SOGoUser;
@interface iCalEntityObject (SOGoAddition)
- (iCalPerson *) findParticipant: (SOGoUser *) user;
@end
#endif /* ICALENTITYOBJECT_UTILITIES_H */

View file

@ -1,53 +0,0 @@
/* iCalEntityObject+Utilities.m - this file is part of SOGo
*
* Copyright (C) 2007 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>
#import <NGCards/iCalPerson.h>
#import "SOGoUser.h"
#import "iCalEntityObject+Utilities.h"
#warning we should move this into Appointments.
@implementation iCalEntityObject (SOGoAddition)
- (iCalPerson *) findParticipant: (SOGoUser *) user
{
iCalPerson *participant, *currentParticipant;
NSEnumerator *participants;
participant = nil;
participants = [[self attendees] objectEnumerator];
currentParticipant = [participants nextObject];
while (currentParticipant && !participant)
if ([user hasEmail: [currentParticipant rfc822Email]])
participant = currentParticipant;
else
currentParticipant = [participants nextObject];
return participant;
}
@end

View file

@ -78,8 +78,11 @@
us = [activeUser userSettings]; us = [activeUser userSettings];
moduleSettings = [us objectForKey: module]; moduleSettings = [us objectForKey: module];
if (!moduleSettings) if (!moduleSettings)
moduleSettings = [NSMutableDictionary dictionary]; {
[us setObject: moduleSettings forKey: module]; moduleSettings = [NSMutableDictionary new];
[us setObject: moduleSettings forKey: module];
[moduleSettings release];
}
contextIsSetup = YES; contextIsSetup = YES;
} }
} }
@ -149,44 +152,6 @@
return email; return email;
} }
- (NSArray *) _responseForResults: (NSArray *) results
{
NSEnumerator *contacts;
NSString *email, *info;
NSDictionary *contact;
NSMutableArray *formattedContacts;
NSMutableDictionary *formattedContact;
formattedContacts = [NSMutableArray arrayWithCapacity: [results count]];
if ([results count] > 0)
{
contacts = [results objectEnumerator];
contact = [contacts nextObject];
while (contact)
{
email = [contact objectForKey: @"c_email"];
if ([email length])
{
formattedContact = [NSMutableDictionary dictionary];
[formattedContact setObject: [contact objectForKey: @"c_uid"]
forKey: @"uid"];
[formattedContact setObject: [contact objectForKey: @"cn"]
forKey: @"name"];
[formattedContact setObject: email
forKey: @"email"];
info = [contact objectForKey: @"c_info"];
if (info != nil)
[formattedContact setObject: info
forKey: @"contactInfo"];
[formattedContacts addObject: formattedContact];
}
contact = [contacts nextObject];
}
}
return formattedContacts;
}
- (id <WOActionResults>) allContactSearchAction - (id <WOActionResults>) allContactSearchAction
{ {
id <WOActionResults> result; id <WOActionResults> result;
@ -196,7 +161,7 @@
NSArray *folders, *contacts, *descriptors, *sortedContacts; NSArray *folders, *contacts, *descriptors, *sortedContacts;
NSMutableArray *sortedFolders; NSMutableArray *sortedFolders;
NSMutableDictionary *contact, *uniqueContacts; NSMutableDictionary *contact, *uniqueContacts;
unsigned int i, j; unsigned int i, j, max;
NSSortDescriptor *commonNameDescriptor; NSSortDescriptor *commonNameDescriptor;
BOOL excludeGroups, excludeLists; BOOL excludeGroups, excludeLists;
@ -217,9 +182,10 @@
else else
[localException raise]; [localException raise];
NS_ENDHANDLER; NS_ENDHANDLER;
sortedFolders = [NSMutableArray arrayWithCapacity: [folders count]]; max = [folders count];
sortedFolders = [NSMutableArray arrayWithCapacity: max];
uniqueContacts = [NSMutableDictionary dictionary]; uniqueContacts = [NSMutableDictionary dictionary];
for (i = 0; i < [folders count]; i++) for (i = 0; i < max; i++)
{ {
folder = [folders objectAtIndex: i]; folder = [folders objectAtIndex: i];
/* We first search in LDAP folders (in case of duplicated entries in GCS folders) */ /* We first search in LDAP folders (in case of duplicated entries in GCS folders) */
@ -228,7 +194,7 @@
else else
[sortedFolders addObject: folder]; [sortedFolders addObject: folder];
} }
for (i = 0; i < [sortedFolders count]; i++) for (i = 0; i < max; i++)
{ {
folder = [sortedFolders objectAtIndex: i]; folder = [sortedFolders objectAtIndex: i];
//NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]); //NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]);
@ -245,8 +211,8 @@
&& [uniqueContacts objectForKey: mail] == nil && [uniqueContacts objectForKey: mail] == nil
&& !(excludeGroups && [contact objectForKey: @"isGroup"])) && !(excludeGroups && [contact objectForKey: @"isGroup"]))
[uniqueContacts setObject: contact forKey: mail]; [uniqueContacts setObject: contact forKey: mail];
else if (!excludeLists else if (!excludeLists && [[contact objectForKey: @"c_component"]
&& [[contact objectForKey: @"c_name"] hasSuffix: @".vlf"]) isEqualToString: @"vlist"])
{ {
[contact setObject: [folder nameInContainer] [contact setObject: [folder nameInContainer]
forKey: @"container"]; forKey: @"container"];
@ -254,14 +220,16 @@
forKey: [contact objectForKey: @"c_name"]]; forKey: [contact objectForKey: @"c_name"]];
} }
} }
} }
if ([uniqueContacts count] > 0) if ([uniqueContacts count] > 0)
{ {
// Sort the contacts by display name // Sort the contacts by display name
commonNameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"c_cn" commonNameDescriptor = [[NSSortDescriptor alloc] initWithKey: @"c_cn"
ascending:YES] autorelease]; ascending:YES];
descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil]; descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil];
sortedContacts = [[uniqueContacts allValues] sortedArrayUsingDescriptors: descriptors]; [commonNameDescriptor release];
sortedContacts = [[uniqueContacts allValues]
sortedArrayUsingDescriptors: descriptors];
} }
else else
sortedContacts = [NSArray array]; sortedContacts = [NSArray array];
@ -269,7 +237,7 @@
sortedContacts, @"contacts", sortedContacts, @"contacts",
nil]; nil];
result = [self responseWithStatus: 200]; result = [self responseWithStatus: 200];
[(WOResponse*)result appendContentString: [data jsonRepresentation]]; [(WOResponse*) result appendContentString: [data jsonRepresentation]];
} }
else else
result = [NSException exceptionWithHTTPStatus: 400 result = [NSException exceptionWithHTTPStatus: 400
@ -278,36 +246,6 @@
return result; return result;
} }
- (id <WOActionResults>) contactSearchAction
{
NSDictionary *data;
NSArray *contacts;
NSString *searchText, *domain;
id <WOActionResults> result;
SOGoUserManager *um;
searchText = [self queryParameterForKey: @"search"];
if ([searchText length] > 0)
{
um = [SOGoUserManager sharedUserManager];
domain = [[context activeUser] domain];
contacts
= [self _responseForResults: [um fetchContactsMatching: searchText
inDomain: domain]];
data = [NSDictionary dictionaryWithObjectsAndKeys:
searchText, @"searchText",
contacts, @"contacts",
nil];
result = [self responseWithStatus: 200];
[(WOResponse*)result appendContentString: [data jsonRepresentation]];
}
else
result = [NSException exceptionWithHTTPStatus: 400
reason: @"missing 'search' parameter"];
return result;
}
- (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder - (NSArray *) _subFoldersFromFolder: (SOGoParentFolder *) parentFolder
{ {
NSMutableArray *folders; NSMutableArray *folders;
@ -375,8 +313,7 @@
- (NSString *) currentContactFolderId - (NSString *) currentContactFolderId
{ {
return [NSString stringWithFormat: @"/%@", return [NSString stringWithFormat: @"/%@", [currentFolder nameInContainer]];
[currentFolder nameInContainer]];
} }
- (NSString *) currentContactFolderName - (NSString *) currentContactFolderName
@ -391,7 +328,8 @@
- (NSString *) currentContactFolderClass - (NSString *) currentContactFolderClass
{ {
return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]? @"remote" : @"local"); return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]
? @"remote" : @"local");
} }
- (NSString *) verticalDragHandleStyle - (NSString *) verticalDragHandleStyle
@ -401,7 +339,8 @@
[self _setupContext]; [self _setupContext];
vertical = [moduleSettings objectForKey: @"DragHandleVertical"]; vertical = [moduleSettings objectForKey: @"DragHandleVertical"];
return ((vertical && [vertical intValue] > 0) ? (id)[vertical stringByAppendingFormat: @"px"] : nil); return ((vertical && [vertical intValue] > 0)
? (id)[vertical stringByAppendingFormat: @"px"] : nil);
} }
- (NSString *) horizontalDragHandleStyle - (NSString *) horizontalDragHandleStyle
@ -411,7 +350,8 @@
[self _setupContext]; [self _setupContext];
horizontal = [moduleSettings objectForKey: @"DragHandleHorizontal"]; horizontal = [moduleSettings objectForKey: @"DragHandleHorizontal"];
return ((horizontal && [horizontal intValue] > 0) ? (id)[horizontal stringByAppendingFormat: @"px"] : nil); return ((horizontal && [horizontal intValue] > 0)
? (id)[horizontal stringByAppendingFormat: @"px"] : nil);
} }
- (NSString *) contactsListContentStyle - (NSString *) contactsListContentStyle
@ -421,7 +361,8 @@
[self _setupContext]; [self _setupContext];
height = [moduleSettings objectForKey: @"DragHandleVertical"]; height = [moduleSettings objectForKey: @"DragHandleVertical"];
return ((height && [height intValue] > 0) ? [NSString stringWithFormat: @"%ipx", ([height intValue] - 27)] : nil); return ((height && [height intValue] > 0)
? [NSString stringWithFormat: @"%ipx", ([height intValue] - 27)] : nil);
} }
- (WOResponse *) saveDragHandleStateAction - (WOResponse *) saveDragHandleStateAction

View file

@ -143,29 +143,23 @@
- (WOResponse *) propertiesAction - (WOResponse *) propertiesAction
{ {
NSArray *references;
NSMutableArray *data; NSMutableArray *data;
NGVCardReference *card; NGVCardReference *card;
WOResponse *rc; int count, max;
int i, count;
data = [NSMutableArray array]; list = [[self clientObject] vList];
co = [self clientObject]; references = [list cardReferences];
list = [co vList]; max = [references count];
data = [NSMutableArray arrayWithCapacity: max];
count = [[list cardReferences] count]; for (count = 0; count < max; count++)
for (i = 0; i < count; i++)
{ {
card = [[list cardReferences] objectAtIndex: i]; card = [references objectAtIndex: count];
[data addObject: [NSArray arrayWithObjects: [card reference], [card fn], [data addObject: [NSArray arrayWithObjects: [card reference],
[card email], nil]]; [card fn], [card email], nil]];
} }
rc = [context response]; return [self responseWithStatus: 200 andJSONRepresentation: data];
[rc setHeader: @"text/plain; charset=utf-8"
forKey: @"content-type"];
[rc appendContentString: [data jsonRepresentation]];
return rc;
} }
@end @end

View file

@ -23,11 +23,6 @@
pageName = "UIxContactFoldersView"; pageName = "UIxContactFoldersView";
actionName = "mailerContacts"; actionName = "mailerContacts";
}; };
contactSearch = {
protectedBy = "<public>";
pageName = "UIxContactFoldersView";
actionName = "contactSearch";
};
allContactSearch = { allContactSearch = {
protectedBy = "<public>"; protectedBy = "<public>";
pageName = "UIxContactFoldersView"; pageName = "UIxContactFoldersView";

View file

@ -33,6 +33,8 @@
- (WOResponse *) acceptAction; - (WOResponse *) acceptAction;
- (WOResponse *) declineAction; - (WOResponse *) declineAction;
- (WOResponse *) tentativeAction;
- (WOResponse *) delegateAction;
- (WOResponse *) addToCalendarAction; - (WOResponse *) addToCalendarAction;
- (WOResponse *) deleteFromCalendarAction; - (WOResponse *) deleteFromCalendarAction;

View file

@ -48,7 +48,6 @@
#import <Mailer/SOGoMailObject.h> #import <Mailer/SOGoMailObject.h>
#import <SOGo/SOGoParentFolder.h> #import <SOGo/SOGoParentFolder.h>
#import <SOGo/SOGoUser.h> #import <SOGo/SOGoUser.h>
#import <SOGo/iCalEntityObject+Utilities.h>
#import <Mailer/SOGoMailBodyPart.h> #import <Mailer/SOGoMailBodyPart.h>
#import "UIxMailPartICalActions.h" #import "UIxMailPartICalActions.h"
@ -261,6 +260,12 @@
withDelegate: nil]; withDelegate: nil];
} }
- (WOResponse *) tentativeAction
{
return [self _changePartStatusAction: @"TENTATIVE"
withDelegate: nil];
}
- (WOResponse *) delegateAction - (WOResponse *) delegateAction
{ {
// BOOL receiveUpdates; // BOOL receiveUpdates;
@ -369,7 +374,7 @@
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0]; address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail]; emailFrom = [address baseEMail];
return [event findParticipantWithEmail: emailFrom]; return [event findAttendeeWithEmail: emailFrom];
} }
- (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent - (BOOL) _updateParticipantStatusInEvent: (iCalEvent *) calendarEvent

View file

@ -44,7 +44,6 @@
#import <SOGo/SOGoUser.h> #import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserFolder.h> #import <SOGo/SOGoUserFolder.h>
#import <SOGo/SOGoUserDefaults.h> #import <SOGo/SOGoUserDefaults.h>
#import <SOGo/iCalEntityObject+Utilities.h>
#import <Appointments/iCalEntityObject+SOGo.h> #import <Appointments/iCalEntityObject+SOGo.h>
#import <Appointments/SOGoAppointmentFolder.h> #import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentObject.h> #import <Appointments/SOGoAppointmentObject.h>
@ -378,7 +377,7 @@
- (BOOL) isLoggedInUserAnAttendee - (BOOL) isLoggedInUserAnAttendee
{ {
return [[self authorativeEvent] userIsParticipant: [context activeUser]]; return [[self authorativeEvent] userIsAttendee: [context activeUser]];
} }
- (NSString *) currentAttendeeClass - (NSString *) currentAttendeeClass
@ -451,7 +450,7 @@
{ {
iCalPerson *currentUser; iCalPerson *currentUser;
currentUser = [[self authorativeEvent] findParticipant: [context activeUser]]; currentUser = [[self authorativeEvent] userAsAttendee: [context activeUser]];
return currentUser; return currentUser;
} }
@ -463,7 +462,7 @@
should translate the email to an internal uid and then retrieve should translate the email to an internal uid and then retrieve
all emails addresses for matching the participant. all emails addresses for matching the participant.
Note: -findParticipantWithEmail: does not parse the email! Note: -findAttendeeWithEmail: does not parse the email!
*/ */
iCalEvent *e; iCalEvent *e;
iCalPerson *p; iCalPerson *p;
@ -473,9 +472,9 @@
e = [self storedEvent]; e = [self storedEvent];
if (e) if (e)
{ {
p = [e findParticipantWithEmail: [self replySenderBaseEMail]]; p = [e findAttendeeWithEmail: [self replySenderBaseEMail]];
if (!p) if (!p)
p = [e findParticipantWithEmail:[self replySenderEMail]]; p = [e findAttendeeWithEmail:[self replySenderEMail]];
} }
return p; return p;
@ -496,7 +495,7 @@
address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0]; address = [[mailObject fromEnvelopeAddresses] objectAtIndex: 0];
emailFrom = [address baseEMail]; emailFrom = [address baseEMail];
return [event findParticipantWithEmail: emailFrom]; return [event findAttendeeWithEmail: emailFrom];
} }
- (BOOL) hasSenderStatusChanged - (BOOL) hasSenderStatusChanged

View file

@ -28,6 +28,11 @@
actionClass = "UIxMailPartICalActions"; actionClass = "UIxMailPartICalActions";
actionName = "decline"; actionName = "decline";
}; };
tentative = {
protectedBy = "View";
actionClass = "UIxMailPartICalActions";
actionName = "tentative";
};
delegate = { delegate = {
protectedBy = "View"; protectedBy = "View";
actionClass = "UIxMailPartICalActions"; actionClass = "UIxMailPartICalActions";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "A data que você informou ocorre antes da data ini
= "Você tem certeza que quer apagar o calendário \"%{0}\"?"; = "Você tem certeza que quer apagar o calendário \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Participante Requerido"; "Participant" = "Participante";
"Optional participant" = "Participante Opcional"; "Optional Participant" = "Participante Opcional";
"Non Participant" = "Não Participante";
"Chair" = "Cadeira"; "Chair" = "Cadeira";
"Needs action" = "Ações necessárias"; "Needs action" = "Ações necessárias";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "Zadané datum konce je před začátkem události.
= "Opravdu chcete smazat kalendář \"%{0}\"?"; = "Opravdu chcete smazat kalendář \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Vyžadovaný účastník"; "Participant" = "Účastník";
"Optional participant" = "Nepovinný účastník"; "Optional Participant" = "Nepovinný účastník";
"Non Participant" = "Non Participant";
"Chair" = "Židle"; "Chair" = "Židle";
"Needs action" = "Vyžaduje akci"; "Needs action" = "Vyžaduje akci";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "Het begin vindt plaats vóór het einde.";
= "Weet u zeker dat u de agenda \"%{0}\" wilt verwijderen?"; = "Weet u zeker dat u de agenda \"%{0}\" wilt verwijderen?";
/* Legend */ /* Legend */
"Required participant" = "Vereiste deelnemer"; "Participant" = "Deelnemer";
"Optional participant" = "Gewenste deelnemer"; "Optional Participant" = "Gewenste deelnemer";
"Non Participant" = "Non Participant";
"Chair" = "Voorzitter"; "Chair" = "Voorzitter";
"Needs action" = "Actie vereist"; "Needs action" = "Actie vereist";

View file

@ -246,10 +246,10 @@
/* Appointments (participation state) */ /* Appointments (participation state) */
"partStat_NEEDS-ACTION" = "Needs action"; "partStat_NEEDS-ACTION" = "I will confirm later";
"partStat_ACCEPTED" = "I will attend"; "partStat_ACCEPTED" = "I will attend";
"partStat_DECLINED" = "I will not attend"; "partStat_DECLINED" = "I will not attend";
"partStat_TENTATIVE" = "I will confirm later"; "partStat_TENTATIVE" = "I might attend";
"partStat_DELEGATED" = "I delegate"; "partStat_DELEGATED" = "I delegate";
"partStat_OTHER" = "Other"; "partStat_OTHER" = "Other";
@ -460,8 +460,9 @@ validate_endbeforestart = "The end date that you entered occurs before the st
= "Are you sure you want to delete the calendar \"%{0}\"?"; = "Are you sure you want to delete the calendar \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Required participant"; "Participant" = "Participant";
"Optional participant" = "Optional participant"; "Optional Participant" = "Optional Participant";
"Non Participant" = "Non Participant";
"Chair" = "Chair"; "Chair" = "Chair";
"Needs action" = "Needs action"; "Needs action" = "Needs action";

View file

@ -246,10 +246,10 @@
/* Appointments (participation state) */ /* Appointments (participation state) */
"partStat_NEEDS-ACTION" = "Décision attendue"; "partStat_NEEDS-ACTION" = "Je confirmerai plus tard";
"partStat_ACCEPTED" = "Je participerai"; "partStat_ACCEPTED" = "Je participerai";
"partStat_DECLINED" = "Je ne participerai pas"; "partStat_DECLINED" = "Je ne participerai pas";
"partStat_TENTATIVE" = "Je confirmerai plus tard"; "partStat_TENTATIVE" = "Je participerai peut-être";
"partStat_DELEGATED" = "Je délègue"; "partStat_DELEGATED" = "Je délègue";
"partStat_OTHER" = "???"; "partStat_OTHER" = "???";
@ -460,9 +460,10 @@ validate_endbeforestart = "La date de fin est avant la date de début.";
= "Voulez-vous vraiment supprimer l'agenda «%{0}»?"; = "Voulez-vous vraiment supprimer l'agenda «%{0}»?";
/* Legend */ /* Legend */
"Required participant" = "Participant obligatoire"; "Participant" = "Invité";
"Optional participant" = "Participant facultatif"; "Optional Participant" = "Invité optionnel";
"Chair" = "Chaise"; "Non Participant" = "Non-invité";
"Chair" = "Président";
"Needs action" = "En attente"; "Needs action" = "En attente";
"Accepted" = "Accepté"; "Accepted" = "Accepté";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "Ihr Beginn ist nach dem Ende";
= "Wollen Sie diesen Kalender wirklich löschen \"%{0}\"?"; = "Wollen Sie diesen Kalender wirklich löschen \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "notwendiger Teilnehmer"; "Participant" = "Teilnehmer";
"Optional participant" = "optionaler Teilnehmer"; "Optional Participant" = "optionaler Teilnehmer";
"Non Participant" = "Non Participant";
"Chair" = "Vorsitz"; "Chair" = "Vorsitz";
"Needs action" = "Benötigt Eingriff"; "Needs action" = "Benötigt Eingriff";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "A megadott befejező dátum korábbi, mint a kezd
= "Biztosan törli ezt a naptárat: \"%{0}\"?"; = "Biztosan törli ezt a naptárat: \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Kötelező résztvevő"; "Participant" = "Kötelező résztvevő";
"Optional participant" = "Nem kötelező résztvevő"; "Optional Participant" = "Nem kötelező résztvevő";
"Non Participant" = "Non Participant";
"Chair" = "Szék"; "Chair" = "Szék";
"Needs action" = "Foglalkozni kell vele"; "Needs action" = "Foglalkozni kell vele";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "La data finale specificata è precedente alla data
= "Sei sicuro di voler cancellare il calendario \"%{0}\"?"; = "Sei sicuro di voler cancellare il calendario \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Richiede partecipanti"; "Participant" = "Partecipanti";
"Optional participant" = "Partecipanti opzionali"; "Optional Participant" = "Partecipanti opzionali";
"Non Participant" = "Non Partecipanti";
"Chair" = "Sedia"; "Chair" = "Sedia";
"Needs action" = "Richiede un'azione"; "Needs action" = "Richiede un'azione";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "The end date that you entered occurs before the st
= "Вы уверены что хотите удалить календарь \"%{0}\"?"; = "Вы уверены что хотите удалить календарь \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Required participant"; "Participant" = "Participant";
"Optional participant" = "Optional participant"; "Optional Participant" = "Optional Participant";
"Non Participant" = "Non Participant";
"Chair" = "Chair"; "Chair" = "Chair";
"Needs action" = "Needs action"; "Needs action" = "Needs action";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "Su fecha/hora de comienzo es posterio a la de fina
= "¿Está seguro/a que desea borrar el calendario \"%{0}\"?"; = "¿Está seguro/a que desea borrar el calendario \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Asistente obligatorio"; "Participant" = "Asistente";
"Optional participant" = "Asistentes opcionales"; "Optional Participant" = "Asistentes opcionales";
"Non Participant" = "Non Particpant";
"Chair" = "Presidente"; "Chair" = "Presidente";
"Needs action" = "Requiere intervención"; "Needs action" = "Requiere intervención";

View file

@ -460,8 +460,9 @@ validate_endbeforestart = "Angivet slutdatumet inträffar före angivet start
= "Är du säker på att du vill ta bort kalendern \"%{0}\"?"; = "Är du säker på att du vill ta bort kalendern \"%{0}\"?";
/* Legend */ /* Legend */
"Required participant" = "Deltagande krävs"; "Participant" = "Deltagande krävs";
"Optional participant" = "Deltagande valfritt"; "Optional Participant" = "Deltagande valfritt";
"Non Participant" = "Non Participant";
"Chair" = "Stol"; "Chair" = "Stol";
"Needs action" = "Behöver åtgärd"; "Needs action" = "Behöver åtgärd";

View file

@ -522,22 +522,32 @@
[event setTransparency: (isTransparent? @"TRANSPARENT" : @"OPAQUE")]; [event setTransparency: (isTransparent? @"TRANSPARENT" : @"OPAQUE")];
} }
// TODO: add tentatively - (id) _statusChangeAction: (NSString *) newStatus
{
[[self clientObject] changeParticipationStatus: newStatus
withDelegate: nil];
return [self responseWith204];
}
- (id) acceptAction - (id) acceptAction
{ {
[[self clientObject] changeParticipationStatus: @"ACCEPTED" return [self _statusChangeAction: @"ACCEPTED"];
withDelegate: nil];
return self;
} }
- (id) declineAction - (id) declineAction
{ {
[[self clientObject] changeParticipationStatus: @"DECLINED" return [self _statusChangeAction: @"DECLINED"];
withDelegate: nil]; }
return self; - (id) needsActionAction
{
return [self _statusChangeAction: @"NEEDS-ACTION"];
}
- (id) tentativeAction
{
return [self _statusChangeAction: @"TENTATIVE"];
} }
- (id) delegateAction - (id) delegateAction
@ -584,7 +594,7 @@
reason: @"missing 'to' parameter"]; reason: @"missing 'to' parameter"];
if (!response) if (!response)
response = [self responseWithStatus: 200]; response = [self responseWith204];
return response; return response;
} }

View file

@ -54,7 +54,6 @@
#import <Appointments/SOGoAppointmentObject.h> #import <Appointments/SOGoAppointmentObject.h>
#import <Appointments/SOGoAppointmentOccurence.h> #import <Appointments/SOGoAppointmentOccurence.h>
#import <Appointments/SOGoTaskObject.h> #import <Appointments/SOGoTaskObject.h>
#import <SOGo/iCalEntityObject+Utilities.h>
#import <SOGo/NSArray+Utilities.h> #import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSDictionary+BSJSONAdditions.h> #import <SOGo/NSDictionary+BSJSONAdditions.h>
#import <SOGo/NSDictionary+Utilities.h> #import <SOGo/NSDictionary+Utilities.h>
@ -268,6 +267,8 @@ iRANGE(2);
[currentAttendeeData setObject: [[currentAttendee partStat] lowercaseString] [currentAttendeeData setObject: [[currentAttendee partStat] lowercaseString]
forKey: @"partstat"]; forKey: @"partstat"];
[currentAttendeeData setObject: [[currentAttendee role] lowercaseString]
forKey: @"role"];
if ([[currentAttendee delegatedTo] length]) if ([[currentAttendee delegatedTo] length])
[currentAttendeeData setObject: [[currentAttendee delegatedTo] rfc822Email] [currentAttendeeData setObject: [[currentAttendee delegatedTo] rfc822Email]
@ -587,7 +588,7 @@ iRANGE(2);
um = [SOGoUserManager sharedUserManager]; um = [SOGoUserManager sharedUserManager];
owner = [componentCalendar ownerInContext: context]; owner = [componentCalendar ownerInContext: context];
ownerEmail = [um getEmailForUID: owner]; ownerEmail = [um getEmailForUID: owner];
ASSIGN (ownerAsAttendee, [component findParticipantWithEmail: (id)ownerEmail]); ASSIGN (ownerAsAttendee, [component findAttendeeWithEmail: (id)ownerEmail]);
} }
} }
// /* cycles */ // /* cycles */
@ -990,14 +991,26 @@ iRANGE(2);
{ {
NSString *word; NSString *word;
if ([item intValue] == iCalPersonPartStatAccepted) switch ([item intValue])
word = @"ACCEPTED"; {
else if ([item intValue] == iCalPersonPartStatDeclined) case iCalPersonPartStatAccepted:
word = @"DECLINED"; word = @"ACCEPTED";
else if ([item intValue] == iCalPersonPartStatDelegated) break;
word = @"DELEGATED"; case iCalPersonPartStatDeclined:
else word = @"DECLINED";
word = @"UNKNOWN"; break;
case iCalPersonPartStatNeedsAction:
word = @"NEEDS-ACTION";
break;
case iCalPersonPartStatTentative:
word = @"TENTATIVE";
break;
case iCalPersonPartStatDelegated:
word = @"DELEGATED";
break;
default:
word = @"UNKNOWN";
}
return [self labelForKey: [NSString stringWithFormat: @"partStat_%@", word]]; return [self labelForKey: [NSString stringWithFormat: @"partStat_%@", word]];
} }
@ -1005,8 +1018,10 @@ iRANGE(2);
- (NSArray *) replyList - (NSArray *) replyList
{ {
return [NSArray arrayWithObjects: return [NSArray arrayWithObjects:
[NSNumber numberWithInt: iCalPersonPartStatAccepted], [NSNumber numberWithInt: iCalPersonPartStatAccepted],
[NSNumber numberWithInt: iCalPersonPartStatDeclined], [NSNumber numberWithInt: iCalPersonPartStatDeclined],
[NSNumber numberWithInt: iCalPersonPartStatNeedsAction],
[NSNumber numberWithInt: iCalPersonPartStatTentative],
[NSNumber numberWithInt: iCalPersonPartStatDelegated], [NSNumber numberWithInt: iCalPersonPartStatDelegated],
nil]; nil];
} }
@ -1508,7 +1523,7 @@ RANGE(2);
unsigned int count, max; unsigned int count, max;
NSString *currentEmail; NSString *currentEmail;
iCalPerson *currentAttendee; iCalPerson *currentAttendee;
NSString *json; NSString *json, *role, *partstat;
NSDictionary *attendeesData; NSDictionary *attendeesData;
NSArray *attendees; NSArray *attendees;
NSDictionary *currentData; NSDictionary *currentData;
@ -1529,17 +1544,33 @@ RANGE(2);
{ {
currentData = [attendees objectAtIndex: count]; currentData = [attendees objectAtIndex: count];
currentEmail = [currentData objectForKey: @"email"]; currentEmail = [currentData objectForKey: @"email"];
currentAttendee = [component findParticipantWithEmail: currentEmail]; role = [[currentData objectForKey: @"role"] uppercaseString];
if (!role)
role = @"REQ-PARTICIPANT";
if ([role isEqualToString: @"NON-PARTICIPANT"])
partstat = @"";
else
{
partstat = [[currentData objectForKey: @"partstat"]
uppercaseString];
if (!partstat)
partstat = @"NEEDS-ACTION";
}
currentAttendee = [component findAttendeeWithEmail: currentEmail];
if (!currentAttendee) if (!currentAttendee)
{ {
currentAttendee = [iCalPerson elementWithTag: @"attendee"]; currentAttendee = [iCalPerson elementWithTag: @"attendee"];
[currentAttendee setCn: [currentData objectForKey: @"name"]]; [currentAttendee setCn: [currentData objectForKey: @"name"]];
[currentAttendee setEmail: currentEmail]; [currentAttendee setEmail: currentEmail];
[currentAttendee setRole: @"REQ-PARTICIPANT"]; // [currentAttendee
[currentAttendee setRsvp: @"TRUE"]; // setParticipationStatus: iCalPersonPartStatNeedsAction];
[currentAttendee
setParticipationStatus: iCalPersonPartStatNeedsAction];
} }
[currentAttendee
setRsvp: ([role isEqualToString: @"NON-PARTICIPANT"]
? @"FALSE"
: @"TRUE")];
[currentAttendee setRole: role];
[currentAttendee setPartStat: partstat];
[newAttendees addObject: currentAttendee]; [newAttendees addObject: currentAttendee];
} }
[component setAttendees: newAttendees]; [component setAttendees: newAttendees];
@ -1965,8 +1996,7 @@ RANGE(2);
isOrganizer = ![ownerUser hasEmail: [[component organizer] sentBy]]; isOrganizer = ![ownerUser hasEmail: [[component organizer] sentBy]];
if ([componentCalendar isKindOfClass: [SOGoWebAppointmentFolder class]] if ([componentCalendar isKindOfClass: [SOGoWebAppointmentFolder class]]
|| ([[component attendees] count] || ([component userIsAttendee: ownerUser]
&& [component userIsParticipant: ownerUser]
&& !isOrganizer && !isOrganizer
// Lightning does not manage participation status within tasks, // Lightning does not manage participation status within tasks,
// so we also ignore the participation status of tasks in the // so we also ignore the participation status of tasks in the
@ -1991,14 +2021,12 @@ RANGE(2);
{ {
SoSecurityManager *sm; SoSecurityManager *sm;
NSString *toolbarFilename, *adminToolbar; NSString *toolbarFilename, *adminToolbar;
SOGoUser *currentUser;
if ([clientObject isKindOfClass: [SOGoAppointmentObject class]]) if ([clientObject isKindOfClass: [SOGoAppointmentObject class]])
adminToolbar = @"SOGoAppointmentObject.toolbar"; adminToolbar = @"SOGoAppointmentObject.toolbar";
else else
adminToolbar = @"SOGoTaskObject.toolbar"; adminToolbar = @"SOGoTaskObject.toolbar";
currentUser = [context activeUser];
sm = [SoSecurityManager sharedSecurityManager]; sm = [SoSecurityManager sharedSecurityManager];
if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
@ -2035,36 +2063,45 @@ RANGE(2);
- (int) ownerIsAttendee: (SOGoUser *) ownerUser - (int) ownerIsAttendee: (SOGoUser *) ownerUser
andClientObject: (SOGoContentObject andClientObject: (SOGoContentObject
<SOGoComponentOccurence> *) clientObject <SOGoComponentOccurence> *) clientObject
{ {
BOOL isOrganizer; BOOL isOrganizer;
int rc = 0; iCalPerson *ownerAttendee;
int rc;
rc = 0;
isOrganizer = [component userIsOrganizer: ownerUser]; isOrganizer = [component userIsOrganizer: ownerUser];
if (isOrganizer) if (isOrganizer)
isOrganizer = ![ownerUser hasEmail: [[component organizer] sentBy]]; isOrganizer = ![ownerUser hasEmail: [[component organizer] sentBy]];
if ([[component attendees] count] if (!isOrganizer && ![[component tag] isEqualToString: @"VTODO"])
&& [component userIsParticipant: ownerUser] {
&& !isOrganizer ownerAttendee = [component userAsAttendee: ownerUser];
&& ![[component tag] isEqualToString: @"VTODO"]) if (ownerAttendee)
rc = 1; {
if ([[ownerAttendee rsvp] isEqualToString: @"true"])
rc = 1;
else
rc = 2;
}
}
return rc; return rc;
} }
- (int) delegateIsAttendee: (SOGoUser *) ownerUser - (int) delegateIsAttendee: (SOGoUser *) ownerUser
andClientObject: (SOGoContentObject andClientObject: (SOGoContentObject
<SOGoComponentOccurence> *) clientObject <SOGoComponentOccurence> *) clientObject
{ {
SoSecurityManager *sm; SoSecurityManager *sm;
SOGoUser *currentUser; iCalPerson *ownerAttendee;
int rc = 0; int rc;
rc = 0;
currentUser = [context activeUser];
sm = [SoSecurityManager sharedSecurityManager]; sm = [SoSecurityManager sharedSecurityManager];
if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
onObject: clientObject onObject: clientObject
inContext: context]) inContext: context])
@ -2072,11 +2109,15 @@ RANGE(2);
andClientObject: clientObject]; andClientObject: clientObject];
else if (![sm validatePermission: SOGoCalendarPerm_RespondToComponent else if (![sm validatePermission: SOGoCalendarPerm_RespondToComponent
onObject: clientObject onObject: clientObject
inContext: context] inContext: context])
&& [[component attendees] count] {
&& [component userIsParticipant: ownerUser] ownerAttendee = [component userAsAttendee: ownerUser];
&& ![component userIsOrganizer: ownerUser]) if ([[ownerAttendee rsvp] isEqualToString: @"true"]
rc = 1; && ![component userIsOrganizer: ownerUser])
rc = 1;
else
rc = 2;
}
else else
rc = 2; // not invited, just RO rc = 2; // not invited, just RO
@ -2090,9 +2131,8 @@ RANGE(2);
int rc; int rc;
clientObject = [self clientObject]; clientObject = [self clientObject];
ownerUser = [SOGoUser userWithLogin: [clientObject ownerInContext: context] ownerUser
roles: nil]; = [SOGoUser userWithLogin: [clientObject ownerInContext: context]];
if ([componentCalendar isKindOfClass: [SOGoWebAppointmentFolder class]]) if ([componentCalendar isKindOfClass: [SOGoWebAppointmentFolder class]])
rc = 2; rc = 2;
else else
@ -2113,9 +2153,54 @@ RANGE(2);
return [self getEventRWType] != 0; return [self getEventRWType] != 0;
} }
- (BOOL) userIsAttendee - (BOOL) userHasRSVP
{ {
return [self getEventRWType] == 1; return ([self getEventRWType] == 1);
}
- (NSString *) currentAttendeeClasses
{
NSMutableArray *classes;
iCalPerson *ownerAttendee;
SOGoUser *ownerUser;
NSString *role, *partStat;
SOGoCalendarComponent *co;
classes = [NSMutableArray arrayWithCapacity: 5];
/* rsvp class */
if (![[attendee rsvp] isEqualToString: @"true"])
[classes addObject: @"not-rsvp"];
/* partstat class */
partStat = [[attendee partStat] lowercaseString];
if (![partStat length])
partStat = @"no-partstat";
[classes addObject: partStat];
/* role class */
role = [[attendee role] lowercaseString];
if (![partStat length])
role = @"no-role";
[classes addObject: role];
/* attendee class */
if ([[attendee delegatedFrom] length] > 0)
[classes addObject: @"delegate"];
/* current attendee class */
co = [self clientObject];
ownerUser = [SOGoUser userWithLogin: [co ownerInContext: context]];
ownerAttendee = [component userAsAttendee: ownerUser];
if (attendee == ownerAttendee)
[classes addObject: @"attendeeUser"];
return [classes componentsJoinedByString: @" "];
}
- (NSString *) ownerLogin
{
return [[self clientObject] ownerInContext: context];
} }
@end @end

View file

@ -462,6 +462,7 @@ validate_endbeforestart = "Mae'r dyddiad gorffen sydd wedi'i roi yn digwydd c
/* Legend */ /* Legend */
"Required participant" = "Cyfranogwr gofynnol"; "Required participant" = "Cyfranogwr gofynnol";
"Optional participant" = "Cyfranogwr opsiynol"; "Optional participant" = "Cyfranogwr opsiynol";
"Non Participant" = "Non Participant";
"Chair" = "Cadair"; "Chair" = "Cadair";
"Needs action" = "Angen gweithred"; "Needs action" = "Angen gweithred";

View file

@ -235,6 +235,16 @@
pageName = "UIxAppointmentEditor"; pageName = "UIxAppointmentEditor";
actionName = "delegate"; actionName = "delegate";
}; };
tentative = {
protectedBy = "RespondToComponent";
pageName = "UIxAppointmentEditor";
actionName = "tentative";
};
needsaction = {
protectedBy = "RespondToComponent";
pageName = "UIxAppointmentEditor";
actionName = "needsAction";
};
adjust = { adjust = {
protectedBy = "ModifyComponent"; protectedBy = "ModifyComponent";
actionClass = "UIxAppointmentActions"; actionClass = "UIxAppointmentActions";

View file

@ -45,6 +45,7 @@
<var:if condition="hasCalendarAccess"> <var:if condition="hasCalendarAccess">
<div class="uix_ical_toolbar" id="iCalendarToolbar"> <div class="uix_ical_toolbar" id="iCalendarToolbar">
<p> <p>
<var:if condition="currentUserAttendee.rsvp" const:value="true">
<var:if condition="currentUserAttendee.partStatWithDefault" <var:if condition="currentUserAttendee.partStatWithDefault"
const:value="ACCEPTED" const:negate="YES"> const:value="ACCEPTED" const:negate="YES">
<a href="#" class="button actionButton" id="iCalendarAccept"> <a href="#" class="button actionButton" id="iCalendarAccept">
@ -56,6 +57,11 @@
<span><var:string label:value="Decline" /></span></a> <span><var:string label:value="Decline" /></span></a>
</var:if> </var:if>
<var:if condition="currentUserAttendee.partStatWithDefault" <var:if condition="currentUserAttendee.partStatWithDefault"
const:value="TENTATIVE" const:negate="YES">
<a href="#" class="button actionButton" id="iCalendarTentative">
<span><var:string label:value="Tentative" /></span></a>
</var:if>
<var:if condition="currentUserAttendee.partStatWithDefault"
const:value="DELEGATED" const:negate="YES"> const:value="DELEGATED" const:negate="YES">
<a href="#" class="button actionButton" id="editDelegate"> <a href="#" class="button actionButton" id="editDelegate">
<span><var:string label:value="Delegate ..." /></span></a> <span><var:string label:value="Delegate ..." /></span></a>
@ -76,6 +82,7 @@
<span><var:string label:value="OK" /></span></a> <span><var:string label:value="OK" /></span></a>
</span> </span>
</var:if> </var:if>
</var:if>
<var:if condition="isEventStoredInCalendar" const:negate="YES"> <var:if condition="isEventStoredInCalendar" const:negate="YES">
<a href="#" class="button actionButton" id="iCalendarAddToCalendar"> <a href="#" class="button actionButton" id="iCalendarAddToCalendar">
<span><var:string label:value="Add to calendar" /></span></a> <span><var:string label:value="Add to calendar" /></span></a>
@ -223,7 +230,9 @@
<td valign="top"><var:string label:value="Attendees"/>:</td> <td valign="top"><var:string label:value="Attendees"/>:</td>
<td id="iCalAttendees"> <td id="iCalAttendees">
<var:foreach list="authorativeEvent.participants" item="attendee"> <var:foreach list="authorativeEvent.participants" item="attendee">
<var:if condition="attendee.delegatedTo" const:negate="YES"><!-- don't show attendees that delegated the invitation --><span var:class="currentAttendeeClass"><a var:href="attendee.email"> <var:if condition="attendee.delegatedTo" const:negate="YES"><!-- don't show attendees that delegated the invitation --><span var:class="currentAttendeeClass"
><div class="status-icon"><!-- space --></div
><a var:href="attendee.email">
<var:string value="attendeeForDisplay"/></a> <var:string value="attendeeForDisplay"/></a>
(<var:string label:value="$attendee.partStatWithDefault" /><var:if condition="attendee.delegatedTo"> <var:string label:value="to" /> <var:string value="attendee.delegatedTo.rfc822Email" /></var:if><var:if condition="attendee.delegatedFrom">, <var:string label:value="delegated from" /> <var:string value="attendee.delegatedFrom.rfc822Email" /></var:if>)</span> (<var:string label:value="$attendee.partStatWithDefault" /><var:if condition="attendee.delegatedTo"> <var:string label:value="to" /> <var:string value="attendee.delegatedTo.rfc822Email" /></var:if><var:if condition="attendee.delegatedFrom">, <var:string label:value="delegated from" /> <var:string value="attendee.delegatedFrom.rfc822Email" /></var:if>)</span>
<br /></var:if> <br /></var:if>

View file

@ -22,29 +22,23 @@
<div id="freeBusyViewButtons"> <div id="freeBusyViewButtons">
<var:string label:value="Suggest time slot:"/> <var:string label:value="Suggest time slot:"/>
<span class="buttons"><a id="nextSlot" href="#" class="button" <span class="buttons"><a id="nextSlot" href="#" class="button"
><span><var:string label:value="Next slot" /></span></a> ><span><var:string label:value="Next slot" /></span></a>
<a id="previousSlot" href="#" class="button" <a id="previousSlot" href="#" class="button"
><span><var:string label:value="Previous slot" /></span></a></span> ><span><var:string label:value="Previous slot" /></span></a></span>
<input class="checkbox" type="checkbox" <input class="checkbox" type="checkbox"
checked="1" id="onlyOfficeHours" /><var:string label:value="Only office hours" /> checked="1" id="onlyOfficeHours" /><var:string label:value="Only office hours" />
</div> </div>
<div id="freeBusyView"> <div id="freeBusyView">
<table id="freeBusy" cellspacing="0" cellpadding="0" <table id="freeBusy" cellspacing="0" cellpadding="0">
xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:uix="OGo:uix"
xmlns:rsrc="OGo:url"
xmlns:label="OGo:label">
<thead> <thead>
<tr> <tr>
<td><!--space --></td> <td><!--space --></td>
<td class="freeBusyHeader"> <td class="freeBusyHeader">
<div><table id="freeBusyHeader" cellspacing="0" cellpadding="0"> <div><table id="freeBusyHeader" cellspacing="0" cellpadding="0">
<tr class="freeBusyHeader1"><!--space --></tr> <tr class="freeBusyHeader1"><!--space --></tr>
<tr class="freeBusyHeader2"><!--space --></tr> <tr class="freeBusyHeader2"><!--space --></tr>
<tr id="currentEventPosition" class="freeBusyHeader3"><!--space --></tr> <tr id="currentEventPosition" class="freeBusyHeader3"><!--space --></tr>
</table></div> </table></div>
</td> </td>
</tr> </tr>
</thead> </thead>
@ -52,25 +46,28 @@
<tr> <tr>
<td class="freeBusyAttendees"> <td class="freeBusyAttendees">
<div><table id="freeBusyAttendees" cellspacing="0" cellpadding="0"> <div><table id="freeBusyAttendees" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr class="futureAttendee" <tr class="futureAttendee"
><td class="attendees"><a href="#" class="button" ><td class="attendeeStatus"><div><!-- space --></div></td
readonly="readonly"><span ><td class="attendees"><a href="#" class="button"
><var:string label:value="newAttendee" /></span></a></td readonly="readonly"><span
></tr> ><var:string label:value="newAttendee" /></span></a></td
<tr class="attendeeModel" ></tr>
><td class="attendees"><input type="text" class="textField" /></td <tr class="attendeeModel"
></tr> ><td class="attendeeStatus"><div><!-- space --></div></td
</tbody> ><td class="attendees"
</table></div> ><input type="text" class="textField" /></td
></tr>
</tbody>
</table></div>
</td> </td>
<td class="freeBusyData"> <td class="freeBusyData">
<div><table id="freeBusyData" cellspacing="0" cellpadding="0"> <div><table id="freeBusyData" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr class="futureData"></tr> <tr class="futureData"></tr>
<tr class="dataModel"></tr> <tr class="dataModel"></tr>
</tbody> </tbody>
</table></div> </table></div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -78,27 +75,38 @@
</div> </div>
<div id="freeBusyFooter"> <div id="freeBusyFooter">
<div id="legend" onmousedown="return false;"> <div id="legend" onmousedown="return false;">
<ul> <ul class="roles-legend">
<li role="req-participant"><span class="role-icon"><!-- space --></span
><var:string label:value="Participant"/></li>
<li role="opt-participant"><span class="role-icon"><!-- space --></span
><var:string label:value="Optional Participant"/></li>
<li role="non-participant"><span class="role-icon"><!-- space --></span
><var:string label:value="Non Participant"/></li>
<li role="chair"><span class="role-icon"><!-- space --></span
><var:string label:value="Chair"/></li>
</ul>
<ul class="freebusy-legend">
<li><span class="colorBox free"><!-- spacer --></span <li><span class="colorBox free"><!-- spacer --></span
><var:string label:value="Free" /></li> ><var:string label:value="Free" /></li>
<li><span class="colorBox busy"><!-- spacer --></span <li><span class="colorBox busy"><!-- spacer --></span
><var:string label:value="Busy" /></li> ><var:string label:value="Busy" /></li>
<!-- li><span class="colorBox maybe-busy">\- spacer -\->/span --> <!-- li><span class="colorBox maybe-busy">\- spacer -\->/span -->
<!-- >var:string label:value="Maybe busy" />/li> --> <!-- >var:string label:value="Maybe busy" />/li> -->
<li><span class="colorBox noFreeBusy"><!-- spacer --></span <li><span class="colorBox noFreeBusy"><!-- spacer --></span
><var:string label:value="No free-busy information" /></li> ><var:string label:value="No free-busy information" /></li>
</ul> </ul>
</div> </div>
<div id="freeBusyReplicas"> <div id="freeBusyReplicas">
<div><span><var:string label:value="Start:" <div><span><var:string label:value="Start:"
/></span><var:component className="UIxTimeDateControl" /></span><var:component className="UIxTimeDateControl"
const:controlID="startTime" const:controlID="startTime"
date="aptStartDate" date="aptStartDate"
const:dayStartHour="0" const:dayStartHour="0"
const:dayEndHour="23" const:dayEndHour="23"
/></div> /></div>
<div><span><var:string label:value="End:" <div><span><var:string label:value="End:"
/></span><var:component className="UIxTimeDateControl" /></span><var:component className="UIxTimeDateControl"
const:controlID="endTime" const:controlID="endTime"
date="aptEndDate" date="aptEndDate"
const:dayStartHour="0" const:dayStartHour="0"
@ -108,9 +116,9 @@
<div id="windowButtons"> <div id="windowButtons">
<hr /> <hr />
<a id="okButton" href="#" class="button actionButton" <a id="okButton" href="#" class="button actionButton"
><span><var:string label:value="OK"/></span></a> ><span><var:string label:value="OK"/></span></a>
<a id="cancelButton" href="#" class="button" <a id="cancelButton" href="#" class="button"
><span><var:string label:value="Cancel"/></span></a> ><span><var:string label:value="Cancel"/></span></a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -19,7 +19,8 @@
var activeComponent = '<var:if condition="isChildOccurence"><var:string value="clientObject.container.nameInContainer"/>/</var:if><var:string value="clientObject.nameInContainer"/>'; var activeComponent = '<var:if condition="isChildOccurence"><var:string value="clientObject.container.nameInContainer"/>/</var:if><var:string value="clientObject.nameInContainer"/>';
var readOnly = <var:if condition="eventIsReadOnly">true</var:if><var:if condition="eventIsReadOnly" var readOnly = <var:if condition="eventIsReadOnly">true</var:if><var:if condition="eventIsReadOnly"
const:negate="YES">false</var:if>; const:negate="YES">false</var:if>;
var attendees = <var:string value="jsonAttendees" const:escapeHTML="NO" />; var attendees = <var:string value="jsonAttendees" const:escapeHTML="NO"/>;
var ownerLogin = '<var:string value="ownerLogin"/>';
</script> </script>
<var:if condition="eventIsReadOnly" const:negate="YES"> <var:if condition="eventIsReadOnly" const:negate="YES">
@ -203,11 +204,9 @@
<span class="content"><var:string value="organizerName"/></span> <span class="content"><var:string value="organizerName"/></span>
</label> </label>
</var:if> </var:if>
<var:if condition="userIsAttendee"> <var:if condition="userHasRSVP">
<label><var:string label:value="Reply:" /> <label><var:string label:value="Reply:" />
<span class="content"><var:popup list="replyList" item="item" <span class="content"><var:popup list="replyList" item="item"
const:disabledValue="-"
label:noSelectionString="partStat_NEEDS-ACTION"
const:name="replyList" const:name="replyList"
const:id="replyList" const:id="replyList"
string="itemReplyText" string="itemReplyText"
@ -251,8 +250,17 @@
</label> </label>
<label id="attendeesLabel"> <label id="attendeesLabel">
<span class="content"><div id="attendeesMenu" class="fakeTextArea"> <span class="content"><div id="attendeesMenu" class="fakeTextArea">
<var:foreach list="component.participants" item="attendee"> <var:foreach list="component.attendees" item="attendee"
<var:if condition="attendee.delegatedTo" const:negate="YES"><div var:class="attendee.partStatWithDefault.lowercaseString"><a var:href="attendee.email" var:email="attendee.email.rfc822Email"><var:string value="attendeeForDisplay" /></a><var:if condition="attendee.delegatedFrom"> (<var:string label:value="delegated from" /> <var:string value="attendee.delegatedFrom.rfc822Email" />)</var:if></div></var:if> ><var:if condition="attendee.delegatedTo" const:negate="YES"
><div var:class="currentAttendeeClasses"
><div const:class="statusIcon"><!-- space --></div
><a var:href="attendee.email" var:email="attendee.email.rfc822Email"
><var:string value="attendeeForDisplay" /></a
><var:if condition="attendee.delegatedFrom"
> (<var:string label:value="delegated from" />
<var:string value="attendee.delegatedFrom.rfc822Email" />)</var:if
></div
></var:if>
</var:foreach> </var:foreach>
</div></span> </div></span>
</label> </label>
@ -331,7 +339,7 @@
var:value="reminderReference"/> var:value="reminderReference"/>
<div id="windowButtons"> <div id="windowButtons">
<var:if condition="userIsAttendee"><a id="okButton" href="#" class="button actionButton" <var:if condition="userHasRSVP"><a id="okButton" href="#" class="button actionButton"
><span><var:string label:value="OK"/></span></a></var:if> ><span><var:string label:value="OK"/></span></a></var:if>
<a id="cancelButton" href="#" class="button" <a id="cancelButton" href="#" class="button"
><span><var:string label:value="Cancel"/></span></a> ><span><var:string label:value="Cancel"/></span></a>

View file

@ -796,22 +796,34 @@ TR#messageCountHeader TD
{ padding: 0; } { padding: 0; }
#iCalAttendees SPAN #iCalAttendees SPAN
{ line-height: 19px; }
#iCalAttendees DIV.status-icon
{ background-repeat: no-repeat; { background-repeat: no-repeat;
background-position: 5px 1px; float: left;
padding-left: 22px; padding: 0px;
padding-right: 4px; } clear: both;
width: 12px;
height: 18px;
margin-top: 1px;
margin-left: 4px;
margin-right: 4px;
background-image: url("attendee-partstats.png"); }
#iCalAttendees .accepted #iCalAttendees .accepted DIV.status-icon
{ background-image: url("accepted.png"); } { background-position: 0px 0px; }
#iCalAttendees .needs-action #iCalAttendees .declined DIV.status-icon
{ background-image: url("needs-action.png"); } { background-position: -12px 0px; }
#iCalAttendees .declined #iCalAttendees .needs-action DIV.status-icon
{ background-image: url("declined.png"); } { background-position: -24px 0px; }
#iCalAttendees .delegated #iCalAttendees .tentative DIV.status-icon
{ background-image: url("delegated.png"); } { background-position: -36px 0px; }
#iCalAttendees .delegated DIV.status-icon
{ background-position: -48px 0px; }
#iCalAttendees .attendeeUser, #iCalAttendees .attendeeUser,
#iCalAttendees .attendeeUser A #iCalAttendees .attendeeUser A

View file

@ -1330,18 +1330,23 @@ DIV.event.alarm DIV.text
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: top right; } background-position: top right; }
DIV.eventInside.tentative,
DIV.eventInside.needs-action DIV.eventInside.needs-action
{ border: 1px dotted #666; { -moz-opacity: 0.7;
-moz-opacity: 0.6;
opacity: 0.7; } opacity: 0.7; }
DIV.eventInside.needs-action
{ border: 2px dotted #000; }
DIV.eventInside.tentative DIV.text,
DIV.eventInside.needs-action DIV.text DIV.eventInside.needs-action DIV.text
{ top: 0px; { top: 0px;
left: 0px; } left: 2px; }
DIV.eventInside.delegated,
DIV.eventInside.declined DIV.eventInside.declined
{ -moz-opacity: 0.3; { -moz-opacity: 0.4;
opacity: 0.3; } opacity: 0.4; }
/* event DnD */ /* event DnD */
DIV.event DIV.topDragGrip, DIV.event DIV.topDragGrip,

View file

@ -25,7 +25,7 @@ var calendarEvents = null;
var preventAutoScroll = false; var preventAutoScroll = false;
var userStates = [ "needs-action", "accepted", "declined", "tentative" ]; var userStates = [ "needs-action", "accepted", "declined", "tentative", "delegated" ];
var calendarHeaderAdjusted = false; var calendarHeaderAdjusted = false;
@ -297,7 +297,7 @@ function closeInvitationWindow() {
function modifyEventCallback(http) { function modifyEventCallback(http) {
if (http.readyState == 4) { if (http.readyState == 4) {
if (http.status == 200) { if (isHttpStatus204(http.status) || http.status == 200) {
var mailInvitation = queryParameters["mail-invitation"]; var mailInvitation = queryParameters["mail-invitation"];
if (mailInvitation && mailInvitation.toLowerCase() == "yes") if (mailInvitation && mailInvitation.toLowerCase() == "yes")
closeInvitationWindow(); closeInvitationWindow();

View file

@ -122,42 +122,53 @@ A#attendeesHref
text-decoration: underline; } text-decoration: underline; }
DIV#attendeesMenu LI DIV#attendeesMenu LI
{ padding-left: 10px; } { padding-left: 10px;
width: auto; }
DIV#attendeesMenu .attendee, DIV#attendeesMenu .attendeeUser
DIV#attendeesMenu DIV { font-weight: bold; }
DIV#attendeesMenu .opt-participant
{ font-style: italic; }
DIV#attendeesMenu .non-participant A
{ color: #888; }
#attendeesLabel DIV#attendeesMenu > DIV
{ margin-left: 4px; }
#attendeesLabel DIV#attendeesMenu > DIV,
DIV#attendeesMenu .attendee
{ height: 18px; }
#attendeesLabel DIV#attendeesMenu .statusIcon
{ margin-top: 4px; }
DIV#attendeesMenu .statusIcon
{ background-repeat: no-repeat; { background-repeat: no-repeat;
background-position: 5px center; float: left;
padding-left: 22px; } width: 12px;
height: 14px;
margin-right: 4px;
background-image: url("attendee-partstats.png"); }
DIV#attendeesMenu .accepted DIV#attendeesMenu .accepted .statusIcon
{ background-image: url("accepted.png"); } { background-position: 0px 0px; }
DIV#attendeesMenu .accepted:hover DIV#attendeesMenu .declined .statusIcon
{ background-image: url("accepted.selected.png"); } { background-position: -12px 0px; }
DIV#attendeesMenu .needs-action DIV#attendeesMenu .needs-action .statusIcon
{ background-image: url("needs-action.png"); } { background-position: -24px 0px; }
DIV#attendeesMenu .needs-action:hover DIV#attendeesMenu .tentative .statusIcon
{ background-image: url("needs-action.selected.png"); } { background-position: -36px 0px; }
DIV#attendeesMenu .declined DIV#attendeesMenu .delegated .statusIcon
{ background-image: url("declined.png"); } { background-position: -48px 0px; }
DIV#attendeesMenu .declined:hover DIV#attendeesMenu .no-partstat .statusIcon
{ background-image: url("declined.selected.png"); } { background-position: -60px 0px; }
DIV#attendeesMenu .delegated
{ background-image: url("delegated.png"); }
DIV#attendeesMenu .delegated:hover
{ background-image: url("delegated.selected.png"); }
DIV#attendeesMenu .delegate DIV#attendeesMenu .delegate
{ background-position: 15px center; { padding-left: 16px !important; }
padding-left: 32px; }
/* read-only view */
DIV#attendeesMenu DIV
{ padding-left: 20px; }

View file

@ -164,7 +164,7 @@ function addContact(tag, fullContactName, contactId, contactName, contactEmail)
function saveEvent(sender) { function saveEvent(sender) {
if (validateAptEditor()) { if (validateAptEditor()) {
document.forms['editform'].attendees.value = attendees.toJSON(); document.forms['editform'].attendees.value = $(attendees).toJSON();
document.forms['editform'].submit(); document.forms['editform'].submit();
} }
@ -298,7 +298,7 @@ function initTimeWidgets(widgets) {
} }
} }
function refreshAttendeesRO () { function refreshAttendeesRO() {
var attendeesMenu = $("attendeesMenu"); var attendeesMenu = $("attendeesMenu");
var attendeesLabel = $("attendeesLabel"); var attendeesLabel = $("attendeesLabel");
var attendeesDiv = $("attendeesDiv"); var attendeesDiv = $("attendeesDiv");
@ -323,7 +323,7 @@ function refreshAttendees(newAttendees) {
var attendeesMenu = $("attendeesMenu"); var attendeesMenu = $("attendeesMenu");
if (!attendeesHref) if (!attendeesHref)
return refreshAttendeesRO (); return refreshAttendeesRO();
if (attendeesMenu) if (attendeesMenu)
attendeesMenu = $("attendeesMenu").down("ul"); attendeesMenu = $("attendeesMenu").down("ul");
@ -337,9 +337,10 @@ function refreshAttendees(newAttendees) {
if (menuItems && attendeesMenu) if (menuItems && attendeesMenu)
for (var i = 0; i < menuItems.length; i++) for (var i = 0; i < menuItems.length; i++)
attendeesMenu.removeChild(menuItems[i]); attendeesMenu.removeChild(menuItems[i]);
if (newAttendees) if (newAttendees) {
attendees = $H(newAttendees.evalJSON(true)); attendees = $H(newAttendees.evalJSON());
}
if (attendees.keys().length > 0) { if (attendees.keys().length > 0) {
// Update attendees link and show label // Update attendees link and show label
@ -353,13 +354,13 @@ function refreshAttendees(newAttendees) {
if (attendeesMenu) { if (attendeesMenu) {
var delegatedTo = attendee.get('delegated-to'); var delegatedTo = attendee.get('delegated-to');
if (!attendee.get('delegated-from') || delegatedTo) { if (!attendee.get('delegated-from') || delegatedTo) {
var node = document.createElement("li"); var node = createElement("li");
attendeesMenu.appendChild(node); attendeesMenu.appendChild(node);
setupAttendeeNode(node, attendee); setupAttendeeNode(node, attendee);
} }
if (delegatedTo) { if (delegatedTo) {
var delegate = attendees.get(delegatedTo); var delegate = attendees.get(delegatedTo);
var node = document.createElement("li"); var node = createElement("li");
attendeesMenu.appendChild(node); attendeesMenu.appendChild(node);
setupAttendeeNode(node, $H(delegate), true); setupAttendeeNode(node, $H(delegate), true);
} }
@ -384,13 +385,18 @@ function setupAttendeeNode(aNode, aAttendee, isDelegate) {
// name = email; // name = email;
name = name || email; name = name || email;
$(aNode).writeAttribute("email", email); aNode.writeAttribute("email", email);
$(aNode).addClassName("attendee"); aNode.addClassName("attendee");
$(aNode).addClassName(aAttendee.get('partstat')); var partstat = aAttendee.get('partstat');
if (!partstat)
partstat = "no-partstat";
aNode.addClassName(partstat);
if (isDelegate) if (isDelegate)
$(aNode).addClassName("delegate"); aNode.addClassName("delegate");
var statusIconNode = createElement("div", null, "statusIcon");
aNode.appendChild(statusIconNode);
aNode.appendChild(document.createTextNode(name)); aNode.appendChild(document.createTextNode(name));
$(aNode).observe("click", onMailTo); aNode.observe("click", onMailTo);
} }
function initializeAttendeesHref() { function initializeAttendeesHref() {
@ -410,8 +416,8 @@ function onAttendeesHrefClick(event) {
} }
function onMailTo(event) { function onMailTo(event) {
var target = getTarget(event); var target = $(getTarget(event));
var address = target.firstChild.nodeValue.trim() + " <" + target.readAttribute("email") + ">"; var address = target.lastChild.nodeValue.trim() + " <" + target.readAttribute("email") + ">";
openMailTo(address); openMailTo(address);
Event.stop(event); Event.stop(event);
return false; return false;

View file

@ -48,20 +48,62 @@ TABLE#freeBusy TD.freeBusyAttendees DIV
TABLE#freeBusy TD.freeBusyData DIV TABLE#freeBusy TD.freeBusyData DIV
{ overflow: scroll; } { overflow: scroll; }
TABLE#freeBusyAttendees TR.needs-action TD.attendees TABLE#freeBusyAttendees TD.attendeeStatus
{ background-image: url("needs-action.png"); { width: 24px;
background-repeat: no-repeat; min-width: 24px;
background-position: 5px center; } max-width: 24px; }
TABLE#freeBusyAttendees TR.declined TD.attendees TABLE#freeBusyAttendees TD.attendeeStatus DIV
{ background-image: url("declined.png"); { width: 12px;
background-repeat: no-repeat; min-width: 12px;
background-position: 5px center; } max-width: 24px;
background-image: none; }
TABLE#freeBusyAttendees TR.accepted TD.attendees UL.roles-legend SPAN.role-icon
{ background-image: url("accepted.png"); { display: block;
background-repeat: no-repeat; float: left; }
background-position: 5px center; }
UL.roles-legend SPAN.role-icon,
TABLE#freeBusyAttendees TR.attendee-row TD.attendeeStatus DIV
{ background-repeat: no-repeat;
width: 24px;
height: 20px;
background-image: url("attendee-roles.png"); }
LI[role="req-participant"] > SPAN.role-icon,
TABLE#freeBusyAttendees TR[role="req-participant"].attendee-row TD.attendeeStatus DIV
{ background-position: 0px 4px; }
LI[role="opt-participant"] > SPAN.role-icon,
TABLE#freeBusyAttendees TR[role="opt-participant"].attendee-row TD.attendeeStatus DIV
{ background-position: -24px 4px; }
LI[role="non-participant"] > SPAN.role-icon,
TABLE#freeBusyAttendees TR[role="non-participant"].attendee-row TD.attendeeStatus DIV
{ background-position: -72px 4px; }
LI[role="chair"] > SPAN.role-icon,
TABLE#freeBusyAttendees TR[role="chair"].attendee-row TD.attendeeStatus DIV
{ background-position: -48px 4px; }
TABLE#freeBusyAttendees TR.organizer-row TD.attendeeStatus DIV
{ background-repeat: no-repeat;
width: 12px;
height: 18px;
margin-left: 6px;
background-image: url("attendee-partstats.png"); }
TABLE#freeBusyAttendees TR[partstat="accepted"].organizer-row TD.attendeeStatus DIV
{ background-position: 0px 4px; }
TABLE#freeBusyAttendees TR[partstat="declined"].organizer-row TD.attendeeStatus DIV
{ background-position: -12px 4px; }
TABLE#freeBusyAttendees TR[partstat="needs-action"].organizer-row TD.attendeeStatus DIV
{ background-position: -24px 4px; }
TABLE#freeBusyAttendees TR[partstat="tentative"].organizer-row TD.attendeeStatus DIV
{ background-position: -36px 4px; }
TABLE#freeBusyHeader TR.freeBusyHeader2 TH TABLE#freeBusyHeader TR.freeBusyHeader2 TH
{ font-weight: normal; } { font-weight: normal; }
@ -112,8 +154,7 @@ TABLE#freeBusyAttendees TD.attendees INPUT
background-position: 4px center; background-position: 4px center;
border: 0px; border: 0px;
width: 12em; width: 12em;
padding-left: 24px; padding-left: 24px; }
margin-left: 2em; }
TABLE#freeBusyAttendees TR.futureAttendee INPUT TABLE#freeBusyAttendees TR.futureAttendee INPUT
{ background-image: none; { background-image: none;
@ -124,9 +165,6 @@ TABLE#freeBusyData TR.futureData TD
{ border: 0; { border: 0;
line-height: 3em; } line-height: 3em; }
TABLE#freeBusyAttendees TR.futureAttendee TD A
{ margin-left: 20px; }
SPAN.freeBusyZoneElement SPAN.freeBusyZoneElement
{ display: block; { display: block;
float: left; float: left;
@ -165,32 +203,29 @@ DIV#legend
DIV#legend UL DIV#legend UL
{ cursor: default; { cursor: default;
float: left; float: left;
width: 30%;
margin: 0px; margin: 0px;
margin-right: 10px;
padding: 0px; padding: 0px;
line-height: 1.5em;
list-style-type: none; list-style-type: none;
list-style-image: none; } list-style-image: none; }
DIV#legend UL LI DIV#legend LI
{ white-space: nowrap; { height: 20px;
white-space: nowrap;
margin: 0px; margin: 0px;
padding: 0px; } padding: 0px; }
DIV#legend UL IMG UL.freebusy-legend SPAN.colorBox
{ margin-right: .5em; }
DIV#legend UL LI SPAN.colorBox
{ float: left; { float: left;
margin-right: .5em; } margin-top: 5px;
margin-right: 2px;
SPAN.colorBox display: block;
{ display: block; border: 1px solid #999;
float: right; border-left: 1px solid #ccc;
border: 1px solid #333; border-right: 1px solid #ccc;
margin: .12em; border-bottom: 1px solid #eee;
width: 1em; width: 32px;
height: .75em; } height: 10px; }
SPAN.colorBox.busy, SPAN.colorBox.busy,
SPAN.freeBusyZoneElement.busy SPAN.freeBusyZoneElement.busy

View file

@ -1,8 +1,9 @@
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
var OwnerLogin = "";
var resultsDiv; var resultsDiv;
var address; var address;
var awaitingFreeBusyRequests = new Array();
var additionalDays = 2; var additionalDays = 2;
var isAllDay = parent$("isAllDay").checked + 0; var isAllDay = parent$("isAllDay").checked + 0;
@ -11,12 +12,9 @@ var displayEndHour = 23;
var attendeesEditor = { var attendeesEditor = {
delay: 500, delay: 500,
delayedSearch: false,
currentField: null,
selectedIndex: -1 selectedIndex: -1
}; };
function handleAllDay () { function handleAllDay () {
window.timeWidgets['end']['hour'].value = 17; window.timeWidgets['end']['hour'].value = 17;
window.timeWidgets['end']['minute'].value = 0; window.timeWidgets['end']['minute'].value = 0;
@ -31,67 +29,135 @@ function handleAllDay () {
/* address completion */ /* address completion */
function resolveListAttendees(input, append) {
var urlstr = (UserFolderURL
+ "Contacts/"
+ escape(input.container) + "/"
+ escape(input.cname) + "/properties");
triggerAjaxRequest(urlstr, resolveListAttendeesCallback,
{ "input": input, "append": append });
}
function resolveListAttendeesCallback(http) {
if (http.readyState == 4 && http.status == 200) {
var input = http.callbackData["input"];
var append = http.callbackData["append"];
var contacts = http.responseText.evalJSON(true);
for (var i = 0; i < contacts.length; i++) {
var contact = contacts[i];
var fullName = contact[1];
if (fullName && fullName.length > 0) {
fullName += " <" + contact[2] + ">";
}
else {
fullName = contact[2];
}
input.uid = null;
input.cname = null;
input.container = null;
input.isList = false;
input.value = contact[2];
input.confirmedValue = null;
input.hasfreebusy = false;
input.modified = true;
// input.focussed = true;
// input.activate();
input.checkAfterLookup = true;
performSearch(input);
if (i < (contacts.length - 1)) {
var nextRow = newAttendee(input.parentNode.parentNode);
input = nextRow.down("input");
} else if (append) {
var row = input.parentNode.parentNode;
var tBody = row.parentNode;
if (row.rowIndex == (tBody.rows.length - 3)) {
if (input.selectText) {
input.selectText(0, 0);
} else if (input.createTextRange) {
input.createTextRange().moveStart();
}
newAttendee();
} else {
var nextRow = tBody.rows[row.rowIndex + 1];
var input = nextRow.down("input");
input.selectText(0, input.value.length);
input.focussed = true;
}
} else {
if (input.selectText) {
input.selectText(0, 0);
} else if (input.createTextRange) {
input.createTextRange().moveStart();
}
input.blur();
}
}
}
}
function onContactKeydown(event) { function onContactKeydown(event) {
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
this.focussed = true; this.focussed = true;
return; return;
} }
if (event.keyCode == 9) { // Tab if (event.keyCode == 9 || event.keyCode == 13) { // Tab
preventDefault(event); preventDefault(event);
if (this.confirmedValue) if (this.confirmedValue)
this.value = this.confirmedValue; this.value = this.confirmedValue;
this.hasfreebusy = false; this.hasfreebusy = false;
var row = $(this).up("tr").next(); var row = $(this).up("tr").next();
this.blur(); // triggers checkAttendee function call if (this.isList) {
var input = row.down("input"); resolveListAttendees(this, true);
if (input) { event.stop();
input.focussed = true; } else {
input.activate(); checkAttendee(this);
// this.blur(); // triggers checkAttendee function call
var input = row.down("input");
if (input) {
input.focussed = true;
input.activate();
}
else
newAttendee();
} }
else
newAttendee(null);
} }
else if (event.keyCode == 0 else if (event.keyCode == 0
|| event.keyCode == 8 // Backspace || event.keyCode == 8 // Backspace
|| event.keyCode == 32 // Space || event.keyCode == 32 // Space
|| event.keyCode > 47) { || event.keyCode > 47) {
this.setAttribute("modified", "1"); this.modified = true;
this.confirmedValue = null; this.confirmedValue = null;
this.cname = null;
this.uid = null; this.uid = null;
this.container = null;
this.hasfreebusy = false; this.hasfreebusy = false;
attendeesEditor.currentField = this; if (this.searchTimeout) {
if (this.value.length > 0 && !attendeesEditor.delayedSearch) { window.clearTimeout(this.searchTimeout);
attendeesEditor.delayedSearch = true; }
setTimeout("performSearch()", attendeesEditor.delay); if (this.value.length > 0) {
var thisInput = this;
this.searchTimeout = setTimeout(function()
{performSearch(thisInput);
thisInput = null;},
attendeesEditor.delay);
} }
else if (this.value.length == 0) { else if (this.value.length == 0) {
if (document.currentPopupMenu) if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu); hideMenu(document.currentPopupMenu);
} }
} }
else if (event.keyCode == 13) {
preventDefault(event);
if (this.confirmedValue)
this.value = this.confirmedValue;
$(this).selectText(0, this.value.length);
if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu);
attendeesEditor.selectedIndex = -1;
if (this.uid) {
this.hasfreebusy = false;
this.writeAttribute("modified", "1");
this.blur(); // triggers checkAttendee function call
}
}
else if ($('attendeesMenu').getStyle('visibility') == 'visible') { else if ($('attendeesMenu').getStyle('visibility') == 'visible') {
attendeesEditor.currentField = this;
if (event.keyCode == Event.KEY_UP) { // Up arrow if (event.keyCode == Event.KEY_UP) { // Up arrow
if (attendeesEditor.selectedIndex > 0) { if (attendeesEditor.selectedIndex > 0) {
var attendees = $('attendeesMenu').select("li"); var attendees = $('attendeesMenu').select("li");
attendees[attendeesEditor.selectedIndex--].removeClassName("selected"); attendees[attendeesEditor.selectedIndex--].removeClassName("selected");
attendees[attendeesEditor.selectedIndex].addClassName("selected"); var attendee = attendees[attendeesEditor.selectedIndex];
this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].readAttribute("address"); attendee.addClassName("selected");
this.uid = attendees[attendeesEditor.selectedIndex].uid; this.value = this.confirmedValue = attendee.address;
this.uid = attendee.uid;
this.isList = attendee.isList;
this.cname = attendee.cname;
this.container = attendee.container;
} }
} }
else if (event.keyCode == Event.KEY_DOWN) { // Down arrow else if (event.keyCode == Event.KEY_DOWN) { // Down arrow
@ -100,30 +166,27 @@ function onContactKeydown(event) {
if (attendeesEditor.selectedIndex >= 0) if (attendeesEditor.selectedIndex >= 0)
attendees[attendeesEditor.selectedIndex].removeClassName("selected"); attendees[attendeesEditor.selectedIndex].removeClassName("selected");
attendeesEditor.selectedIndex++; attendeesEditor.selectedIndex++;
attendees[attendeesEditor.selectedIndex].addClassName("selected"); var attendee = attendees[attendeesEditor.selectedIndex];
this.value = this.confirmedValue = attendees[attendeesEditor.selectedIndex].readAttribute("address"); attendee.addClassName("selected");
this.uid = attendees[attendeesEditor.selectedIndex].uid; this.value = this.confirmedValue = attendee.address;
this.isList = attendee.isList;
this.uid = attendee.uid;
this.cname = attendee.cname;
this.container = attendee.container;
} }
} }
} }
} }
function performSearch() { function performSearch(input) {
// Perform address completion // Perform address completion
if (attendeesEditor.currentField) { if (input.value.trim().length > 0) {
if (document.contactLookupAjaxRequest) { var urlstr = (UserFolderURL
// Abort any pending request + "Contacts/allContactSearch?excludeGroups=1&search="
document.contactLookupAjaxRequest.aborted = true; + escape(input.value));
document.contactLookupAjaxRequest.abort(); triggerAjaxRequest(urlstr, performSearchCallback, input);
}
if (attendeesEditor.currentField.value.trim().length > 0) {
var urlstr = ( UserFolderURL + "Contacts/contactSearch?search="
+ escape(attendeesEditor.currentField.value) );
document.contactLookupAjaxRequest =
triggerAjaxRequest(urlstr, performSearchCallback, attendeesEditor.currentField);
}
} }
attendeesEditor.delayedSearch = false; input.searchTimeout = null;
} }
function performSearchCallback(http) { function performSearchCallback(http) {
@ -138,6 +201,7 @@ function performSearchCallback(http) {
var data = http.responseText.evalJSON(true); var data = http.responseText.evalJSON(true);
if (data.contacts.length > 1) { if (data.contacts.length > 1) {
list.input = input;
$(list.childNodesWithTag("li")).each(function(item) { $(list.childNodesWithTag("li")).each(function(item) {
item.remove(); item.remove();
}); });
@ -145,25 +209,42 @@ function performSearchCallback(http) {
// Populate popup menu // Populate popup menu
for (var i = 0; i < data.contacts.length; i++) { for (var i = 0; i < data.contacts.length; i++) {
var contact = data.contacts[i]; var contact = data.contacts[i];
var completeEmail = contact["name"] + " <" + contact["email"] + ">"; var isList = (contact["c_component"] &&
var node = new Element('li', { 'address': completeEmail }); contact["c_component"] == "vlist");
var completeEmail = contact["c_cn"].trim();
if (!isList) {
if (completeEmail)
completeEmail += " <" + contact["c_mail"] + ">";
else
completeEmail = contact["c_mail"];
}
var node = createElement('li');
list.appendChild(node);
node.address = completeEmail;
log("node.address: " + node.address);
node.uid = contact["c_uid"];
node.isList = isList;
if (isList) {
node.cname = contact["c_name"];
node.container = contact["container"];
}
var matchPosition = completeEmail.toLowerCase().indexOf(data.searchText.toLowerCase()); var matchPosition = completeEmail.toLowerCase().indexOf(data.searchText.toLowerCase());
var matchBefore = completeEmail.substring(0, matchPosition); var matchBefore = completeEmail.substring(0, matchPosition);
var matchText = completeEmail.substring(matchPosition, matchPosition + data.searchText.length); var matchText = completeEmail.substring(matchPosition, matchPosition + data.searchText.length);
var matchAfter = completeEmail.substring(matchPosition + data.searchText.length); var matchAfter = completeEmail.substring(matchPosition + data.searchText.length);
list.appendChild(node);
node.uid = contact["uid"];
node.appendChild(document.createTextNode(matchBefore)); node.appendChild(document.createTextNode(matchBefore));
node.appendChild(new Element('strong').update(matchText)); node.appendChild(new Element('strong').update(matchText));
node.appendChild(document.createTextNode(matchAfter)); node.appendChild(document.createTextNode(matchAfter));
if (contact["contactInfo"]) if (contact["contactInfo"])
node.appendChild(document.createTextNode(" (" + contact["contactInfo"] + ")")); node.appendChild(document.createTextNode(" (" +
$(node).observe("mousedown", onAttendeeResultClick); contact["contactInfo"] + ")"));
node.observe("mousedown",
onAttendeeResultClick.bindAsEventListener(node));
} }
// Show popup menu // Show popup menu
var offsetScroll = Element.cumulativeScrollOffset(attendeesEditor.currentField); var offsetScroll = Element.cumulativeScrollOffset(input);
var offset = Element.cumulativeOffset(attendeesEditor.currentField); var offset = Element.cumulativeOffset(input);
var top = offset[1] - offsetScroll[1] + node.offsetHeight + 3; var top = offset[1] - offsetScroll[1] + node.offsetHeight + 3;
var height = 'auto'; var height = 'auto';
var heightDiff = window.height() - offset[1]; var heightDiff = window.height() - offset[1];
@ -189,37 +270,73 @@ function performSearchCallback(http) {
if (data.contacts.length == 1) { if (data.contacts.length == 1) {
// Single result // Single result
var contact = data.contacts[0]; var contact = data.contacts[0];
if (contact["uid"].length > 0) input.uid = contact["c_uid"];
input.uid = contact["uid"]; var row = $(input.parentNode.parentNode);
var completeEmail = contact["name"] + " <" + contact["email"] + ">"; if (input.uid == OwnerLogin) {
if (contact["name"].substring(0, input.value.length).toUpperCase() row.removeAttribute("role");
== input.value.toUpperCase()) row.setAttribute("partstat", "accepted");
row.addClassName("organizer-row");
row.removeClassName("attendee-row");
row.isOrganizer = true;
} else {
row.removeAttribute("partstat");
row.setAttribute("role", "req-participant");
row.addClassName("attendee-row");
row.removeClassName("organizer-row");
row.isOrganizer = false;
}
var isList = (contact["c_component"] &&
contact["c_component"] == "vlist");
if (isList) {
input.cname = contact["c_name"];
input.container = contact["container"];
}
var completeEmail = contact["c_cn"].trim();
if (!isList) {
if (completeEmail)
completeEmail += " <" + contact["c_mail"] + ">";
else
completeEmail = contact["c_mail"];
}
if ((input.value == contact["c_mail"])
|| (contact["c_cn"].substring(0, input.value.length).toUpperCase()
== input.value.toUpperCase())) {
input.value = completeEmail; input.value = completeEmail;
}
else else
// The result matches email address, not user name // The result matches email address, not user name
input.value += ' >> ' + completeEmail; input.value += ' >> ' + completeEmail;
input.isList = isList;
input.confirmedValue = completeEmail; input.confirmedValue = completeEmail;
var end = input.value.length; var end = input.value.length;
$(input).selectText(start, end); $(input).selectText(start, end);
attendeesEditor.selectedIndex = -1; attendeesEditor.selectedIndex = -1;
if (input.checkAfterLookup) {
input.checkAfterLookup = false;
input.modified = true;
input.hasfreebusy = false;
checkAttendee(input);
}
} }
} }
} }
else else
if (document.currentPopupMenu) if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu); hideMenu(document.currentPopupMenu);
document.contactLookupAjaxRequest = null;
} }
} }
function onAttendeeResultClick(event) { function onAttendeeResultClick(event) {
if (attendeesEditor.currentField) { var input = this.parentNode.input;
attendeesEditor.currentField.uid = this.uid; input.uid = this.uid;
attendeesEditor.currentField.value = $(this).readAttribute("address"); input.cname = this.cname;
attendeesEditor.currentField.confirmedValue = attendeesEditor.currentField.value; input.container = this.container;
attendeesEditor.currentField.blur(); // triggers checkAttendee function call input.isList = this.isList;
} input.confirmedValue = input.value = this.address;
checkAttendee(input);
this.parentNode.input = null;
} }
function resetFreeBusyZone() { function resetFreeBusyZone() {
@ -284,19 +401,66 @@ function redisplayFreeBusyZone() {
scrollToEvent(); scrollToEvent();
} }
function newAttendee(event) { function onAttendeeStatusClick(event) {
rotateAttendeeStatus(this);
}
function rotateAttendeeStatus(row) {
var values;
var attributeName;
if (row.isOrganizer) {
values = [ "accepted", "declined", "tentative", "needs-action" ];
attributeName = "partstat";
} else {
values = [ "req-participant", "opt-participant",
"chair", "non-participant" ];
attributeName = "role";
}
var value = row.getAttribute(attributeName);
var idx = (value ? values.indexOf(value) : -1);
if (idx == -1 || idx > (values.length - 2)) {
idx = 0;
} else {
idx++;
}
row.setAttribute(attributeName, values[idx]);
if (Prototype.Browser.IE) {
/* This hack enables a refresh of the row element right after the
click. Otherwise, this occurs only when leaving the element with
them mouse cursor. */
row.className = row.className;
}
}
function onNewAttendeeClick(event) {
newAttendee();
event.stop();
}
function newAttendee(previousAttendee) {
var table = $("freeBusyAttendees"); var table = $("freeBusyAttendees");
var tbody = table.tBodies[0]; var tbody = table.tBodies[0];
var model = tbody.rows[tbody.rows.length - 1]; var model = tbody.rows[tbody.rows.length - 1];
var futureRow = tbody.rows[tbody.rows.length - 2]; var nextRowIndex = tbody.rows.length - 2;
var newRow = model.cloneNode(true); if (previousAttendee) {
tbody.insertBefore(newRow, futureRow); nextRowIndex = previousAttendee.rowIndex + 1;
}
var nextRow = tbody.rows[nextRowIndex];
var newRow = $(model.cloneNode(true));
tbody.insertBefore(newRow, nextRow);
var result = newRow;
var statusTD = newRow.down(".attendeeStatus");
if (statusTD) {
var boundOnStatusClick = onAttendeeStatusClick.bindAsEventListener(newRow);
statusTD.observe("click", boundOnStatusClick, false);
}
$(newRow).removeClassName("attendeeModel"); $(newRow).removeClassName("attendeeModel");
var input = $(newRow).down("input"); var input = newRow.down("input");
input.observe("keydown", onContactKeydown); input.observe("keydown", onContactKeydown.bindAsEventListener(input));
input.observe("blur", checkAttendee); input.observe("blur", onInputBlur);
input.focussed = true; input.focussed = true;
input.activate(); input.activate();
@ -304,91 +468,98 @@ function newAttendee(event) {
table = $("freeBusyData"); table = $("freeBusyData");
tbody = table.tBodies[0]; tbody = table.tBodies[0];
model = tbody.rows[tbody.rows.length - 1]; model = tbody.rows[tbody.rows.length - 1];
futureRow = tbody.rows[tbody.rows.length - 2]; nextRow = tbody.rows[nextRowIndex];
newRow = model.cloneNode(true); newRow = $(model.cloneNode(true));
tbody.insertBefore(newRow, futureRow); tbody.insertBefore(newRow, nextRow);
$(newRow).removeClassName("dataModel"); newRow.removeClassName("dataModel");
var attendeesDiv = $$('TABLE#freeBusy TD.freeBusyAttendees DIV').first(); var attendeesDiv = $$('TABLE#freeBusy TD.freeBusyAttendees DIV').first();
var dataDiv = $$('TABLE#freeBusy TD.freeBusyData DIV').first(); var dataDiv = $$('TABLE#freeBusy TD.freeBusyData DIV').first();
dataDiv.scrollTop = attendeesDiv.scrollTop; dataDiv.scrollTop = attendeesDiv.scrollTop;
return result;
} }
function checkAttendee() { log ("checkAttendee"); function checkAttendee(input) {
if (document.currentPopupMenu) var row = $(input.parentNode.parentNode);
hideMenu(document.currentPopupMenu);
if (document.currentPopupMenu && !this.confirmedValue) {
// Hack for IE7; blur event is triggered on input field when
// selecting a menu item
var visible = $(document.currentPopupMenu).getStyle('visibility') != 'hidden';
if (visible)
return;
}
this.focussed = false;
var row = this.parentNode.parentNode;
var tbody = row.parentNode; var tbody = row.parentNode;
if (tbody && this.value.trim().length == 0) { if (tbody && input.value.trim().length == 0) {
var dataTable = $("freeBusyData").tBodies[0]; var dataTable = $("freeBusyData").tBodies[0];
var dataRow = dataTable.rows[row.sectionRowIndex]; var dataRow = dataTable.rows[row.sectionRowIndex];
tbody.removeChild(row); tbody.removeChild(row);
dataTable.removeChild(dataRow); dataTable.removeChild(dataRow);
} }
else if (this.readAttribute("modified") == "1") { else if (input.modified) {
if (!$(row).hasClassName("needs-action")) { if (!row.hasClassName("needs-action")) {
$(row).addClassName("needs-action"); row.addClassName("needs-action");
$(row).removeClassName("declined"); row.removeClassName("declined");
$(row).removeClassName("accepted"); row.removeClassName("accepted");
} }
if (!this.hasfreebusy) { if (!input.hasfreebusy) {
if (this.uid && this.confirmedValue) if (input.uid && input.confirmedValue) {
this.value = this.confirmedValue; input.value = input.confirmedValue;
log ("4"); }
displayFreeBusyForNode(this); displayFreeBusyForNode(input);
this.hasfreebusy = true; input.hasfreebusy = true;
} }
this.setAttribute("modified", "0"); input.modified = false;
}
}
function onInputBlur(event) {
if (document.currentPopupMenu && !this.confirmedValue) {
// Hack for IE7; blur event is triggered on input field when
// selecting a menu item
var visible = $(document.currentPopupMenu).getStyle('visibility') != 'hidden';
if (visible) {
log("XXX we return");
return;
}
}
if (document.currentPopupMenu)
hideMenu(document.currentPopupMenu);
if (this.isList) {
resolveListAttendees(this, false);
} else {
checkAttendee(this);
} }
attendeesEditor.currentField = null;
} }
function displayFreeBusyForNode(input) { function displayFreeBusyForNode(input) {
var rowIndex = input.parentNode.parentNode.sectionRowIndex; var rowIndex = input.parentNode.parentNode.sectionRowIndex;
var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells; log ("displayFreeBusyForNode index " + rowIndex + " (" + nodes.length + " cells)"); var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells;
log ("displayFreeBusyForNode index " + rowIndex + " (" + nodes.length + " cells)");
if (input.uid) { if (input.uid) {
if (document.contactFreeBusyAjaxRequest) { log ("busy -- delay " + rowIndex); for (var i = 0; i < nodes.length; i++) {
awaitingFreeBusyRequests.push(input); } var node = $(nodes[i]);
else { node.removeClassName("noFreeBusy");
for (var i = 0; i < nodes.length; i++) { while (node.firstChild) {
$(nodes[i]).removeClassName("noFreeBusy"); node.removeChild(node.firstChild);
$(nodes[i]).innerHTML = ('<span class="freeBusyZoneElement"></span>' }
+ '<span class="freeBusyZoneElement"></span>' for (var j = 0; j < 4; j++) {
+ '<span class="freeBusyZoneElement"></span>' createElement("span", null, "freeBusyZoneElement",
+ '<span class="freeBusyZoneElement"></span>'); null, null, 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 + "../" + input.uid
+ "/freebusy.ifb/ajaxRead?"
+ "sday=" + sd + "&eday=" + ed + "&additional=" +
additionalDays ); log (urlstr);
document.contactFreeBusyAjaxRequest
= triggerAjaxRequest(urlstr,
updateFreeBusyDataCallback,
input);
} }
var sd = $('startTime_date').valueAsShortDateString();
var ed = $('endTime_date').valueAsShortDateString();
var urlstr = (UserFolderURL + "../" + input.uid
+ "/freebusy.ifb/ajaxRead?"
+ "sday=" + sd + "&eday=" + ed + "&additional=" +
additionalDays);
triggerAjaxRequest(urlstr,
updateFreeBusyDataCallback,
input);
} else { } else {
for (var i = 0; i < nodes.length; i++) { for (var i = 0; i < nodes.length; i++) {
$(nodes[i]).addClassName("noFreeBusy"); var node = $(nodes[i]);
$(nodes[i]).update(); node.addClassName("noFreeBusy");
while (node.firstChild) {
node.removeChild(node.firstChild);
}
} }
} }
} }
@ -419,15 +590,12 @@ function updateFreeBusyDataCallback(http) {
var slots = http.responseText.split(","); var slots = http.responseText.split(",");
var rowIndex = input.parentNode.parentNode.sectionRowIndex; var rowIndex = input.parentNode.parentNode.sectionRowIndex;
var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells; var nodes = $("freeBusyData").tBodies[0].rows[rowIndex].cells;
log ("received " + slots.length + " slots for " + rowIndex + " with " + nodes.length + " cells"); // log ("received " + slots.length + " slots for " + rowIndex + " with " + nodes.length + " cells");
for (var i = 0; i < slots.length; i++) { for (var i = 0; i < slots.length; i++) {
if (slots[i] != '0') if (slots[i] != '0')
setSlot(nodes, i, slots[i]); setSlot(nodes, i, slots[i]);
} }
} }
document.contactFreeBusyAjaxRequest = null;
if (awaitingFreeBusyRequests.length > 0) { log ("1");
displayFreeBusyForNode(awaitingFreeBusyRequests.shift()); }
} }
} }
@ -436,8 +604,8 @@ function resetAllFreeBusys() {
var inputs = table.getElementsByTagName("input"); var inputs = table.getElementsByTagName("input");
for (var i = 0; i < inputs.length - 1; i++) { for (var i = 0; i < inputs.length - 1; i++) {
var currentInput = inputs[i]; log ("reset fb " + currentInput.uid); var currentInput = inputs[i];
currentInput.hasfreebusy = false; log ("2"); currentInput.hasfreebusy = false;
displayFreeBusyForNode(currentInput); displayFreeBusyForNode(currentInput);
} }
} }
@ -453,7 +621,7 @@ function initializeWindowButtons() {
$("nextSlot").observe ("click", onNextSlotClick, false); $("nextSlot").observe ("click", onNextSlotClick, false);
} }
function findSlot (direction) { function findSlot(direction) {
var userList = UserLogin; var userList = UserLogin;
var table = $("freeBusy"); var table = $("freeBusy");
var inputs = table.getElementsByTagName("input"); var inputs = table.getElementsByTagName("input");
@ -565,7 +733,7 @@ function onNextSlotClick(event) {
function onEditorOkClick(event) { function onEditorOkClick(event) {
preventDefault(event); preventDefault(event);
var attendees = window.opener.attendees; var attendees = window.opener.attendees;
var newAttendees = new Hash(); var newAttendees = new Hash();
var table = $("freeBusy"); var table = $("freeBusy");
@ -578,16 +746,24 @@ function onEditorOkClick(event) {
if (inputs[i].uid) if (inputs[i].uid)
uid = inputs[i].uid; uid = inputs[i].uid;
if (!(name && name.length > 0)) if (!(name && name.length > 0))
if (inputs[i].uid) if (uid.length > 0)
name = inputs[i].uid; name = uid;
else else
name = email; name = email;
var attendee = attendees.get(email); var attendee = attendees["email"];
if (!attendee) if (!attendee) {
attendee = new Hash({'email': email, attendee = {"email": email,
'name': name, "name": name,
'uid': uid, "role": "req-participant",
'partstat': 'needs-action'}); "partstat": "needs-action",
"uid": uid};
}
var partstat = row.getAttribute("partstat");
if (partstat)
attendee["partstat"] = partstat;
var role = row.getAttribute("role");
if (role)
attendee["role"] = role;
newAttendees.set(email, attendee); newAttendees.set(email, attendee);
} }
window.opener.refreshAttendees(newAttendees.toJSON()); window.opener.refreshAttendees(newAttendees.toJSON());
@ -733,38 +909,56 @@ function prepareAttendees() {
var modelData = tbodyData.rows[tbodyData.rows.length - 1]; var modelData = tbodyData.rows[tbodyData.rows.length - 1];
var newDataRow = tbodyData.rows[tbodyData.rows.length - 2]; var newDataRow = tbodyData.rows[tbodyData.rows.length - 2];
attendees.values().each(function(attendee) { attendees.keys().each(function(atKey) {
attendee = $H(attendee); var attendee = attendees.get(atKey);
var row = modelAttendee.cloneNode(true); var row = $(modelAttendee.cloneNode(true));
tbodyAttendees.insertBefore(row, newAttendeeRow); tbodyAttendees.insertBefore(row, newAttendeeRow);
$(row).removeClassName("attendeeModel"); row.removeClassName("attendeeModel");
$(row).addClassName(attendee.get('partstat')); row.setAttribute("partstat", attendee["partstat"]);
var input = $(row).down("input"); row.setAttribute("role", attendee["role"]);
var value = attendee.get('name'); var uid = attendee["uid"];
if (value) if (uid && uid == OwnerLogin) {
value += " "; row.addClassName("organizer-row");
else row.removeClassName("attendee-row");
value = ""; row.isOrganizer = true;
value += "<" + attendee.get('email') + ">"; } else {
input.value = value; row.addClassName("attendee-row");
if (attendee.get('uid')) row.removeClassName("organizer-row");
input.uid = attendee.get('uid'); row.isOrganizer = false;
input.setAttribute("name", ""); }
input.setAttribute("modified", "0"); var statusTD = row.down(".attendeeStatus");
input.observe("blur", checkAttendee); if (statusTD) {
input.observe("keydown", onContactKeydown); var boundOnStatusClick
= onAttendeeStatusClick.bindAsEventListener(row);
row = modelData.cloneNode(true); statusTD.observe("click", boundOnStatusClick, false);
tbodyData.insertBefore(row, newDataRow); }
$(row).removeClassName("dataModel");
log ("3"); var input = row.down("input");
displayFreeBusyForNode(input); var value = attendee["name"];
}); if (value)
value += " ";
else
value = "";
value += "<" + attendee["email"] + ">";
input.value = value;
input.uid = attendee["uid"];
input.cname = attendee["cname"];
input.setAttribute("name", "");
input.modified = false;
input.observe("blur", onInputBlur);
input.observe("keydown", onContactKeydown.bindAsEventListener(input)
);
row = $(modelData.cloneNode(true));
tbodyData.insertBefore(row, newDataRow);
row.removeClassName("dataModel");
displayFreeBusyForNode(input);
});
} }
// Activate "Add attendee" button // Activate "Add attendee" button
var links = tableAttendees.select("TR.futureAttendee TD A"); var links = tableAttendees.select("TR.futureAttendee TD A");
links.first().observe("click", newAttendee); links.first().observe("click", onNewAttendeeClick);
} }
function onWindowResize(event) { function onWindowResize(event) {
@ -799,6 +993,8 @@ function onFreeBusyLoadHandler() {
'hour': $("endTime_time_hour"), 'hour': $("endTime_time_hour"),
'minute': $("endTime_time_minute")}}; 'minute': $("endTime_time_minute")}};
OwnerLogin = window.opener.getOwnerLogin();
synchronizeWithParent("startTime", "startTime"); synchronizeWithParent("startTime", "startTime");
synchronizeWithParent("endTime", "endTime"); synchronizeWithParent("endTime", "endTime");
@ -858,7 +1054,7 @@ function initTimeWidgets(widgets) {
function onAdjustTime(event) { function onAdjustTime(event) {
var endDate = window.getEndDate(); var endDate = window.getEndDate();
var startDate = window.getStartDate(); var startDate = window.getStartDate();
if ($(this).readAttribute("id").startsWith("start")) { if (this.id.startsWith("start")) {
// Start date was changed // Start date was changed
var delta = window.getShadowStartDate().valueOf() - var delta = window.getShadowStartDate().valueOf() -
startDate.valueOf(); startDate.valueOf();

View file

@ -6,6 +6,10 @@ var ComponentEditor = {
reminderWindow: null reminderWindow: null
}; };
function getOwnerLogin() {
return ownerLogin;
}
function onPopupAttendeesWindow(event) { function onPopupAttendeesWindow(event) {
if (event) if (event)
preventDefault(event); preventDefault(event);
@ -202,7 +206,7 @@ function onSummaryChange (e) {
function onReplyChange(event) { function onReplyChange(event) {
var delegateEditor = $("delegateEditor"); var delegateEditor = $("delegateEditor");
if (this.value == 2) { if (this.value == 4) {
// Delegated // Delegated
delegateEditor.show(); delegateEditor.show();
$("delegatedTo").focus(); $("delegatedTo").focus();
@ -328,7 +332,11 @@ function onOkButtonClick (e) {
action = 'accept'; action = 'accept';
else if (value == 1) else if (value == 1)
action = 'decline'; action = 'decline';
else if (value == 2) { else if (value == 2)
action = 'needsaction';
else if (value == 3)
action = 'tentative';
else if (value == 4) {
var url = ApplicationBaseURL + activeCalendar + '/' + activeComponent; var url = ApplicationBaseURL + activeCalendar + '/' + activeComponent;
document.modifyEventAjaxRequest = delegateInvitation(url, modifyEventCallback); document.modifyEventAjaxRequest = delegateInvitation(url, modifyEventCallback);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

View file

@ -67,7 +67,7 @@ function createElement(tagName, id, classes,
if (parentNode) if (parentNode)
parentNode.appendChild(newElement); parentNode.appendChild(newElement);
return $(newElement); return newElement;
} }
function URLForFolderID(folderID) { function URLForFolderID(folderID) {

View file

@ -83,6 +83,10 @@ DIV#attendeesMenu LI.separator
DIV#attendeesView DIV#attendeesView
{ left: 0.5em; } { left: 0.5em; }
TABLE#freeBusyAttendees TD.attendeeStatus,
TABLE#freeBusyAttendees TD.attendeeStatus DIV
{ width: 24px; }
TABLE TABLE
{ empty-cells: show; } { empty-cells: show; }
@ -100,6 +104,18 @@ DIV#propertiesView LEGEND
/* UIxMailPartICalViewer */ /* UIxMailPartICalViewer */
#iCalAttendees SPAN
{ display: block;
height: 18px;
border: 0px;
margin: 0px;
padding: 0px; }
#iCalAttendees DIV.status-icon
{ margin-top: 2px;
border: 0px;
padding: 0px; }
.clear .clear
{ padding-top: 0; } { padding-top: 0; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B