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
maint-2.0.2
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
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>
* 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>
* iCalRecurrenceRule.m (-iCalRepresentationForWeekDay:): made
@ -34,7 +47,7 @@
* iCalWeeklyRecurrenceCalculator.m
(-recurrenceRangesWithinCalendarDateRange:): make use of the new
iCalByDayMask class.
iCalByDayMask class.
(-lastInstanceStartDate): make use of the previous method when a
BYxxx mask is defined in the rule.

View File

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

View File

@ -330,6 +330,40 @@
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
{
[children removeObjectsInArray: [self alarms]];
@ -406,48 +440,6 @@
}
/* 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
{
NSString *organizerMail;
@ -458,6 +450,35 @@
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
{
NSArray *partEmails;
@ -468,26 +489,6 @@
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
withValue: (id) otherValue
{

View File

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

View File

@ -133,6 +133,9 @@
NSString *stat;
switch (_status) {
case iCalPersonPartStatUndefined:
stat = @"";
break;
case iCalPersonPartStatAccepted:
stat = @"ACCEPTED";
break;
@ -170,7 +173,9 @@
NSString *stat;
stat = [[self partStat] uppercaseString];
if (![stat length] || [stat isEqualToString:@"NEEDS-ACTION"])
if (![stat length])
return iCalPersonPartStatUndefined;
else if ([stat isEqualToString:@"NEEDS-ACTION"])
return iCalPersonPartStatNeedsAction;
else if ([stat isEqualToString:@"ACCEPTED"])
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>
* VSSaxDriver.m (_endComponent:value:): worked-around the lameness

View File

@ -31,6 +31,7 @@
#import "VSSaxDriver.h"
#import <SaxObjC/SaxException.h>
#import <NGExtensions/NGQuotedPrintableCoding.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+Encoding.h>
#import <NGCards/NSString+NGCards.h>
#import "common.h"
@ -653,6 +654,7 @@ static NSCharacterSet *whitespaceCharSet = nil;
value: (NSString *) tagValue
{
NSString *mtName;
int max;
mtName = [[self _mapTagName: tagValue] uppercaseString];
if ([cardStack count] > 0)
@ -692,11 +694,19 @@ static NSCharacterSet *whitespaceCharSet = nil;
stringByAppendingString: 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 */
if ([cardStack count] == 0)
if (max == 0)
[self reportQueuedTags];
}

View File

