(feat) Handle invitations in appointment viewer
parent
87aec2fc01
commit
5e19a889c2
|
@ -200,7 +200,7 @@
|
||||||
"General" = "General";
|
"General" = "General";
|
||||||
"Reply" = "Reply";
|
"Reply" = "Reply";
|
||||||
"Created by" = "Created by";
|
"Created by" = "Created by";
|
||||||
|
"You are invited to participate" = "You are invited to participate";
|
||||||
|
|
||||||
"Target:" = "Target:";
|
"Target:" = "Target:";
|
||||||
|
|
||||||
|
@ -431,6 +431,7 @@ validate_endbeforestart = "The end date that you entered occurs before the st
|
||||||
"New Event" = "New Event";
|
"New Event" = "New Event";
|
||||||
"New Task" = "New Task";
|
"New Task" = "New Task";
|
||||||
"Edit" = "Edit";
|
"Edit" = "Edit";
|
||||||
|
"Update" = "Update";
|
||||||
"Delete" = "Delete";
|
"Delete" = "Delete";
|
||||||
"Go to Today" = "Go to Today";
|
"Go to Today" = "Go to Today";
|
||||||
"Day View" = "Day View";
|
"Day View" = "Day View";
|
||||||
|
@ -523,6 +524,8 @@ vtodo_class2 = "(Confidential task)";
|
||||||
"button_allOccurrences" = "All occurences";
|
"button_allOccurrences" = "All occurences";
|
||||||
"Edit This Occurrence" = "Edit This Occurrence";
|
"Edit This Occurrence" = "Edit This Occurrence";
|
||||||
"Edit All Occurrences" = "Edit All Occurrences";
|
"Edit All Occurrences" = "Edit All Occurrences";
|
||||||
|
"Update This Occurrence" = "Update This Occurrence";
|
||||||
|
"Update All Occurrences" = "Update All Occurrences";
|
||||||
|
|
||||||
/* Properties dialog */
|
/* Properties dialog */
|
||||||
"Name" = "Name";
|
"Name" = "Name";
|
||||||
|
|
|
@ -237,55 +237,77 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
/**
|
||||||
//
|
* @api {post} /so/:username/Calendar/:calendarId/:appointmentId/rsvpAppointment Set participation state
|
||||||
//
|
* @apiVersion 1.0.0
|
||||||
|
* @apiName PostEventRsvp
|
||||||
|
* @apiGroup Calendar
|
||||||
|
* @apiDescription Set the participation state of an attendee.
|
||||||
|
* @apiExample {curl} Example usage:
|
||||||
|
* curl -i http://localhost/SOGo/so/sogo1/Calendar/personal/71B6-54904400-1-7C308500.ics/rsvpAppointment \
|
||||||
|
* -H 'Content-Type: application/json' \
|
||||||
|
* -d '{ "reply": 1, \
|
||||||
|
* "alarm": { { "quantity": 15, "unit": "MINUTES", "action": "display", "reference": "BEFORE", "relation": "START" } }'
|
||||||
|
*
|
||||||
|
* @apiParam {Number} reply 0 if needs-action, 1 if accepted, 2 if declined, 3 if tentative, 4 if delegated
|
||||||
|
* @apiParam {String} [delegatedTo] Email address of delegated attendee
|
||||||
|
* @apiParam {Object[]} [alarm] Set an alarm for the attendee
|
||||||
|
* @apiParam {String} alarm.action Either display or email
|
||||||
|
* @apiParam {Number} alarm.quantity Quantity of units
|
||||||
|
* @apiParam {String} alarm.unit Either MINUTES, HOURS, or DAYS
|
||||||
|
* @apiParam {String} alarm.reference Either BEFORE or AFTER
|
||||||
|
* @apiParam {String} alarm.relation Either START or END
|
||||||
|
* @apiParam {Boolean} [alarm.attendees] Alert attendees by email if 1 and action is email
|
||||||
|
* @apiParam {Boolean} [alarm.organizer] Alert organizer by email if 1 and action is email
|
||||||
|
*/
|
||||||
- (id <WOActionResults>) rsvpAction
|
- (id <WOActionResults>) rsvpAction
|
||||||
{
|
{
|
||||||
iCalPerson *delegatedAttendee;
|
iCalPerson *delegatedAttendee;
|
||||||
NSDictionary *message;
|
NSDictionary *params, *jsonResponse;
|
||||||
WOResponse *response;
|
WOResponse *response;
|
||||||
WORequest *request;
|
WORequest *request;
|
||||||
iCalAlarm *anAlarm;
|
iCalAlarm *anAlarm;
|
||||||
|
NSException *ex;
|
||||||
NSString *status;
|
NSString *status;
|
||||||
|
id alarm;
|
||||||
|
|
||||||
int replyList, reminderList;
|
int replyList;
|
||||||
|
|
||||||
request = [context request];
|
request = [context request];
|
||||||
message = [[request contentAsString] objectFromJSONString];
|
params = [[request contentAsString] objectFromJSONString];
|
||||||
|
|
||||||
delegatedAttendee = nil;
|
delegatedAttendee = nil;
|
||||||
anAlarm = nil;
|
anAlarm = nil;
|
||||||
status = nil;
|
status = nil;
|
||||||
|
|
||||||
replyList = [[message objectForKey: @"replyList"] intValue];
|
replyList = [[params objectForKey: @"reply"] intValue];
|
||||||
|
|
||||||
switch (replyList)
|
switch (replyList)
|
||||||
{
|
{
|
||||||
case 0:
|
case iCalPersonPartStatAccepted:
|
||||||
status = @"ACCEPTED";
|
status = @"ACCEPTED";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case iCalPersonPartStatDeclined:
|
||||||
status = @"DECLINED";
|
status = @"DECLINED";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case iCalPersonPartStatNeedsAction:
|
||||||
status = @"NEEDS-ACTION";
|
status = @"NEEDS-ACTION";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case iCalPersonPartStatTentative:
|
||||||
status = @"TENTATIVE";
|
status = @"TENTATIVE";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4:
|
case iCalPersonPartStatDelegated:
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
NSString *delegatedEmail, *delegatedUid;
|
NSString *delegatedEmail, *delegatedUid;
|
||||||
SOGoUser *user;
|
SOGoUser *user;
|
||||||
|
|
||||||
status = @"DELEGATED";
|
status = @"DELEGATED";
|
||||||
delegatedEmail = [[message objectForKey: @"delegatedTo"] stringByTrimmingSpaces];
|
delegatedEmail = [[params objectForKey: @"delegatedTo"] stringByTrimmingSpaces];
|
||||||
|
|
||||||
if ([delegatedEmail length])
|
if ([delegatedEmail length])
|
||||||
{
|
{
|
||||||
|
@ -308,60 +330,58 @@
|
||||||
[NSString stringWithFormat: @"mailto:%@", [[user allEmails] objectAtIndex: 0]]];
|
[NSString stringWithFormat: @"mailto:%@", [[user allEmails] objectAtIndex: 0]]];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return [NSException exceptionWithHTTPStatus: 400
|
{
|
||||||
reason: @"missing 'to' parameter"];
|
jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
@"failure", @"status",
|
||||||
|
@"missing 'delegatedTo' parameter", @"message",
|
||||||
|
nil];
|
||||||
|
return [self responseWithStatus: 400
|
||||||
|
andString: [jsonResponse jsonRepresentation]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the user alarm, if any
|
// Set an alarm for the user
|
||||||
reminderList = [[message objectForKey: @"reminderList"] intValue];
|
alarm = [params objectForKey: @"alarm"];
|
||||||
|
if ([alarm isKindOfClass: [NSDictionary class]])
|
||||||
|
{
|
||||||
|
NSString *reminderAction, *reminderUnit, *reminderQuantity, *reminderReference, *reminderRelation;
|
||||||
|
BOOL reminderEmailAttendees, reminderEmailOrganizer;
|
||||||
|
|
||||||
if ([[message objectForKey: @"reminderList"] isEqualToString: @"WONoSelectionString"] || reminderList == 5 || reminderList == 10 || reminderList == 14)
|
reminderAction = [alarm objectForKey: @"action"];
|
||||||
{
|
reminderUnit = [alarm objectForKey: @"unit"];
|
||||||
// No selection, wipe alarm which will be done in changeParticipationStatus...
|
reminderQuantity = [alarm objectForKey: @"quantity"];
|
||||||
}
|
reminderReference = [alarm objectForKey: @"reference"];
|
||||||
else if (reminderList == 15)
|
reminderRelation = [alarm objectForKey: @"relation"];
|
||||||
{
|
reminderEmailAttendees = [[alarm objectForKey: @"attendees"] boolValue];
|
||||||
// Custom
|
reminderEmailOrganizer = [[alarm objectForKey: @"organizer"] boolValue];
|
||||||
anAlarm = [iCalAlarm alarmForEvent: [self event]
|
anAlarm = [iCalAlarm alarmForEvent: [self event]
|
||||||
owner: [[self clientObject] ownerInContext: context]
|
owner: [[self clientObject] ownerInContext: context]
|
||||||
action: [message objectForKey: @"reminderAction"]
|
action: reminderAction
|
||||||
unit: [message objectForKey: @"reminderUnit"]
|
unit: reminderUnit
|
||||||
quantity: [message objectForKey: @"reminderQuantity"]
|
quantity: reminderQuantity
|
||||||
reference: [message objectForKey: @"reminderReference"]
|
reference: reminderReference
|
||||||
reminderRelation: [message objectForKey: @"reminderRelation"]
|
reminderRelation: reminderRelation
|
||||||
emailAttendees: [[message objectForKey: @"reminderEmailAttendees"] boolValue]
|
emailAttendees: reminderEmailAttendees
|
||||||
emailOrganizer: [[message objectForKey: @"reminderEmailOrganizer"] boolValue]];
|
emailOrganizer: reminderEmailOrganizer];
|
||||||
|
}
|
||||||
|
|
||||||
|
ex = [[self clientObject] changeParticipationStatus: status
|
||||||
|
withDelegate: delegatedAttendee
|
||||||
|
alarm: anAlarm];
|
||||||
|
|
||||||
|
if (ex)
|
||||||
|
{
|
||||||
|
jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
[ex reason], @"message",
|
||||||
|
nil];
|
||||||
|
response = [self responseWithStatus: [ex httpStatus]
|
||||||
|
andString: [jsonResponse jsonRepresentation]];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
// Standard
|
|
||||||
NSString *aValue;
|
|
||||||
|
|
||||||
aValue = [[UIxComponentEditor reminderValues] objectAtIndex: reminderList];
|
|
||||||
|
|
||||||
// Predefined alarm
|
|
||||||
if ([aValue length])
|
|
||||||
{
|
|
||||||
iCalTrigger *aTrigger;
|
|
||||||
|
|
||||||
anAlarm = [[[iCalAlarm alloc] init] autorelease];
|
|
||||||
aTrigger = [iCalTrigger elementWithTag: @"TRIGGER"];
|
|
||||||
[aTrigger setValueType: @"DURATION"];
|
|
||||||
[anAlarm setTrigger: aTrigger];
|
|
||||||
[anAlarm setAction: @"DISPLAY"];
|
|
||||||
[aTrigger setSingleValue: aValue forKey: @""];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response = (WOResponse *)[[self clientObject] changeParticipationStatus: status
|
|
||||||
withDelegate: delegatedAttendee
|
|
||||||
alarm: anAlarm];
|
|
||||||
|
|
||||||
if (!response)
|
|
||||||
response = [self responseWith204];
|
response = [self responseWith204];
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,9 +433,7 @@
|
||||||
* @apiParam {String} alarm.reference Either BEFORE or AFTER
|
* @apiParam {String} alarm.reference Either BEFORE or AFTER
|
||||||
* @apiParam {String} alarm.relation Either START or END
|
* @apiParam {String} alarm.relation Either START or END
|
||||||
* @apiParam {Boolean} [alarm.attendees] Alert attendees by email if true and action is email
|
* @apiParam {Boolean} [alarm.attendees] Alert attendees by email if true and action is email
|
||||||
* @apiParam {Object} [alarm.organizer] Alert organizer at this email address if action is email
|
* @apiParam {Boolean} [alarm.organizer] Alert organizer by email if true and action is email
|
||||||
* @apiParam {String} [alarm.organizer.name] Attendee's name
|
|
||||||
* @apiParam {String} alarm.organizer.email Attendee's email address
|
|
||||||
*
|
*
|
||||||
* @apiParam {_} ... _Save in [iCalRepeatbleEntityObject+SOGo setAttributes:inContext:]_
|
* @apiParam {_} ... _Save in [iCalRepeatbleEntityObject+SOGo setAttributes:inContext:]_
|
||||||
*
|
*
|
||||||
|
@ -561,6 +579,8 @@
|
||||||
* @apiSuccess (Success 200) {String} localizedEndDate Formatted end date
|
* @apiSuccess (Success 200) {String} localizedEndDate Formatted end date
|
||||||
* @apiSuccess (Success 200) {String} [localizedEndTime] Formatted end time
|
* @apiSuccess (Success 200) {String} [localizedEndTime] Formatted end time
|
||||||
* @apiSuccess (Success 200) {Number} isReadOnly 1 if event is read-only
|
* @apiSuccess (Success 200) {Number} isReadOnly 1 if event is read-only
|
||||||
|
* @apiSuccess (Success 200) {Number} userHasRSVP 1 if owner is invited
|
||||||
|
* @apiSuccess (Success 200) {Number} [reply] 0 if needs-action, 1 if accepted, 2 if declined, 3 if tentative, 4 if delegated
|
||||||
* @apiSuccess (Success 200) {Object[]} [attachUrls] Attached URLs
|
* @apiSuccess (Success 200) {Object[]} [attachUrls] Attached URLs
|
||||||
* @apiSuccess (Success 200) {String} attachUrls.value URL
|
* @apiSuccess (Success 200) {String} attachUrls.value URL
|
||||||
*
|
*
|
||||||
|
@ -572,10 +592,8 @@
|
||||||
* @apiSuccess (Success 200) {String} alarm.unit Either MINUTES, HOURS, or DAYS
|
* @apiSuccess (Success 200) {String} alarm.unit Either MINUTES, HOURS, or DAYS
|
||||||
* @apiSuccess (Success 200) {String} alarm.reference Either BEFORE or AFTER
|
* @apiSuccess (Success 200) {String} alarm.reference Either BEFORE or AFTER
|
||||||
* @apiSuccess (Success 200) {String} alarm.relation Either START or END
|
* @apiSuccess (Success 200) {String} alarm.relation Either START or END
|
||||||
* @apiSuccess (Success 200) {Object[]} [alarm.attendees] List of attendees
|
* @apiSuccess (Success 200) {Boolean} alarm.attendees Alert attendees by email if true and action is email
|
||||||
* @apiSuccess (Success 200) {String} [alarm.attendees.name] Attendee's name
|
* @apiSuccess (Success 200) {Boolean} alarm.organizer Alert organizer by email if true and action is email
|
||||||
* @apiSuccess (Success 200) {String} alarm.attendees.email Attendee's email address
|
|
||||||
* @apiSuccess (Success 200) {String} [alarm.attendees.uid] System user ID
|
|
||||||
*
|
*
|
||||||
* @apiSuccess {_} ... _From [iCalEvent+SOGo attributes]_
|
* @apiSuccess {_} ... _From [iCalEvent+SOGo attributes]_
|
||||||
*
|
*
|
||||||
|
@ -687,6 +705,7 @@
|
||||||
[componentCalendar nameInContainer], @"pid",
|
[componentCalendar nameInContainer], @"pid",
|
||||||
[componentCalendar displayName], @"calendar",
|
[componentCalendar displayName], @"calendar",
|
||||||
[NSNumber numberWithBool: [self isReadOnly]], @"isReadOnly",
|
[NSNumber numberWithBool: [self isReadOnly]], @"isReadOnly",
|
||||||
|
[NSNumber numberWithBool: [self userHasRSVP]], @"userHasRSVP",
|
||||||
[dateFormatter formattedDate: eventStartDate], @"localizedStartDate",
|
[dateFormatter formattedDate: eventStartDate], @"localizedStartDate",
|
||||||
[dateFormatter formattedDate: eventEndDate], @"localizedEndDate",
|
[dateFormatter formattedDate: eventEndDate], @"localizedEndDate",
|
||||||
[self alarm], @"alarm",
|
[self alarm], @"alarm",
|
||||||
|
@ -711,6 +730,9 @@
|
||||||
[data setObject: [dateFormatter formattedTime: eventEndDate] forKey: @"localizedEndTime"];
|
[data setObject: [dateFormatter formattedTime: eventEndDate] forKey: @"localizedEndTime"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ([self userHasRSVP])
|
||||||
|
[data setObject: [self reply] forKey: @"reply"];
|
||||||
|
|
||||||
// Add attributes from iCalEvent+SOGo, iCalEntityObject+SOGo and iCalRepeatableEntityObject+SOGo
|
// Add attributes from iCalEvent+SOGo, iCalEntityObject+SOGo and iCalRepeatableEntityObject+SOGo
|
||||||
[data addEntriesFromDictionary: [event attributesInContext: context]];
|
[data addEntriesFromDictionary: [event attributesInContext: context]];
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
#import <NGObjWeb/WORequest.h>
|
#import <NGObjWeb/WORequest.h>
|
||||||
#import <NGObjWeb/WOResponse.h>
|
#import <NGObjWeb/WOResponse.h>
|
||||||
|
|
||||||
|
#import <NGCards/iCalPerson.h>
|
||||||
|
|
||||||
#import <SOGo/NSArray+Utilities.h>
|
#import <SOGo/NSArray+Utilities.h>
|
||||||
#import <SOGo/SOGoPermissions.h>
|
#import <SOGo/SOGoPermissions.h>
|
||||||
#import <SOGo/SOGoUser.h>
|
#import <SOGo/SOGoUser.h>
|
||||||
|
@ -329,9 +331,67 @@
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
/* Component Viewer, parent class of Appointment Viewer and Task Viewer */
|
||||||
|
|
||||||
|
@interface UIxComponentViewTemplate : UIxComponent
|
||||||
|
{
|
||||||
|
id item;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation UIxComponentViewTemplate
|
||||||
|
|
||||||
|
- (id) init
|
||||||
|
{
|
||||||
|
if ((self = [super init]))
|
||||||
|
{
|
||||||
|
item = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) dealloc
|
||||||
|
{
|
||||||
|
[item release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setItem: (id) _item
|
||||||
|
{
|
||||||
|
ASSIGN (item, _item);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) item
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray *) replyList
|
||||||
|
{
|
||||||
|
return [NSArray arrayWithObjects:
|
||||||
|
[NSNumber numberWithInt: iCalPersonPartStatAccepted],
|
||||||
|
[NSNumber numberWithInt: iCalPersonPartStatDeclined],
|
||||||
|
[NSNumber numberWithInt: iCalPersonPartStatNeedsAction],
|
||||||
|
[NSNumber numberWithInt: iCalPersonPartStatTentative],
|
||||||
|
[NSNumber numberWithInt: iCalPersonPartStatDelegated],
|
||||||
|
nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *) itemReplyText
|
||||||
|
{
|
||||||
|
NSString *word;
|
||||||
|
|
||||||
|
word = [iCalPerson descriptionForParticipationStatus: [item intValue]];
|
||||||
|
|
||||||
|
return [self labelForKey: [NSString stringWithFormat: @"partStat_%@", word]];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
/* Appointment Viewer */
|
/* Appointment Viewer */
|
||||||
|
|
||||||
@interface UIxAppointmentViewTemplate : UIxComponent
|
@interface UIxAppointmentViewTemplate : UIxComponentViewTemplate
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation UIxAppointmentViewTemplate
|
@implementation UIxAppointmentViewTemplate
|
||||||
|
@ -339,7 +399,7 @@
|
||||||
|
|
||||||
/* Task Viewer */
|
/* Task Viewer */
|
||||||
|
|
||||||
@interface UIxTaskViewTemplate : UIxComponent
|
@interface UIxTaskViewTemplate : UIxComponentViewTemplate
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation UIxTaskViewTemplate
|
@implementation UIxTaskViewTemplate
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) isReadOnly;
|
- (BOOL) isReadOnly;
|
||||||
|
- (BOOL) userHasRSVP;
|
||||||
|
- (NSNumber *) reply;
|
||||||
- (BOOL) isChildOccurrence;
|
- (BOOL) isChildOccurrence;
|
||||||
- (void) setAttributes: (NSDictionary *) attributes;
|
- (void) setAttributes: (NSDictionary *) attributes;
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,10 @@
|
||||||
#import "UIxComponentEditor.h"
|
#import "UIxComponentEditor.h"
|
||||||
#import "UIxDatePicker.h"
|
#import "UIxDatePicker.h"
|
||||||
|
|
||||||
|
#define componentReadableWritable 0
|
||||||
|
#define componentOwnerIsInvited 1
|
||||||
|
#define componentReadableOnly 2
|
||||||
|
|
||||||
static NSArray *reminderItems = nil;
|
static NSArray *reminderItems = nil;
|
||||||
static NSArray *reminderValues = nil;
|
static NSArray *reminderValues = nil;
|
||||||
|
|
||||||
|
@ -317,14 +321,21 @@ static NSArray *reminderValues = nil;
|
||||||
// nil];
|
// nil];
|
||||||
//}
|
//}
|
||||||
//
|
//
|
||||||
//- (NSNumber *) reply
|
- (NSNumber *) reply
|
||||||
//{
|
{
|
||||||
// iCalPersonPartStat participationStatus;
|
NSString *owner, *ownerEmail;
|
||||||
//
|
SOGoUserManager *um;
|
||||||
// participationStatus = [ownerAsAttendee participationStatus];
|
iCalPerson *ownerAsAttendee;
|
||||||
//
|
iCalPersonPartStat participationStatus;
|
||||||
// return [NSNumber numberWithInt: participationStatus];
|
|
||||||
//}
|
um = [SOGoUserManager sharedUserManager];
|
||||||
|
owner = [componentCalendar ownerInContext: context];
|
||||||
|
ownerEmail = [um getEmailForUID: owner];
|
||||||
|
ownerAsAttendee = [component findAttendeeWithEmail: (id)ownerEmail];
|
||||||
|
participationStatus = [ownerAsAttendee participationStatus];
|
||||||
|
|
||||||
|
return [NSNumber numberWithInt: participationStatus];
|
||||||
|
}
|
||||||
|
|
||||||
///* priorities */
|
///* priorities */
|
||||||
//
|
//
|
||||||
|
@ -798,7 +809,7 @@ static NSArray *reminderValues = nil;
|
||||||
iCalPerson *ownerAttendee;
|
iCalPerson *ownerAttendee;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = 0;
|
rc = componentReadableWritable;
|
||||||
|
|
||||||
sm = [SoSecurityManager sharedSecurityManager];
|
sm = [SoSecurityManager sharedSecurityManager];
|
||||||
if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
|
if (![sm validatePermission: SOGoCalendarPerm_ModifyComponent
|
||||||
|
@ -813,42 +824,42 @@ static NSArray *reminderValues = nil;
|
||||||
ownerAttendee = [component userAsAttendee: ownerUser];
|
ownerAttendee = [component userAsAttendee: ownerUser];
|
||||||
if ([[ownerAttendee rsvp] isEqualToString: @"true"]
|
if ([[ownerAttendee rsvp] isEqualToString: @"true"]
|
||||||
&& ![component userIsOrganizer: ownerUser])
|
&& ![component userIsOrganizer: ownerUser])
|
||||||
rc = 1;
|
rc = componentOwnerIsInvited;
|
||||||
else
|
else
|
||||||
rc = 2;
|
rc = componentReadableOnly;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
rc = 2; // not invited, just RO
|
rc = componentReadableOnly;
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (int) getEventRWType
|
- (int) getEventRWType
|
||||||
{
|
{
|
||||||
SOGoContentObject <SOGoComponentOccurence> *clientObject;
|
SOGoContentObject <SOGoComponentOccurence> *clientObject;
|
||||||
SOGoUser *ownerUser;
|
SOGoUser *ownerUser;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
clientObject = [self clientObject];
|
clientObject = [self clientObject];
|
||||||
ownerUser = [SOGoUser userWithLogin: [clientObject ownerInContext: context]];
|
ownerUser = [SOGoUser userWithLogin: [clientObject ownerInContext: context]];
|
||||||
if ([componentCalendar isKindOfClass: [SOGoWebAppointmentFolder class]])
|
if ([clientObject isKindOfClass: [SOGoWebAppointmentFolder class]])
|
||||||
rc = 2;
|
rc = componentReadableOnly;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ([ownerUser isEqual: [context activeUser]])
|
if ([ownerUser isEqual: [context activeUser]])
|
||||||
rc = [self ownerIsAttendee: ownerUser
|
rc = [self ownerIsAttendee: ownerUser
|
||||||
andClientObject: clientObject];
|
andClientObject: clientObject];
|
||||||
else
|
else
|
||||||
rc = [self delegateIsAttendee: ownerUser
|
rc = [self delegateIsAttendee: ownerUser
|
||||||
andClientObject: clientObject];
|
andClientObject: clientObject];
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) isReadOnly
|
- (BOOL) isReadOnly
|
||||||
{
|
{
|
||||||
return [self getEventRWType] != 0;
|
return [self getEventRWType] != componentReadableWritable;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
//- (NSString *) emailAlarmsEnabled
|
//- (NSString *) emailAlarmsEnabled
|
||||||
|
@ -862,10 +873,10 @@ static NSArray *reminderValues = nil;
|
||||||
// : @"false");
|
// : @"false");
|
||||||
//}
|
//}
|
||||||
|
|
||||||
//- (BOOL) userHasRSVP
|
- (BOOL) userHasRSVP
|
||||||
//{
|
{
|
||||||
// return ([self getEventRWType] == 1);
|
return ([self getEventRWType] == componentOwnerIsInvited);
|
||||||
//}
|
}
|
||||||
|
|
||||||
//- (unsigned int) firstDayOfWeek
|
//- (unsigned int) firstDayOfWeek
|
||||||
//{
|
//{
|
||||||
|
|
|
@ -4,21 +4,20 @@
|
||||||
xmlns:var="http://www.skyrix.com/od/binding"
|
xmlns:var="http://www.skyrix.com/od/binding"
|
||||||
xmlns:const="http://www.skyrix.com/od/constant"
|
xmlns:const="http://www.skyrix.com/od/constant"
|
||||||
xmlns:label="OGo:label">
|
xmlns:label="OGo:label">
|
||||||
<md-dialog flex="40">
|
<md-dialog flex="40" flex-md="60" flex-sm="100">
|
||||||
<form name="eventForm" ng-submit="viewer.save(eventForm)">
|
<md-toolbar class="md-padding" ng-class="editor.component.getClassName('bg')">
|
||||||
<md-toolbar class="md-padding" ng-class="viewer.component.getClassName('bg')">
|
|
||||||
<div class="md-toolbar-tools">
|
<div class="md-toolbar-tools">
|
||||||
<md-icon class="material-icons sg-icon-toolbar-bg">event</md-icon>
|
<md-icon class="material-icons sg-icon-toolbar-bg">event</md-icon>
|
||||||
<h2 class="md-flex">
|
<div class="sg-md-title md-flex">
|
||||||
<!-- classification -->
|
<!-- classification -->
|
||||||
<md-icon ng-if="viewer.component.classification == 'confidential'">visibility_off</md-icon>
|
<md-icon ng-if="editor.component.classification == 'confidential'">visibility_off</md-icon>
|
||||||
<md-icon ng-if="viewer.component.classification == 'private'">vpn_key</md-icon>
|
<md-icon ng-if="editor.component.classification == 'private'">vpn_key</md-icon>
|
||||||
<!-- summary -->
|
<!-- summary -->
|
||||||
{{viewer.component.summary}}
|
{{::editor.component.summary}}
|
||||||
<!-- priority -->
|
<!-- priority -->
|
||||||
<md-icon ng-repeat="i in viewer.component.priority | range">star</md-icon>
|
<md-icon ng-repeat="i in ::editor.component.priority | range">star</md-icon>
|
||||||
</h2>
|
</div>
|
||||||
<md-button class="md-icon-button" ng-click="viewer.close()">
|
<md-button class="md-icon-button" ng-click="editor.close()">
|
||||||
<md-icon aria-label="Close dialog">close</md-icon>
|
<md-icon aria-label="Close dialog">close</md-icon>
|
||||||
</md-button>
|
</md-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,104 +25,177 @@
|
||||||
<md-dialog-content>
|
<md-dialog-content>
|
||||||
<md-list>
|
<md-list>
|
||||||
<!-- categories -->
|
<!-- categories -->
|
||||||
<md-list-item ng-show="viewer.component.categories.length > 0">
|
<md-list-item ng-show="editor.component.categories.length > 0">
|
||||||
<md-chips class="sg-readonly" ng-model="viewer.component.categories" readonly="true">
|
<md-chips class="sg-readonly" ng-model="::editor.component.categories" readonly="true">
|
||||||
<md-chip-template>
|
<md-chip-template>
|
||||||
<span>{{$chip}}</span>
|
<span>{{$chip}}</span>
|
||||||
</md-chip-template>
|
</md-chip-template>
|
||||||
</md-chips>
|
</md-chips>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- location -->
|
<!-- location -->
|
||||||
<md-list-item ng-show="viewer.component.location">
|
<md-list-item ng-show="editor.component.location">
|
||||||
<md-icon>place</md-icon>
|
<md-icon>place</md-icon>
|
||||||
<p>{{viewer.component.location}}</p>
|
<p>{{::editor.component.location}}</p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- calendar -->
|
<!-- calendar -->
|
||||||
<md-list-item>
|
<md-list-item>
|
||||||
<md-icon>event</md-icon>
|
<md-icon>event</md-icon>
|
||||||
<p>{{viewer.component.calendar}}</p>
|
<p>{{editor.component.calendar}}</p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- start/end dates -->
|
<!-- start/end dates -->
|
||||||
<md-list-item class="md-2-line">
|
<md-list-item ng-class="{ 'md-2-line': editor.component.isAllDay, 'md-3-line': !editor.component.isAllDay }">
|
||||||
<md-icon>access_time</md-icon>
|
<md-icon>access_time</md-icon>
|
||||||
<div layout="row" class="md-flex">
|
<div layout="row" class="md-flex">
|
||||||
<div class="md-list-item-text" ng-show="viewer.component.startDate">
|
<div class="md-list-item-text" ng-show="editor.component.startDate">
|
||||||
<p><var:string label:value="Start"/></p>
|
<p><var:string label:value="Start"/></p>
|
||||||
<h3>
|
<h3>{{::editor.component.localizedStartDate}}</h3>
|
||||||
{{viewer.component.localizedStartDate}}
|
<h3 ng-hide="editor.component.isAllDay">{{::editor.component.localizedStartTime}}</h3>
|
||||||
<span ng-hide="viewer.component.isAllDay"> {{viewer.component.localizedStartTime}}</span>
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md-list-item-text" ng-show="viewer.component.endDate">
|
<div class="md-list-item-text" ng-show="editor.component.endDate">
|
||||||
<p><var:string label:value="End"/></p>
|
<p><var:string label:value="End"/></p>
|
||||||
<h3>
|
<h3>{{::editor.component.localizedEndDate}}</h3>
|
||||||
{{viewer.component.localizedEndDate}}
|
<h3 ng-hide="editor.component.isAllDay">{{::editor.component.localizedEndTime}}</h3>
|
||||||
<span ng-hide="viewer.component.isAllDay">{{viewer.component.localizedEndTime}}</span>
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- is transparent -->
|
<!-- is transparent -->
|
||||||
<md-list-item ng-show="viewer.component.isTransparent">
|
<md-list-item ng-show="editor.component.isTransparent">
|
||||||
<md-icon>event_available</md-icon>
|
<md-icon>event_available</md-icon>
|
||||||
<p><var:string label:value="Show Time as Free"/></p>
|
<p><var:string label:value="Show Time as Free"/></p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- send appointment notifications -->
|
<!-- send appointment notifications -->
|
||||||
<md-list-item ng-show="viewer.component.sendAppointmentNotifications">
|
<md-list-item ng-show="editor.component.sendAppointmentNotifications">
|
||||||
<md-icon>send</md-icon>
|
<md-icon>send</md-icon>
|
||||||
<p><var:string label:value="Send Appointment Notifications"/></p>
|
<p><var:string label:value="Send Appointment Notifications"/></p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- attach urls -->
|
<!-- attach urls -->
|
||||||
<md-list-item ng-show="viewer.component.attachUrls.length > 0">
|
<md-list-item ng-show="editor.component.attachUrls.length > 0">
|
||||||
<md-icon>link</md-icon>
|
<md-icon>link</md-icon>
|
||||||
<p ng-repeat="url in viewer.component.attachUrls"><a ng-href="{{url.value}}">{{url.value}}</a></p>
|
<p ng-repeat="url in ::editor.component.attachUrls"><a ng-href="{{url.value}}">{{url.value}}</a></p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- comment -->
|
<!-- comment -->
|
||||||
<md-list-item ng-show="viewer.component.comment">
|
<md-list-item ng-show="editor.component.comment">
|
||||||
<md-icon>mode_comment</md-icon>
|
<md-icon>mode_comment</md-icon>
|
||||||
<p>{{viewer.component.comment}}</p>
|
<p>{{::editor.component.comment}}</p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- repeat -->
|
<!-- repeat -->
|
||||||
<md-list-item ng-show="viewer.component.repeat">
|
<md-list-item ng-show="editor.component.$isRecurrent">
|
||||||
<md-icon>repeat</md-icon>
|
<md-icon>repeat</md-icon>
|
||||||
<p><!-- viewer.component.repeat.toString() --></p>
|
<p><!-- editor.component.repeat.toString() --></p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- reminder -->
|
<!-- reminder -->
|
||||||
<md-list-item ng-show="viewer.component.$hasAlarm">
|
<md-list-item ng-show="editor.component.$hasAlarm" ng-hide="editor.component.userHasRSVP">
|
||||||
<md-icon>alarm</md-icon>
|
<md-icon>alarm_on</md-icon>
|
||||||
<p><!-- viewer.component.alarm.toString() --></p>
|
<p><!-- editor.component.alarm.toString() --></p>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
<!-- attendees -->
|
<md-list-item class="md-2-line" ng-show="editor.component.organizer">
|
||||||
<md-list-item ng-show="viewer.component.attendees.length > 0">
|
<md-icon>people</md-icon>
|
||||||
<div class="pseudo-input-container">
|
<div layout="column" layout-fill="layout-fill">
|
||||||
<label class="pseudo-input-label"><var:string label:value="Attendees"/></label>
|
<!-- organizer -->
|
||||||
<!-- md-contact-chips don't support "readonly", so we build them using md-chips
|
<div class="pseudo-input-container" ng-show="editor.component.organizer">
|
||||||
in readonly mode and a template similar to the one of md-contact-chips -->
|
<label class="pseudo-input-label"><var:string label:value="Organizer"/></label>
|
||||||
<md-chips class="md-contact-chips"
|
<md-chips class="md-contact-chips sg-readonly"
|
||||||
ng-model="viewer.component.attendees"
|
ng-model="::editor.organizer"
|
||||||
readonly="true">
|
readonly="true">
|
||||||
<md-chip-template>
|
<md-chip-template>
|
||||||
<div class="md-contact-avatar"><img src="#" ng-src="{{$chip.image}}" alt="{{$chip.name}}"/></div>
|
<div class="md-contact-avatar"><img src="#" ng-src="{{$chip.$image}}" alt="{{$chip.name}}"/></div>
|
||||||
<div class="md-contact-name">{{$chip.name}}</div>
|
<div class="md-contact-name">{{$chip.name}}</div>
|
||||||
<md-icon ng-class="'icon-' + $chip.status"><!-- partstat --></md-icon>
|
</md-chip-template>
|
||||||
</md-chip-template>
|
</md-chips>
|
||||||
</md-chips>
|
</div>
|
||||||
|
<!-- attendees -->
|
||||||
|
<div class="pseudo-input-container" ng-show="editor.component.attendees.length > 0">
|
||||||
|
<label class="pseudo-input-label"><var:string label:value="Attendees"/></label>
|
||||||
|
<!-- md-contact-chips don't support "readonly", so we build them using md-chips
|
||||||
|
in readonly mode and a template similar to the one of md-contact-chips -->
|
||||||
|
<md-chips class="md-contact-chips sg-readonly"
|
||||||
|
ng-model="::editor.component.attendees"
|
||||||
|
readonly="true">
|
||||||
|
<md-chip-template>
|
||||||
|
<div class="md-contact-avatar"><img src="#" ng-src="{{$chip.image}}" alt="{{$chip.name}}"/></div>
|
||||||
|
<div class="md-contact-name">{{$chip.name}}</div>
|
||||||
|
<md-icon ng-class="'icon-' + $chip.status"><!-- partstat --></md-icon>
|
||||||
|
</md-chip-template>
|
||||||
|
</md-chips>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</md-list-item>
|
</md-list-item>
|
||||||
</md-list>
|
</md-list>
|
||||||
|
<!-- invitation -->
|
||||||
|
<md-list ng-show="editor.component.userHasRSVP">
|
||||||
|
<md-divider><!-- dividerd --></md-divider>
|
||||||
|
<md-subheader class="md-no-sticky sg-padded--top"><var:string label:value="You are invited to participate"/></md-subheader>
|
||||||
|
<!-- participation status -->
|
||||||
|
<md-list-item>
|
||||||
|
<md-icon>insert_invitation</md-icon>
|
||||||
|
<md-input-container class="md-no-flex">
|
||||||
|
<md-select ng-model="editor.component.reply">
|
||||||
|
<var:foreach list="replyList" item="item">
|
||||||
|
<md-option var:value="item">
|
||||||
|
<var:string value="itemReplyText"/>
|
||||||
|
</md-option>
|
||||||
|
</var:foreach>
|
||||||
|
</md-select>
|
||||||
|
</md-input-container>
|
||||||
|
</md-list-item>
|
||||||
|
<md-list-item ng-show="editor.component.reply == 4">
|
||||||
|
<md-icon><!-- space --></md-icon>
|
||||||
|
<md-autocomplete
|
||||||
|
class="md-flex"
|
||||||
|
md-input-name="delegatedTo"
|
||||||
|
md-search-text="editor.component.delegatedTo"
|
||||||
|
md-selected-item="editor.cardToAdd"
|
||||||
|
md-items="card in editor.cardFilter(editor.component.delegatedTo)"
|
||||||
|
md-item-text="card.$$email"
|
||||||
|
md-min-length="3"
|
||||||
|
md-no-cache="true"
|
||||||
|
label:md-floating-label="Delegated to">
|
||||||
|
<span class="md-contact-suggestion" layout="row" layout-align="space-between center">
|
||||||
|
<span class="md-contact-name"
|
||||||
|
md-highlight-text="editor.component.delegatedTo"
|
||||||
|
md-highlight-flags="^i">{{card.$$fullname}}</span> <span class="md-contact-email"
|
||||||
|
md-highlight-text="editor.component.delegatedTo"
|
||||||
|
md-highlight-flags="^i">{{card.$$email}}</span>
|
||||||
|
</span>
|
||||||
|
</md-autocomplete>
|
||||||
|
</md-list-item>
|
||||||
|
<!-- reminder -->
|
||||||
|
<md-list-item ng-show="editor.component.userHasRSVP">
|
||||||
|
<md-checkbox ng-model="editor.component.$hasAlarm"
|
||||||
|
label:aria-label="Reminder"><!-- reminder --></md-checkbox>
|
||||||
|
<p><var:string label:value="Reminder"/></p>
|
||||||
|
</md-list-item>
|
||||||
|
</md-list>
|
||||||
|
<div class="sg-inset" ng-show="editor.component.userHasRSVP">
|
||||||
|
<span ng-show="editor.component.$hasAlarm"><var:component className="UIxReminderEditor" /></span>
|
||||||
|
</div>
|
||||||
</md-dialog-content>
|
</md-dialog-content>
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="md-actions">
|
<div class="md-actions" ng-show="editor.component.isEditable()">
|
||||||
<md-button type="button" ng-click="viewer.edit()" ng-hide="viewer.component.occurrenceId">
|
<md-button type="button" ng-click="editor.edit()">
|
||||||
<var:string label:value="Edit"/>
|
<var:string label:value="Edit"/>
|
||||||
</md-button>
|
</md-button>
|
||||||
<md-button type="button" ng-click="viewer.editAllOccurrences()" ng-show="viewer.component.occurrenceId">
|
</div>
|
||||||
|
<div class="md-actions" ng-show="editor.component.isEditableOccurrence()">
|
||||||
|
<md-button type="button" ng-click="editor.editAllOccurrences()">
|
||||||
<var:string label:value="Edit All Occurrences"/>
|
<var:string label:value="Edit All Occurrences"/>
|
||||||
</md-button>
|
</md-button>
|
||||||
<md-button type="button" ng-click="viewer.edit()" ng-show="viewer.component.occurrenceId">
|
<md-button type="button" ng-click="editor.edit()">
|
||||||
<var:string label:value="Edit This Occurrence"/>
|
<var:string label:value="Edit This Occurrence"/>
|
||||||
</md-button>
|
</md-button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div class="md-actions" ng-show="editor.component.isInvitation()">
|
||||||
</md-dialog>
|
<md-button type="button" ng-click="editor.reply()">
|
||||||
|
<var:string label:value="Update"/>
|
||||||
|
</md-button>
|
||||||
|
</div>
|
||||||
|
<div class="md-actions" ng-show="editor.component.isInvitationOccurrence()">
|
||||||
|
<md-button type="button" ng-click="editor.replyAllOccurrences()">
|
||||||
|
<var:string label:value="Update All Occurrences"/>
|
||||||
|
</md-button>
|
||||||
|
<md-button type="button" ng-click="editor.reply()">
|
||||||
|
<var:string label:value="Update This Occurrence"/>
|
||||||
|
</md-button>
|
||||||
|
</div>
|
||||||
|
</md-dialog>
|
||||||
</container>
|
</container>
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
* @memberof User
|
* @memberof User
|
||||||
* @desc Search for users that match a string.
|
* @desc Search for users that match a string.
|
||||||
* @param {string} search - a string used to performed the search
|
* @param {string} search - a string used to performed the search
|
||||||
|
* @param {object[]} excludedUsers - a list of User objects that must be excluded from the results
|
||||||
* @return a promise of an array of matching User objects
|
* @return a promise of an array of matching User objects
|
||||||
*/
|
*/
|
||||||
User.$filter = function(search, excludedUsers) {
|
User.$filter = function(search, excludedUsers) {
|
||||||
|
|
|
@ -67,19 +67,58 @@
|
||||||
* @desc Search for cards among all addressbooks matching some criterias.
|
* @desc Search for cards among all addressbooks matching some criterias.
|
||||||
* @param {string} search - the search string to match
|
* @param {string} search - the search string to match
|
||||||
* @param {object} [options] - additional options to the query (excludeGroups and excludeLists)
|
* @param {object} [options] - additional options to the query (excludeGroups and excludeLists)
|
||||||
|
* @param {object[]} excludedCards - a list of Card objects that must be excluded from the results
|
||||||
* @returns a collection of Cards instances
|
* @returns a collection of Cards instances
|
||||||
*/
|
*/
|
||||||
AddressBook.$filterAll = function(search, options) {
|
AddressBook.$filterAll = function(search, options, excludedCards) {
|
||||||
var params = {search: search};
|
var params = {search: search};
|
||||||
|
|
||||||
|
if (!search) {
|
||||||
|
// No query specified
|
||||||
|
AddressBook.$cards = [];
|
||||||
|
return AddressBook.$q.when(AddressBook.$cards);
|
||||||
|
}
|
||||||
|
if (angular.isUndefined(AddressBook.$cards)) {
|
||||||
|
// First session query
|
||||||
|
AddressBook.$cards = [];
|
||||||
|
}
|
||||||
|
else if (AddressBook.$query == search) {
|
||||||
|
// Query hasn't changed
|
||||||
|
return AddressBook.$q.when(AddressBook.$cards);
|
||||||
|
}
|
||||||
|
AddressBook.$query = search;
|
||||||
|
|
||||||
angular.extend(params, options);
|
angular.extend(params, options);
|
||||||
|
|
||||||
return AddressBook.$$resource.fetch(null, 'allContactSearch', params).then(function(response) {
|
return AddressBook.$$resource.fetch(null, 'allContactSearch', params).then(function(response) {
|
||||||
var results = [];
|
var results, card, index,
|
||||||
angular.forEach(response.contacts, function(data) {
|
compareIds = function(data) {
|
||||||
var card = new AddressBook.$Card(data);
|
return this.id == data.id;
|
||||||
results.push(card);
|
};
|
||||||
|
if (excludedCards) {
|
||||||
|
// Remove excluded cards from results
|
||||||
|
results = _.filter(response.contacts, function(data) {
|
||||||
|
return _.isUndefined(_.find(excludedCards, compareIds, data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
results = response.contacts;
|
||||||
|
}
|
||||||
|
// Remove cards that no longer match the search query
|
||||||
|
for (index = AddressBook.$cards.length - 1; index >= 0; index--) {
|
||||||
|
card = AddressBook.$cards[index];
|
||||||
|
if (_.isUndefined(_.find(results, compareIds, card))) {
|
||||||
|
AddressBook.$cards.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add new cards matching the search query
|
||||||
|
_.each(results, function(data, index) {
|
||||||
|
if (_.isUndefined(_.find(AddressBook.$cards, compareIds, data))) {
|
||||||
|
var card = new AddressBook.$Card(data, search);
|
||||||
|
AddressBook.$cards.splice(index, 0, card);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return results;
|
return AddressBook.$cards;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,9 @@
|
||||||
if (!this.$$image)
|
if (!this.$$image)
|
||||||
this.$$image = this.image || Card.$gravatar(this.$preferredEmail(partial), 32);
|
this.$$image = this.image || Card.$gravatar(this.$preferredEmail(partial), 32);
|
||||||
this.selected = false;
|
this.selected = false;
|
||||||
|
|
||||||
|
// An empty attribute to trick md-autocomplete when adding attendees from the appointment editor
|
||||||
|
this.empty = ' ';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
escapeToClose: true,
|
escapeToClose: true,
|
||||||
templateUrl: templateUrl,
|
templateUrl: templateUrl,
|
||||||
controller: 'ComponentController',
|
controller: 'ComponentController',
|
||||||
controllerAs: 'viewer',
|
controllerAs: 'editor',
|
||||||
locals: {
|
locals: {
|
||||||
stateComponent: component
|
stateComponent: component
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,6 +311,7 @@
|
||||||
this.due = new Date(this.dueDate.substring(0,10) + ' ' + this.dueDate.substring(11,16));
|
this.due = new Date(this.dueDate.substring(0,10) + ' ' + this.dueDate.substring(11,16));
|
||||||
|
|
||||||
// Parse recurrence rule definition and initialize default values
|
// Parse recurrence rule definition and initialize default values
|
||||||
|
this.$isRecurrent = angular.isDefined(data.repeat);
|
||||||
if (this.repeat.days) {
|
if (this.repeat.days) {
|
||||||
var byDayMask = _.find(this.repeat.days, function(o) {
|
var byDayMask = _.find(this.repeat.days, function(o) {
|
||||||
return angular.isDefined(o.occurrence);
|
return angular.isDefined(o.occurrence);
|
||||||
|
@ -368,6 +369,10 @@
|
||||||
// Allow the component to be moved to a different calendar
|
// Allow the component to be moved to a different calendar
|
||||||
this.destinationCalendar = this.pid;
|
this.destinationCalendar = this.pid;
|
||||||
|
|
||||||
|
if (this.organizer && this.organizer.email) {
|
||||||
|
this.organizer.$image = Component.$gravatar(this.organizer.email, 32);
|
||||||
|
}
|
||||||
|
|
||||||
// Load freebusy of attendees
|
// Load freebusy of attendees
|
||||||
this.freebusy = this.updateFreeBusyCoverage();
|
this.freebusy = this.updateFreeBusyCoverage();
|
||||||
|
|
||||||
|
@ -394,6 +399,56 @@
|
||||||
return b;
|
return b;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function isEditable
|
||||||
|
* @memberof Component.prototype
|
||||||
|
* @desc Check if the component is editable and not an occurrence of a recurrent component
|
||||||
|
* @returns true or false
|
||||||
|
*/
|
||||||
|
Component.prototype.isEditable = function() {
|
||||||
|
return (!this.occurrenceId && !this.isReadOnly);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function isEditableOccurrence
|
||||||
|
* @memberof Component.prototype
|
||||||
|
* @desc Check if the component is editable and an occurrence of a recurrent component
|
||||||
|
* @returns true or false
|
||||||
|
*/
|
||||||
|
Component.prototype.isEditableOccurrence = function() {
|
||||||
|
return (this.occurrenceId && !this.isReadOnly);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function isInvitation
|
||||||
|
* @memberof Component.prototype
|
||||||
|
* @desc Check if the component an invitation and not an occurrence of a recurrent component
|
||||||
|
* @returns true or false
|
||||||
|
*/
|
||||||
|
Component.prototype.isInvitation = function() {
|
||||||
|
return (!this.occurrenceId && this.userHasRSVP);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function isInvitationOccurrence
|
||||||
|
* @memberof Component.prototype
|
||||||
|
* @desc Check if the component an invitation and an occurrence of a recurrent component
|
||||||
|
* @returns true or false
|
||||||
|
*/
|
||||||
|
Component.prototype.isInvitationOccurrence = function() {
|
||||||
|
return (this.occurrenceId && this.userHasRSVP);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function isReadOnly
|
||||||
|
* @memberof Component.prototype
|
||||||
|
* @desc Check if the component is not editable and not an invitation
|
||||||
|
* @returns true or false
|
||||||
|
*/
|
||||||
|
Component.prototype.isReadOnly = function() {
|
||||||
|
return (this.isReadOnly && !this.userHasRSVP);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function enablePercentComplete
|
* @function enablePercentComplete
|
||||||
* @memberof Component.prototype
|
* @memberof Component.prototype
|
||||||
|
@ -581,6 +636,7 @@
|
||||||
*/
|
*/
|
||||||
Component.prototype.canRemindAttendeesByEmail = function() {
|
Component.prototype.canRemindAttendeesByEmail = function() {
|
||||||
return this.alarm.action == 'email' &&
|
return this.alarm.action == 'email' &&
|
||||||
|
!this.isReadOnly &&
|
||||||
this.attendees && this.attendees.length > 0;
|
this.attendees && this.attendees.length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -635,6 +691,32 @@
|
||||||
this.$shadowData = this.$omit(true);
|
this.$shadowData = this.$omit(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function reply
|
||||||
|
* @memberof Component.prototype
|
||||||
|
* @desc Reply to an invitation.
|
||||||
|
* @returns a promise of the HTTP operation
|
||||||
|
*/
|
||||||
|
Component.prototype.$reply = function() {
|
||||||
|
var _this = this, data, path = [this.pid, this.id];
|
||||||
|
|
||||||
|
if (this.occurrenceId)
|
||||||
|
path.push(this.occurrenceId);
|
||||||
|
|
||||||
|
data = {
|
||||||
|
reply: this.reply,
|
||||||
|
delegatedTo: this.delegatedTo,
|
||||||
|
alarm: this.$hasAlarm? this.alarm : {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Component.$$resource.save(path.join('/'), data, { action: 'rsvpAppointment' })
|
||||||
|
.then(function(data) {
|
||||||
|
// Make a copy of the data for an eventual reset
|
||||||
|
_this.$shadowData = _this.$omit(true);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function $save
|
* @function $save
|
||||||
* @memberof Component.prototype
|
* @memberof Component.prototype
|
||||||
|
|
|
@ -6,20 +6,24 @@
|
||||||
/**
|
/**
|
||||||
* @ngInject
|
* @ngInject
|
||||||
*/
|
*/
|
||||||
ComponentController.$inject = ['$mdDialog', 'Calendar', 'stateComponent'];
|
ComponentController.$inject = ['$rootScope', '$mdDialog', 'Calendar', 'AddressBook', 'Alarm', 'stateComponent'];
|
||||||
function ComponentController($mdDialog, Calendar, stateComponent) {
|
function ComponentController($rootScope, $mdDialog, Calendar, AddressBook, Alarm, stateComponent) {
|
||||||
var vm = this, component;
|
var vm = this, component;
|
||||||
|
|
||||||
vm.component = stateComponent;
|
vm.component = stateComponent;
|
||||||
vm.close = close;
|
vm.close = close;
|
||||||
|
vm.cardFilter = cardFilter;
|
||||||
vm.edit = edit;
|
vm.edit = edit;
|
||||||
vm.editAllOccurrences = editAllOccurrences;
|
vm.editAllOccurrences = editAllOccurrences;
|
||||||
|
vm.reply = reply;
|
||||||
|
vm.replyAllOccurrences = replyAllOccurrences;
|
||||||
|
|
||||||
// Load all attributes of component
|
// Load all attributes of component
|
||||||
if (angular.isUndefined(vm.component.$futureComponentData)) {
|
if (angular.isUndefined(vm.component.$futureComponentData)) {
|
||||||
component = Calendar.$get(vm.component.c_folder).$getComponent(vm.component.c_name, vm.component.c_recurrence_id);
|
component = Calendar.$get(vm.component.c_folder).$getComponent(vm.component.c_name, vm.component.c_recurrence_id);
|
||||||
component.$futureComponentData.then(function() {
|
component.$futureComponentData.then(function() {
|
||||||
vm.component = component;
|
vm.component = component;
|
||||||
|
vm.organizer = [vm.component.organizer];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,12 +31,10 @@
|
||||||
$mdDialog.hide();
|
$mdDialog.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function editAllOccurrences() {
|
// Autocomplete cards for attendees
|
||||||
component = Calendar.$get(vm.component.pid).$getComponent(vm.component.id);
|
function cardFilter($query) {
|
||||||
component.$futureComponentData.then(function() {
|
AddressBook.$filterAll($query);
|
||||||
vm.component = component;
|
return AddressBook.$cards;
|
||||||
edit();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit() {
|
function edit() {
|
||||||
|
@ -54,6 +56,38 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editAllOccurrences() {
|
||||||
|
component = Calendar.$get(vm.component.pid).$getComponent(vm.component.id);
|
||||||
|
component.$futureComponentData.then(function() {
|
||||||
|
vm.component = component;
|
||||||
|
edit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reply(component) {
|
||||||
|
var c = component || vm.component;
|
||||||
|
|
||||||
|
c.$reply().then(function() {
|
||||||
|
$rootScope.$broadcast('calendars:list');
|
||||||
|
$mdDialog.hide();
|
||||||
|
Alarm.getAlarms();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replyAllOccurrences() {
|
||||||
|
// Retrieve master event
|
||||||
|
component = Calendar.$get(vm.component.pid).$getComponent(vm.component.id);
|
||||||
|
component.$futureComponentData.then(function() {
|
||||||
|
// Propagate the participant status and alarm to the master event
|
||||||
|
component.reply = vm.component.reply;
|
||||||
|
component.delegatedTo = vm.component.delegatedTo;
|
||||||
|
component.$hasAlarm = vm.component.$hasAlarm;
|
||||||
|
component.alarm = vm.component.alarm;
|
||||||
|
// Send reply to the server
|
||||||
|
reply(component);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,7 +105,6 @@
|
||||||
vm.showAttendeesEditor = angular.isDefined(vm.component.attendees);
|
vm.showAttendeesEditor = angular.isDefined(vm.component.attendees);
|
||||||
vm.toggleAttendeesEditor = toggleAttendeesEditor;
|
vm.toggleAttendeesEditor = toggleAttendeesEditor;
|
||||||
vm.cardFilter = cardFilter;
|
vm.cardFilter = cardFilter;
|
||||||
vm.cardResults = [];
|
|
||||||
vm.addAttendee = addAttendee;
|
vm.addAttendee = addAttendee;
|
||||||
vm.addAttachUrl = addAttachUrl;
|
vm.addAttachUrl = addAttachUrl;
|
||||||
vm.cancel = cancel;
|
vm.cancel = cancel;
|
||||||
|
@ -119,29 +152,8 @@
|
||||||
|
|
||||||
// Autocomplete cards for attendees
|
// Autocomplete cards for attendees
|
||||||
function cardFilter($query) {
|
function cardFilter($query) {
|
||||||
var index, indexResult, card;
|
AddressBook.$filterAll($query);
|
||||||
if ($query) {
|
return AddressBook.$cards;
|
||||||
AddressBook.$filterAll($query).then(function(results) {
|
|
||||||
var compareIds = function(result) {
|
|
||||||
return this.id == result.id;
|
|
||||||
};
|
|
||||||
// Remove cards that no longer match the search query
|
|
||||||
for (index = vm.cardResults.length - 1; index >= 0; index--) {
|
|
||||||
card = vm.cardResults[index];
|
|
||||||
indexResult = _.findIndex(results, compareIds, card);
|
|
||||||
if (indexResult >= 0)
|
|
||||||
results.splice(indexResult, 1);
|
|
||||||
else
|
|
||||||
vm.cardResults.splice(index, 1);
|
|
||||||
}
|
|
||||||
_.each(results, function(card) {
|
|
||||||
// Add cards matching the search query but not already in the list of attendees
|
|
||||||
if (!vm.component.hasAttendee(card))
|
|
||||||
vm.cardResults.push(card);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return vm.cardResults;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addAttendee(card) {
|
function addAttendee(card) {
|
||||||
|
@ -202,7 +214,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
angular
|
angular
|
||||||
.module('SOGo.SchedulerUI')
|
.module('SOGo.SchedulerUI')
|
||||||
.controller('ComponentController', ComponentController)
|
.controller('ComponentController', ComponentController)
|
||||||
.controller('ComponentEditorController', ComponentEditorController);
|
.controller('ComponentEditorController', ComponentEditorController);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -21,4 +21,9 @@ md-content {
|
||||||
border-left: $baseline-grid solid sg-color($sogoGreen, 100);
|
border-left: $baseline-grid solid sg-color($sogoGreen, 100);
|
||||||
margin-left: ($baseline-grid / 2);
|
margin-left: ($baseline-grid / 2);
|
||||||
padding-left: $baseline-grid;
|
padding-left: $baseline-grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sg-inset {
|
||||||
|
// Inspired from <md-divider md-inset>
|
||||||
|
margin-left: $baseline-grid * 10;
|
||||||
}
|
}
|
|
@ -26,6 +26,7 @@ md-list-item {
|
||||||
.md-list-item-inner {
|
.md-list-item-inner {
|
||||||
flex-grow: 1; // use all column space
|
flex-grow: 1; // use all column space
|
||||||
}
|
}
|
||||||
|
md-icon,
|
||||||
.md-button md-icon {
|
.md-button md-icon {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,15 @@ main {
|
||||||
.sg-padded {
|
.sg-padded {
|
||||||
padding-left: $mg;
|
padding-left: $mg;
|
||||||
padding-right: $mg;
|
padding-right: $mg;
|
||||||
|
&--left {
|
||||||
|
padding-left: $mg;
|
||||||
|
}
|
||||||
&--right {
|
&--right {
|
||||||
padding-right: $mg;
|
padding-right: $mg;
|
||||||
}
|
}
|
||||||
|
&--top {
|
||||||
|
padding-top: $mg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sg-logo {
|
.sg-logo {
|
||||||
|
|
Loading…
Reference in New Issue