@ -39,7 +39,6 @@
#import <SOPE/NGCards/NSString+NGCards.h>
#import <SOGo/iCalEntityObject+Utilities.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSObject+DAV.h>
@ -591,13 +590,13 @@
SOGoUser *currentUser;
currentUser = [context activeUser];
otherAttendee = [event findParticipant: theOwnerUser];
otherAttendee = [event userAsAttendee: theOwnerUser];
delegateEmail = [otherAttendee delegatedTo];
if ([delegateEmail length])
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else
otherDelegate = NO;
@ -635,7 +634,7 @@
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else
otherDelegate = NO;
}
@ -708,7 +707,7 @@
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else
otherDelegate = NO;
@ -792,7 +791,7 @@
delegateEmail = [delegateEmail rfc822Email];
if ([delegateEmail length])
otherDelegate = [event findParticipantWithEmail: delegateEmail];
otherDelegate = [event findAttendeeWithEmail: delegateEmail];
else
otherDelegate = NO;
}
@ -860,7 +859,7 @@
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
// invitation from organizer B and users C, D, E who are also
// attendees need to verify if A has accepted.
@ -1075,7 +1074,7 @@
[self sendReceiptEmailUsingTemplateNamed: (isUpdate
? @"Update" : @"Invitation")
forObject: emailEvent
to: [newEvent participants]];
to: [newEvent attendees]];
return elements;
}
@ -1120,7 +1119,7 @@
[self sendReceiptEmailUsingTemplateNamed: @"Deletion"
forObject: event
to: [event participants]];
to: [event attendees]];
return elements;
}
@ -1161,7 +1160,7 @@
}
// Find attendee within event
localAttendee = [event findParticipantWithEmail: [attendee rfc822Email]];
localAttendee = [event findAttendeeWithEmail: [attendee rfc822Email]];
if (localAttendee)
{
// Update the attendee's status
@ -1230,7 +1229,7 @@
[[event parent] setMethod: @""];
ownerUser = [SOGoUser userWithLogin: [[SOGoUserManager sharedUserManager]
getUIDForEmail: originator]];
attendee = [event findParticipant: ownerUser];
attendee = [event userAsAttendee: ownerUser];
eventUID = [event uid];
delegate = nil;
@ -1240,7 +1239,7 @@
delegateEmail = [delegateEmail substringFromIndex: 7];
if ([delegateEmail length])
delegate
= [event findParticipantWithEmail: delegateEmail];
= [event findAttendeeWithEmail: delegateEmail];
}
recipientsEnum = [recipients objectEnumerator];
@ -1326,7 +1325,7 @@
// change will be on the attendee corresponding to the ownerUser.
ownerUser = [SOGoUser userWithLogin: owner];
attendee = [event findParticipant: ownerUser];
attendee = [event userAsAttendee: ownerUser];
if (attendee)
{
if (delegate
@ -1338,9 +1337,9 @@
if (delegatedUser != nil && [event userIsOrganizer: delegatedUser])
ex = [NSException exceptionWithHTTPStatus: 403
reason: @"delegate is organizer"];
if ([event isParticipant: [[delegate email] rfc822Email]])
if ([event isAttendee: [[delegate email] rfc822Email]])
ex = [NSException exceptionWithHTTPStatus: 403
reason: @"delegate is a participant"];
reason: @"delegate is a attendee"];
else if ([SOGoGroup groupWithEmail: [[delegate email] rfc822Email]
inDomain: [ownerUser domain]])
ex = [NSException exceptionWithHTTPStatus: 403
@ -1411,7 +1410,7 @@
to: attendees];
}
}
else if ([occurence userIsParticipant: ownerUser])
else if ([occurence userIsAttendee: ownerUser])
// The current user deletes the occurence; let the organizer know that
// the user has declined this occurence.
[self changeParticipationStatus: @"DECLINED" withDelegate: nil
@ -1439,7 +1438,7 @@
NSArray *allEvents;
int count, max;
iCalEvent *currentEvent;
iCalPerson *ownerParticipant;
iCalPerson *ownerAttendee;
NSString *key;
SOGoUser *ownerUser;
@ -1452,14 +1451,14 @@
for (count = 0; count < max; count++)
{
currentEvent = [allEvents objectAtIndex: count];
ownerParticipant = [currentEvent userAsParticipant: ownerUser];
if (ownerParticipant)
ownerAttendee = [currentEvent userAsAttendee: ownerUser];
if (ownerAttendee)
{
if (count == 0)
key = @"master";
else
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 <NGMail/NGMimeMessage.h>
#import <SOGo/iCalEntityObject+Utilities.h>
#import <SOGo/NSCalendarDate+SOGo.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/NSObject+DAV.h>
@ -825,7 +824,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
p = [app pageWithName: pageName inContext: context];
[p setApt: (iCalEvent *) event];
attendee = [event findParticipant: from];
attendee = [event userAsAttendee: from];
[p setAttendee: attendee];
/* construct message */
@ -897,7 +896,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
if (![event userIsOrganizer: ownerUser])
{
organizer = [event organizer];
attendee = [event findParticipant: ownerUser];
attendee = [event userAsAttendee: ownerUser];
[event setAttendees: [NSArray arrayWithObject: attendee]];
[self sendIMIPReplyForEvent: event from: from to: organizer];
}
@ -981,7 +980,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
user = [SOGoUser userWithLogin: uid];
component = [self component: NO secure: NO];
return [component findParticipant: user];
return [component userAsAttendee: user];
}
- (iCalPerson *) iCalPersonWithUID: (NSString *) uid
@ -1061,7 +1060,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
ownerUser = [SOGoUser userWithLogin: owner];
if ([component userIsOrganizer: ownerUser])
role = SOGoCalendarRole_Organizer;
else if ([component userIsParticipant: ownerUser])
else if ([component userIsAttendee: ownerUser])
role = SOGoCalendarRole_Participant;
else
role = SOGoRole_None;
@ -1119,7 +1118,7 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence,
aclUser = [SOGoUser userWithLogin: uid];
if ([component userIsOrganizer: aclUser])
[roles addObject: SOGoCalendarRole_Organizer];
else if ([component userIsParticipant: aclUser])
else if ([component userIsAttendee: aclUser])
[roles addObject: SOGoCalendarRole_Participant];
accessRole
= [container roleForComponentsWithAccessClass: [component symbolicAccessClass]

View File

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

View File

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

View File

@ -46,13 +46,20 @@
#import "SOGoContactGCSFolder.h"
#define folderListingFields [NSArray arrayWithObjects: @"c_name", @"c_cn", \
@"c_givenname", @"c_sn", @"c_screenname", \
@"c_o", @"c_mail", @"c_telephonenumber", \
nil]
static NSArray *folderListingFields = nil;
@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
{
CardGroup *cardEntry;

View File

@ -34,7 +34,6 @@ SOGo_HEADER_FILES = \
SOGoDateFormatter.h \
SOGoPermissions.h \
SOGoStartupLogger.h \
iCalEntityObject+Utilities.h \
NSArray+DAV.h \
NSArray+Utilities.h \
NSCalendarDate+SOGo.h \
@ -103,7 +102,6 @@ SOGo_OBJC_FILES = \
SQLSource.m \
SOGoUserProfile.m \
SOGoSQLUserProfile.m \
iCalEntityObject+Utilities.m \
NSArray+DAV.m \
NSArray+Utilities.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];
moduleSettings = [us objectForKey: module];
if (!moduleSettings)
moduleSettings = [NSMutableDictionary dictionary];
[us setObject: moduleSettings forKey: module];
{
moduleSettings = [NSMutableDictionary new];
[us setObject: moduleSettings forKey: module];
[moduleSettings release];
}
contextIsSetup = YES;
}
}
@ -149,44 +152,6 @@
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> result;
@ -196,7 +161,7 @@
NSArray *folders, *contacts, *descriptors, *sortedContacts;
NSMutableArray *sortedFolders;
NSMutableDictionary *contact, *uniqueContacts;
unsigned int i, j;
unsigned int i, j, max;
NSSortDescriptor *commonNameDescriptor;
BOOL excludeGroups, excludeLists;
@ -217,9 +182,10 @@
else
[localException raise];
NS_ENDHANDLER;
sortedFolders = [NSMutableArray arrayWithCapacity: [folders count]];
max = [folders count];
sortedFolders = [NSMutableArray arrayWithCapacity: max];
uniqueContacts = [NSMutableDictionary dictionary];
for (i = 0; i < [folders count]; i++)
for (i = 0; i < max; i++)
{
folder = [folders objectAtIndex: i];
/* We first search in LDAP folders (in case of duplicated entries in GCS folders) */
@ -228,7 +194,7 @@
else
[sortedFolders addObject: folder];
}
for (i = 0; i < [sortedFolders count]; i++)
for (i = 0; i < max; i++)
{
folder = [sortedFolders objectAtIndex: i];
//NSLog(@" Address book: %@ (%@)", [folder displayName], [folder class]);
@ -245,8 +211,8 @@
&& [uniqueContacts objectForKey: mail] == nil
&& !(excludeGroups && [contact objectForKey: @"isGroup"]))
[uniqueContacts setObject: contact forKey: mail];
else if (!excludeLists
&& [[contact objectForKey: @"c_name"] hasSuffix: @".vlf"])
else if (!excludeLists && [[contact objectForKey: @"c_component"]
isEqualToString: @"vlist"])
{
[contact setObject: [folder nameInContainer]
forKey: @"container"];
@ -254,14 +220,16 @@
forKey: [contact objectForKey: @"c_name"]];
}
}
}
}
if ([uniqueContacts count] > 0)
{
// Sort the contacts by display name
commonNameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"c_cn"
ascending:YES] autorelease];
commonNameDescriptor = [[NSSortDescriptor alloc] initWithKey: @"c_cn"
ascending:YES];
descriptors = [NSArray arrayWithObjects: commonNameDescriptor, nil];
sortedContacts = [[uniqueContacts allValues] sortedArrayUsingDescriptors: descriptors];
[commonNameDescriptor release];
sortedContacts = [[uniqueContacts allValues]
sortedArrayUsingDescriptors: descriptors];
}
else
sortedContacts = [NSArray array];
@ -269,7 +237,7 @@
sortedContacts, @"contacts",
nil];
result = [self responseWithStatus: 200];
[(WOResponse*)result appendContentString: [data jsonRepresentation]];
[(WOResponse*) result appendContentString: [data jsonRepresentation]];
}
else
result = [NSException exceptionWithHTTPStatus: 400
@ -278,36 +246,6 @@
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
{
NSMutableArray *folders;
@ -375,8 +313,7 @@
- (NSString *) currentContactFolderId
{
return [NSString stringWithFormat: @"/%@",
[currentFolder nameInContainer]];
return [NSString stringWithFormat: @"/%@", [currentFolder nameInContainer]];
}
- (NSString *) currentContactFolderName
@ -391,7 +328,8 @@
- (NSString *) currentContactFolderClass
{
return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]? @"remote" : @"local");
return ([currentFolder isKindOfClass: [SOGoContactSourceFolder class]]
? @"remote" : @"local");
}
- (NSString *) verticalDragHandleStyle
@ -401,7 +339,8 @@
[self _setupContext];
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
@ -411,7 +350,8 @@
[self _setupContext];
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
@ -421,7 +361,8 @@
[self _setupContext];
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,11 @@
actionClass = "UIxMailPartICalActions";
actionName = "decline";
};
tentative = {
protectedBy = "View";
actionClass = "UIxMailPartICalActions";
actionName = "tentative";
};
delegate = {
protectedBy = "View";
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}\"?";
/* Legend */
"Required participant" = "Participante Requerido";
"Optional participant" = "Participante Opcional";
"Participant" = "Participante";
"Optional Participant" = "Participante Opcional";
"Non Participant" = "Não Participante";
"Chair" = "Cadeira";
"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}\"?";
/* Legend */
"Required participant" = "Vyžadovaný účastník";
"Optional participant" = "Nepovinný účastník";
"Participant" = "Účastník";
"Optional Participant" = "Nepovinný účastník";
"Non Participant" = "Non Participant";
"Chair" = "Židle";
"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?";
/* Legend */
"Required participant" = "Vereiste deelnemer";
"Optional participant" = "Gewenste deelnemer";
"Participant" = "Deelnemer";
"Optional Participant" = "Gewenste deelnemer";
"Non Participant" = "Non Participant";
"Chair" = "Voorzitter";
"Needs action" = "Actie vereist";

View File

@ -246,10 +246,10 @@
/* Appointments (participation state) */
"partStat_NEEDS-ACTION" = "Needs action";
"partStat_NEEDS-ACTION" = "I will confirm later";
"partStat_ACCEPTED" = "I will attend";
"partStat_DECLINED" = "I will not attend";
"partStat_TENTATIVE" = "I will confirm later";
"partStat_TENTATIVE" = "I might attend";
"partStat_DELEGATED" = "I delegate";
"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}\"?";
/* Legend */
"Required participant" = "Required participant";
"Optional participant" = "Optional participant";
"Participant" = "Participant";
"Optional Participant" = "Optional Participant";
"Non Participant" = "Non Participant";
"Chair" = "Chair";
"Needs action" = "Needs action";

View File

@ -246,10 +246,10 @@
/* Appointments (participation state) */
"partStat_NEEDS-ACTION" = "Décision attendue";
"partStat_NEEDS-ACTION" = "Je confirmerai plus tard";
"partStat_ACCEPTED" = "Je participerai";
"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_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}»?";
/* Legend */
"Required participant" = "Participant obligatoire";
"Optional participant" = "Participant facultatif";
"Chair" = "Chaise";
"Participant" = "Invité";
"Optional Participant" = "Invité optionnel";
"Non Participant" = "Non-invité";
"Chair" = "Président";
"Needs action" = "En attente";
"Accepted" = "Accepté";

View File

@ -460,8 +460,9 @@ validate_endbeforestart = "Ihr Beginn ist nach dem Ende";
= "Wollen Sie diesen Kalender wirklich löschen \"%{0}\"?";
/* Legend */
"Required participant" = "notwendiger Teilnehmer";
"Optional participant" = "optionaler Teilnehmer";
"Participant" = "Teilnehmer";
"Optional Participant" = "optionaler Teilnehmer";
"Non Participant" = "Non Participant";
"Chair" = "Vorsitz";
"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}\"?";
/* Legend */
"Required participant" = "Kötelező résztvevő";
"Optional participant" = "Nem kötelező résztvevő";
"Participant" = "Kötelező résztvevő";
"Optional Participant" = "Nem kötelező résztvevő";
"Non Participant" = "Non Participant";
"Chair" = "Szék";
"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}\"?";
/* Legend */
"Required participant" = "Richiede partecipanti";
"Optional participant" = "Partecipanti opzionali";
"Participant" = "Partecipanti";
"Optional Participant" = "Partecipanti opzionali";
"Non Participant" = "Non Partecipanti";
"Chair" = "Sedia";
"Needs action" = "Richiede un'azione";

View File

@ -460,8 +460,9 @@ validate_endbeforestart = "The end date that you entered occurs before the st
= "Вы уверены что хотите удалить календарь \"%{0}\"?";
/* Legend */
"Required participant" = "Required participant";
"Optional participant" = "Optional participant";
"Participant" = "Participant";
"Optional Participant" = "Optional Participant";
"Non Participant" = "Non Participant";
"Chair" = "Chair";
"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}\"?";
/* Legend */
"Required participant" = "Asistente obligatorio";
"Optional participant" = "Asistentes opcionales";
"Participant" = "Asistente";
"Optional Participant" = "Asistentes opcionales";
"Non Participant" = "Non Particpant";
"Chair" = "Presidente";
"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}\"?";
/* Legend */
"Required participant" = "Deltagande krävs";
"Optional participant" = "Deltagande valfritt";
"Participant" = "Deltagande krävs";
"Optional Participant" = "Deltagande valfritt";
"Non Participant" = "Non Participant";
"Chair" = "Stol";
"Needs action" = "Behöver åtgärd";

View File

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

View File

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

View File

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

View File

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

View File

@ -45,6 +45,7 @@
<var:if condition="hasCalendarAccess">
<div class="uix_ical_toolbar" id="iCalendarToolbar">
<p>
<var:if condition="currentUserAttendee.rsvp" const:value="true">
<var:if condition="currentUserAttendee.partStatWithDefault"
const:value="ACCEPTED" const:negate="YES">
<a href="#" class="button actionButton" id="iCalendarAccept">
@ -56,6 +57,11 @@
<span><var:string label:value="Decline" /></span></a>
</var:if>
<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">
<a href="#" class="button actionButton" id="editDelegate">
<span><var:string label:value="Delegate ..." /></span></a>
@ -76,6 +82,7 @@
<span><var:string label:value="OK" /></span></a>
</span>
</var:if>
</var:if>
<var:if condition="isEventStoredInCalendar" const:negate="YES">
<a href="#" class="button actionButton" id="iCalendarAddToCalendar">
<span><var:string label:value="Add to calendar" /></span></a>
@ -223,7 +230,9 @@
<td valign="top"><var:string label:value="Attendees"/>:</td>
<td id="iCalAttendees">
<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 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>

View File

@ -22,29 +22,23 @@
<div id="freeBusyViewButtons">
<var:string label:value="Suggest time slot:"/>
<span class="buttons"><a id="nextSlot" href="#" class="button"
><span><var:string label:value="Next slot" /></span></a>
<a id="previousSlot" href="#" class="button"
><span><var:string label:value="Previous slot" /></span></a></span>
><span><var:string label:value="Next slot" /></span></a>
<a id="previousSlot" href="#" class="button"
><span><var:string label:value="Previous slot" /></span></a></span>
<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 id="freeBusyView">
<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">
<table id="freeBusy" cellspacing="0" cellpadding="0">
<thead>
<tr>
<td><!--space --></td>
<td class="freeBusyHeader">
<div><table id="freeBusyHeader" cellspacing="0" cellpadding="0">
<tr class="freeBusyHeader1"><!--space --></tr>
<tr class="freeBusyHeader2"><!--space --></tr>
<tr id="currentEventPosition" class="freeBusyHeader3"><!--space --></tr>
</table></div>
<tr class="freeBusyHeader1"><!--space --></tr>
<tr class="freeBusyHeader2"><!--space --></tr>
<tr id="currentEventPosition" class="freeBusyHeader3"><!--space --></tr>
</table></div>
</td>
</tr>
</thead>
@ -52,25 +46,28 @@
<tr>
<td class="freeBusyAttendees">
<div><table id="freeBusyAttendees" cellspacing="0" cellpadding="0">
<tbody>
<tr class="futureAttendee"
><td class="attendees"><a href="#" class="button"
readonly="readonly"><span
><var:string label:value="newAttendee" /></span></a></td
></tr>
<tr class="attendeeModel"
><td class="attendees"><input type="text" class="textField" /></td
></tr>
</tbody>
</table></div>
<tbody>
<tr class="futureAttendee"
><td class="attendeeStatus"><div><!-- space --></div></td
><td class="attendees"><a href="#" class="button"
readonly="readonly"><span
><var:string label:value="newAttendee" /></span></a></td
></tr>
<tr class="attendeeModel"
><td class="attendeeStatus"><div><!-- space --></div></td
><td class="attendees"
><input type="text" class="textField" /></td
></tr>
</tbody>
</table></div>
</td>
<td class="freeBusyData">
<div><table id="freeBusyData" cellspacing="0" cellpadding="0">
<tbody>
<tr class="futureData"></tr>
<tr class="dataModel"></tr>
</tbody>
</table></div>
<tbody>
<tr class="futureData"></tr>
<tr class="dataModel"></tr>
</tbody>
</table></div>
</td>
</tr>
</tbody>
@ -78,27 +75,38 @@
</div>
<div id="freeBusyFooter">
<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
><var:string label:value="Free" /></li>
<li><span class="colorBox busy"><!-- spacer --></span
><var:string label:value="Busy" /></li>
<!-- li><span class="colorBox maybe-busy">\- spacer -\->/span -->
<!-- >var:string label:value="Maybe busy" />/li> -->
<!-- li><span class="colorBox maybe-busy">\- spacer -\->/span -->
<!-- >var:string label:value="Maybe busy" />/li> -->
<li><span class="colorBox noFreeBusy"><!-- spacer --></span
><var:string label:value="No free-busy information" /></li>
</ul>
</div>
<div id="freeBusyReplicas">
<div><span><var:string label:value="Start:"
/></span><var:component className="UIxTimeDateControl"
/></span><var:component className="UIxTimeDateControl"
const:controlID="startTime"
date="aptStartDate"
const:dayStartHour="0"
const:dayEndHour="23"
/></div>
<div><span><var:string label:value="End:"
/></span><var:component className="UIxTimeDateControl"
<div><span><var:string label:value="End:"
/></span><var:component className="UIxTimeDateControl"
const:controlID="endTime"
date="aptEndDate"
const:dayStartHour="0"
@ -108,9 +116,9 @@
<div id="windowButtons">
<hr />
<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"
><span><var:string label:value="Cancel"/></span></a>
><span><var:string label:value="Cancel"/></span></a>
</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 readOnly = <var:if condition="eventIsReadOnly">true</var:if><var:if condition="eventIsReadOnly"
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>
<var:if condition="eventIsReadOnly" const:negate="YES">
@ -203,11 +204,9 @@
<span class="content"><var:string value="organizerName"/></span>
</label>
</var:if>
<var:if condition="userIsAttendee">
<var:if condition="userHasRSVP">
<label><var:string label:value="Reply:" />
<span class="content"><var:popup list="replyList" item="item"
const:disabledValue="-"
label:noSelectionString="partStat_NEEDS-ACTION"
const:name="replyList"
const:id="replyList"
string="itemReplyText"
@ -251,8 +250,17 @@
</label>
<label id="attendeesLabel">
<span class="content"><div id="attendeesMenu" class="fakeTextArea">
<var:foreach list="component.participants" 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:foreach list="component.attendees" item="attendee"
><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>
</div></span>
</label>
@ -331,7 +339,7 @@
var:value="reminderReference"/>
<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>
<a id="cancelButton" href="#" class="button"
><span><var:string label:value="Cancel"/></span></a>

View File

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

View File

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

View File

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

View File

@ -122,42 +122,53 @@ A#attendeesHref
text-decoration: underline; }
DIV#attendeesMenu LI
{ padding-left: 10px; }
{ padding-left: 10px;
width: auto; }
DIV#attendeesMenu .attendee,
DIV#attendeesMenu DIV
DIV#attendeesMenu .attendeeUser
{ 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-position: 5px center;
padding-left: 22px; }
float: left;
width: 12px;
height: 14px;
margin-right: 4px;
background-image: url("attendee-partstats.png"); }
DIV#attendeesMenu .accepted
{ background-image: url("accepted.png"); }
DIV#attendeesMenu .accepted .statusIcon
{ background-position: 0px 0px; }
DIV#attendeesMenu .accepted:hover
{ background-image: url("accepted.selected.png"); }
DIV#attendeesMenu .declined .statusIcon
{ background-position: -12px 0px; }
DIV#attendeesMenu .needs-action
{ background-image: url("needs-action.png"); }
DIV#attendeesMenu .needs-action .statusIcon
{ background-position: -24px 0px; }
DIV#attendeesMenu .needs-action:hover
{ background-image: url("needs-action.selected.png"); }
DIV#attendeesMenu .tentative .statusIcon
{ background-position: -36px 0px; }
DIV#attendeesMenu .declined
{ background-image: url("declined.png"); }
DIV#attendeesMenu .delegated .statusIcon
{ background-position: -48px 0px; }
DIV#attendeesMenu .declined:hover
{ background-image: url("declined.selected.png"); }
DIV#attendeesMenu .delegated
{ background-image: url("delegated.png"); }
DIV#attendeesMenu .delegated:hover
{ background-image: url("delegated.selected.png"); }
DIV#attendeesMenu .no-partstat .statusIcon
{ background-position: -60px 0px; }
DIV#attendeesMenu .delegate
{ background-position: 15px center;
padding-left: 32px; }
/* read-only view */
DIV#attendeesMenu DIV
{ padding-left: 20px; }
{ padding-left: 16px !important; }

View File

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

View File

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

View File

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

View File

@ -6,6 +6,10 @@ var ComponentEditor = {
reminderWindow: null
};
function getOwnerLogin() {
return ownerLogin;
}
function onPopupAttendeesWindow(event) {
if (event)
preventDefault(event);
@ -202,7 +206,7 @@ function onSummaryChange (e) {
function onReplyChange(event) {
var delegateEditor = $("delegateEditor");
if (this.value == 2) {
if (this.value == 4) {
// Delegated
delegateEditor.show();
$("delegatedTo").focus();
@ -328,7 +332,11 @@ function onOkButtonClick (e) {
action = 'accept';
else if (value == 1)
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;
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.0 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)
parentNode.appendChild(newElement);
return $(newElement);
return newElement;
}
function URLForFolderID(folderID) {

View File

@ -83,6 +83,10 @@ DIV#attendeesMenu LI.separator
DIV#attendeesView
{ left: 0.5em; }
TABLE#freeBusyAttendees TD.attendeeStatus,
TABLE#freeBusyAttendees TD.attendeeStatus DIV
{ width: 24px; }
TABLE
{ empty-cells: show; }
@ -100,6 +104,18 @@ DIV#propertiesView LEGEND
/* 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
{ padding-top: 0; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B