New events list view
parent
614a137128
commit
77f9f60276
1
NEWS
1
NEWS
|
@ -5,6 +5,7 @@ New features
|
|||
- [core] can now invite attendees to exceptions only (#2561)
|
||||
- [web] display freebusy information of owner in appointment editor
|
||||
- [web] register SOGo as a handler for the mailto scheme (#1223)
|
||||
- [web] new events list view where events are grouped by day
|
||||
|
||||
Enhancements
|
||||
- [web] follow requested URL after user authentication
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
@class WOResponse, WOResourceManager;
|
||||
|
||||
@interface SOGoDirectAction : WODirectAction
|
||||
{
|
||||
NSDictionary *locale;
|
||||
}
|
||||
|
||||
- (WOResponse *) responseWithStatus: (unsigned int) status;
|
||||
- (WOResponse *) responseWithStatus: (unsigned int) status
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#import <Foundation/NSKeyValueCoding.h>
|
||||
#import <Foundation/NSUserDefaults.h> /* for locale strings */
|
||||
|
||||
#import <NGObjWeb/SoObjects.h>
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
|
@ -30,7 +31,9 @@
|
|||
#import <SoObjects/SOGo/NSString+Utilities.h>
|
||||
#import <SoObjects/SOGo/SOGoSession.h>
|
||||
#import <SoObjects/SOGo/SOGoSystemDefaults.h>
|
||||
#import <SoObjects/SOGo/SOGoUser.h>
|
||||
#import <SoObjects/SOGo/SOGoWebAuthenticator.h>
|
||||
#import <SoObjects/SOGo/WOResourceManager+SOGo.h>
|
||||
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
|
@ -50,6 +53,31 @@ static SoProduct *commonProduct = nil;
|
|||
}
|
||||
}
|
||||
|
||||
- (id) initWithContext: (WOContext *)_context;
|
||||
{
|
||||
NSString *language;
|
||||
SOGoUserDefaults *userDefaults;
|
||||
WOResourceManager *resMgr;
|
||||
|
||||
if ((self = [super initWithContext: _context]))
|
||||
{
|
||||
userDefaults = [[_context activeUser] userDefaults];
|
||||
if (!userDefaults)
|
||||
userDefaults = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
language = [userDefaults language];
|
||||
resMgr = [[WOApplication application] resourceManager];
|
||||
ASSIGN (locale, [resMgr localeForLanguageNamed: language]);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[locale release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (WOResponse *) responseWithStatus: (unsigned int) status
|
||||
{
|
||||
WOResponse *response;
|
||||
|
|
|
@ -603,9 +603,6 @@ static NSArray *tasksFields = nil;
|
|||
}
|
||||
else if (abs(delta = [date dayOfCommonEra] - [now dayOfCommonEra]) < 7)
|
||||
{
|
||||
WOResourceManager *resMgr = [[WOApplication application] resourceManager];
|
||||
NSString *language = [[[context activeUser] userDefaults] language];
|
||||
NSDictionary *locale = [resMgr localeForLanguageNamed: language];
|
||||
NSString *dayOfWeek = [[locale objectForKey: NSWeekDayNameArray] objectAtIndex: [date dayOfWeek]];
|
||||
if (delta < 7)
|
||||
// Wihtin the next 7 days
|
||||
|
@ -740,6 +737,38 @@ static NSArray *tasksFields = nil;
|
|||
}
|
||||
}
|
||||
|
||||
static inline NSString* _userStateInEvent (NSArray *event)
|
||||
{
|
||||
unsigned int count, max;
|
||||
NSString *state;
|
||||
NSArray *participants, *states;
|
||||
SOGoUser *user;
|
||||
|
||||
participants = nil;
|
||||
state = nil;
|
||||
|
||||
participants = [event objectAtIndex: eventPartMailsIndex];
|
||||
|
||||
// We guard ourself against bogus value coming from the quick tables
|
||||
if ([participants isKindOfClass: [NSArray class]])
|
||||
{
|
||||
states = [event objectAtIndex: eventPartStatesIndex];
|
||||
count = 0;
|
||||
max = [participants count];
|
||||
user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex]
|
||||
roles: nil];
|
||||
while (state == nil && count < max)
|
||||
{
|
||||
if ([user hasEmail: [participants objectAtIndex: count]])
|
||||
state = [states objectAtIndex: count];
|
||||
else
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /so/:username/Calendar/eventslist List events
|
||||
* @apiVersion 1.0.0
|
||||
|
@ -755,34 +784,39 @@ static NSArray *tasksFields = nil;
|
|||
* @apiParam {String} [search] Search field criteria. Either title_Category_Location or entireContent.
|
||||
* @apiParam {String} [value] String to match
|
||||
*
|
||||
* @apiSuccess (Success 200) {String[]} fields List of fields for each event definition
|
||||
* @apiSuccess (Success 200) {String[]} events List of events
|
||||
* @apiSuccess (Success 200) {String} events.c_name Event UID
|
||||
* @apiSuccess (Success 200) {String} events.c_folder Calendar ID
|
||||
* @apiSuccess (Success 200) {String} events.calendarName Human readable name of calendar
|
||||
* @apiSuccess (Success 200) {Number} events.c_status 0: Cancelled, 1: Normal, 2: Tentative
|
||||
* @apiSuccess (Success 200) {Number} events.c_isopaque 1 if event is opaque (not transparent)
|
||||
* @apiSuccess (Success 200) {String} events.c_title Title
|
||||
* @apiSuccess (Success 200) {String} events.c_startdate Epoch time of start date
|
||||
* @apiSuccess (Success 200) {String} events.c_enddate Epoch time of end date
|
||||
* @apiSuccess (Success 200) {String} events.c_location Event's location
|
||||
* @apiSuccess (Success 200) {Number} events.c_isallday 1 if event lasts all day
|
||||
* @apiSuccess (Success 200) {Number} events.c_classification 0: Public, 1: Private, 2: Confidential
|
||||
* @apiSuccess (Success 200) {String} events.c_category Category
|
||||
* @apiSuccess (Success 200) {Number} events.c_priority Priority (0 to 9)
|
||||
* @apiSuccess (Success 200) {String[]} events.c_partmails Participants email addresses
|
||||
* @apiSuccess (Success 200) {String[]} events.c_partstates Participants states
|
||||
* @apiSuccess (Success 200) {String} events.c_owner Event's owner
|
||||
* @apiSuccess (Success 200) {Number} events.c_iscycle 1 if the event is cyclic/recurring
|
||||
* @apiSuccess (Success 200) {Number} events.c_nextalarm Epoch time of next alarm
|
||||
* @apiSuccess (Success 200) {String} [events.c_recurrence_id] Recurrence ID if event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.isException 1 if recurrence is an exception
|
||||
* @apiSuccess (Success 200) {Number} events.viewable 1 if active user can view the event
|
||||
* @apiSuccess (Success 200) {Number} events.editable 1 if active user can edit the event
|
||||
* @apiSuccess (Success 200) {Number} events.erasable 1 if active user can erase the event
|
||||
* @apiSuccess (Success 200) {Number} events.ownerIsOrganizer 1 if owner is also the organizer
|
||||
* @apiSuccess (Success 200) {String} events.formatted_startdate Localized start date
|
||||
* @apiSuccess (Success 200) {String} events.formatted_enddate Localized end date
|
||||
* @apiSuccess (Success 200) {String[]} fields List of fields for each event definition
|
||||
* @apiSuccess (Success 200) {String} events.<month>.month Full month and year
|
||||
* @apiSuccess (Success 200) {Object} events.<month>.days List of days
|
||||
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.month Abbreviated month name
|
||||
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.monthDay Day of month
|
||||
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.weekDay Abbreviated weeday name
|
||||
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.events List of events
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_name Event UID
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_folder Calendar ID
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.calendarName Human readable name of calendar
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_status 0: Cancelled, 1: Normal, 2: Tentative
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_isopaque 1 if event is opaque (not transparent)
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_title Title
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_startdate Epoch time of start date
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_enddate Epoch time of end date
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_location Event's location
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_isallday 1 if event lasts all day
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_classification 0: Public, 1: Private, 2: Confidential
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_category Category
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_priority Priority (0 to 9)
|
||||
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.events.c_partmails Participants email addresses
|
||||
* @apiSuccess (Success 200) {String[]} events.<month>.days.<date>.events.c_partstates Participants states
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.c_owner Event's owner
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_iscycle 1 if the event is cyclic/recurring
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.c_nextalarm Epoch time of next alarm
|
||||
* @apiSuccess (Success 200) {String} [events.<month>.days.<date>.events.c_recurrence_id] Recurrence ID if event is cyclic
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.isException 1 if recurrence is an exception
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.viewable 1 if active user can view the event
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.editable 1 if active user can edit the event
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.erasable 1 if active user can erase the event
|
||||
* @apiSuccess (Success 200) {Number} events.<month>.days.<date>.events.ownerIsOrganizer 1 if owner is also the organizer
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.formatted_startdate Localized start date
|
||||
* @apiSuccess (Success 200) {String} events.<month>.days.<date>.events.formatted_enddate Localized end date
|
||||
*/
|
||||
- (WOResponse *) eventsListAction
|
||||
{
|
||||
|
@ -791,15 +825,18 @@ static NSArray *tasksFields = nil;
|
|||
NSCalendarDate *date;
|
||||
NSDictionary *data;
|
||||
NSEnumerator *events;
|
||||
NSMutableArray *fields, *newEvents, *newEvent;
|
||||
NSString *sort, *ascending, *weekDay;
|
||||
unsigned int interval;
|
||||
NSMutableArray *fields, *dayEvents, *newEvent, *allDayEvents;
|
||||
NSMutableDictionary *allEvents, *monthData, *monthEvents, *dayData;
|
||||
NSString *sort, *ascending, *day, *weekDay, *month, *userState;
|
||||
unsigned int interval, count, max;
|
||||
SEL sortSelector;
|
||||
|
||||
[self _setupContext];
|
||||
[self saveFilterValue: @"EventsFilterState"];
|
||||
[self saveSortValue: @"EventsSortingState"];
|
||||
|
||||
newEvents = [NSMutableArray array];
|
||||
allEvents = [NSMutableDictionary dictionary];
|
||||
allDayEvents = [NSMutableArray array];
|
||||
events = [[self _fetchFields: eventsFields
|
||||
forComponentOfType: @"vevent"] objectEnumerator];
|
||||
while ((oldEvent = [events nextObject]))
|
||||
|
@ -813,6 +850,33 @@ static NSArray *tasksFields = nil;
|
|||
if ([enabledWeekDays count] && ![enabledWeekDays containsObject: weekDay])
|
||||
continue;
|
||||
|
||||
month = [date descriptionWithCalendarFormat: @"%Y%m" locale: locale];
|
||||
day = [date shortDateString];
|
||||
if (!(monthData = [allEvents objectForKey: month]))
|
||||
{
|
||||
monthEvents = [NSMutableDictionary dictionary];
|
||||
monthData = [NSMutableDictionary
|
||||
dictionaryWithObjectsAndKeys:
|
||||
[date descriptionWithCalendarFormat: @"%B %Y" locale: locale], @"month",
|
||||
monthEvents, @"days",
|
||||
nil];
|
||||
[allEvents setObject: monthData forKey: month];
|
||||
}
|
||||
monthEvents = [monthData objectForKey: @"days"];
|
||||
if (!(dayData = [monthEvents objectForKey: day]))
|
||||
{
|
||||
dayEvents = [NSMutableArray array];
|
||||
[allDayEvents addObject: dayEvents];
|
||||
dayData = [NSMutableDictionary
|
||||
dictionaryWithObjectsAndKeys:
|
||||
[date descriptionWithCalendarFormat: @"%b" locale: locale], @"month",
|
||||
[date descriptionWithCalendarFormat: @"%e" locale: locale], @"monthDay",
|
||||
[date descriptionWithCalendarFormat: @"%a" locale: locale], @"weekDay",
|
||||
dayEvents, @"events",
|
||||
nil];
|
||||
[monthEvents setObject: dayData forKey: day];
|
||||
}
|
||||
dayEvents = [dayData objectForKey: @"events"];
|
||||
newEvent = [NSMutableArray arrayWithArray: oldEvent];
|
||||
isAllDay = [[oldEvent objectAtIndex: eventIsAllDayIndex] boolValue];
|
||||
[newEvent addObject: [self _formattedDateForSeconds: interval
|
||||
|
@ -820,31 +884,40 @@ static NSArray *tasksFields = nil;
|
|||
interval = [[oldEvent objectAtIndex: eventEndDateIndex] intValue];
|
||||
[newEvent addObject: [self _formattedDateForSeconds: interval
|
||||
forAllDay: isAllDay]];
|
||||
[newEvents addObject: newEvent];
|
||||
userState = _userStateInEvent(oldEvent);
|
||||
if (userState != nil) [newEvent addObject: userState];
|
||||
[dayEvents addObject: newEvent];
|
||||
}
|
||||
|
||||
// Sort affects events of each day (but not the days)
|
||||
sort = [[context request] formValueForKey: @"sort"];
|
||||
if ([sort isEqualToString: @"title"])
|
||||
[newEvents sortUsingSelector: @selector (compareEventsTitleAscending:)];
|
||||
sortSelector = @selector (compareEventsTitleAscending:);
|
||||
else if ([sort isEqualToString: @"end"])
|
||||
[newEvents sortUsingSelector: @selector (compareEventsEndDateAscending:)];
|
||||
sortSelector = @selector (compareEventsEndDateAscending:);
|
||||
else if ([sort isEqualToString: @"location"])
|
||||
[newEvents sortUsingSelector: @selector (compareEventsLocationAscending:)];
|
||||
sortSelector = @selector (compareEventsLocationAscending:);
|
||||
else if ([sort isEqualToString: @"calendarName"])
|
||||
[newEvents sortUsingSelector: @selector (compareEventsCalendarNameAscending:)];
|
||||
sortSelector = @selector (compareEventsCalendarNameAscending:);
|
||||
else
|
||||
[newEvents sortUsingSelector: @selector (compareEventsStartDateAscending:)];
|
||||
|
||||
sortSelector = @selector (compareEventsStartDateAscending:);
|
||||
ascending = [[context request] formValueForKey: @"asc"];
|
||||
if (![ascending boolValue])
|
||||
[newEvents reverseArray];
|
||||
max = [allDayEvents count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
dayEvents = [allDayEvents objectAtIndex: count];
|
||||
[dayEvents sortUsingSelector: sortSelector];
|
||||
if (![ascending boolValue])
|
||||
[dayEvents reverseArray];
|
||||
}
|
||||
|
||||
// Fields names
|
||||
fields = [NSMutableArray arrayWithArray: eventsFields];
|
||||
[fields addObject: @"formatted_startdate"];
|
||||
[fields addObject: @"formatted_enddate"];
|
||||
[fields addObject: @"userState"];
|
||||
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys: fields, @"fields", newEvents, @"events", nil];
|
||||
data = [NSDictionary dictionaryWithObjectsAndKeys: fields, @"fields", allEvents, @"events", nil];
|
||||
|
||||
return [self _responseWithData: data];
|
||||
}
|
||||
|
@ -927,38 +1000,6 @@ static inline void _feedBlockWithDayBasedData (NSMutableDictionary *block, unsig
|
|||
return block;
|
||||
}
|
||||
|
||||
static inline NSString* _userStateInEvent (NSArray *event)
|
||||
{
|
||||
unsigned int count, max;
|
||||
NSString *state;
|
||||
NSArray *participants, *states;
|
||||
SOGoUser *user;
|
||||
|
||||
participants = nil;
|
||||
state = nil;
|
||||
|
||||
participants = [event objectAtIndex: eventPartMailsIndex];
|
||||
|
||||
// We guard ourself against bogus value coming from the quick tables
|
||||
if ([participants isKindOfClass: [NSArray class]])
|
||||
{
|
||||
states = [event objectAtIndex: eventPartStatesIndex];
|
||||
count = 0;
|
||||
max = [participants count];
|
||||
user = [SOGoUser userWithLogin: [event objectAtIndex: eventOwnerIndex]
|
||||
roles: nil];
|
||||
while (state == nil && count < max)
|
||||
{
|
||||
if ([user hasEmail: [participants objectAtIndex: count]])
|
||||
state = [states objectAtIndex: count];
|
||||
else
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
- (void) _fillBlocks: (NSArray *) blocks
|
||||
withEvent: (NSArray *) event
|
||||
withNumber: (NSNumber *) number
|
||||
|
@ -1306,6 +1347,9 @@ _computeBlocksPosition (NSArray *blocks)
|
|||
* @apiParam {Number} [ed] Period end date (YYYYMMDD)
|
||||
* @apiParam {String} [view] Formatting view. Either dayview, multicolumndayview, weekview or monthview.
|
||||
*
|
||||
* @apiSuccess (Success 200) {Object[]} days
|
||||
* @apiSuccess (Success 200) {String} days.date
|
||||
* @apiSuccess (Success 200) {Number} days.number
|
||||
* @apiSuccess (Success 200) {String[]} eventsFields List of fields for each event definition
|
||||
* @apiSuccess (Success 200) {String[]} events List of events
|
||||
* @apiSuccess (Success 200) {String} events.c_name Event UID
|
||||
|
|
|
@ -383,40 +383,39 @@
|
|||
<span ng-bind="list.filterpopup() | loc"><!-- active filter --></span>
|
||||
</md-subheader>
|
||||
<md-list class="sg-section-list"
|
||||
ng-class="{ 'sg-list-selectable': list.mode.multiple }">
|
||||
<md-list-item ng-repeat="event in list.component.$events"
|
||||
aria-label="{{::event.c_title}}"
|
||||
ng-click="list.openEvent($event, event)">
|
||||
<div class="md-secondary sg-avatar-selectable"
|
||||
label:aria-label="Toggle item"
|
||||
ng-class="[event.getClassName('fg'), { 'sg-avatar-selected' : event.selected }]"
|
||||
ng-click="list.toggleComponentSelection($event, event)">
|
||||
<div class="sg-color-chip"
|
||||
ng-class="::event.getClassName('bg')"><!-- calendar color --></div>
|
||||
</div>
|
||||
<div class="sg-category"
|
||||
ng-repeat="category in ::event.categories | limitTo:5"
|
||||
ng-class="::'bg-category' + category"
|
||||
ng-style="::{ left: ($index * 3) + 'px' }"><!-- calendar color --></div>
|
||||
<div class="sg-tile-content">
|
||||
<div class="sg-md-subhead">
|
||||
<div>
|
||||
<span ng-show="::event.c_priority" class="sg-priority" ng-bind="::event.c_priority"><!-- priority --></span>
|
||||
<span ng-bind="::event.c_title"><!-- title --></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sg-md-body">
|
||||
<div ng-bind="::event.c_location"><!-- location --></div>
|
||||
<div class="sg-tile-date" ng-bind="::event.formatted_startdate"><!-- start --></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sg-tile-icons">
|
||||
<md-icon ng-show="::event.c_iscycle">repeat</md-icon>
|
||||
<md-icon ng-show="::event.c_nextalarm">alarm</md-icon>
|
||||
</div>
|
||||
<md-divider><!-- divider --></md-divider>
|
||||
ng-class="{ 'sg-list-selectable': list.mode.multiple }"
|
||||
ng-repeat="(key, monthData) in list.component.$events">
|
||||
<md-list-item aria-label="{{::monthData.month}}"
|
||||
md-colors="::{ color: 'default-primary-800' }">
|
||||
<span ng-bind="::monthData.month"><!-- month name --></span>
|
||||
</md-list-item>
|
||||
<md-list-item disabled="disabled" ng-if="list.component.$events.length == 0">
|
||||
<md-list-item layout="row" layout-align="start start"
|
||||
ng-repeat="dayData in monthData.days">
|
||||
<div class="sg-calendar-date">
|
||||
<span class="sg-calendar-day sg-md-display-1--narrow"
|
||||
md-colors="::{ color: 'default-primary-800' }"
|
||||
ng-bind="::dayData.monthDay"><!-- month day --></span>
|
||||
<span>
|
||||
<div md-colors="::{ color: 'default-primary-400' }"
|
||||
ng-bind="::dayData.weekDay"><!-- week day --></div>
|
||||
<div class="md-caption"
|
||||
md-colors="::{ color: 'default-primary-800' }"
|
||||
ng-bind="::dayData.month"><!-- month --></div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="sg-calendar-list md-flex">
|
||||
<sg-calendar-list-event
|
||||
ng-repeat="event in dayData.events"
|
||||
sg-component="event"
|
||||
sg-click="list.openEvent($event, clickComponent)">
|
||||
<!-- directive -->
|
||||
</sg-calendar-list-event>
|
||||
<md-divider><!-- divider --></md-divider>
|
||||
</div>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
<md-list ng-if="!list.component.$events">
|
||||
<md-list-item disabled="disabled">
|
||||
<p class="sg-md-caption"><var:string label:value="No events for selected criteria"/></p>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
|
@ -447,8 +446,8 @@
|
|||
<div class="sg-tile-content">
|
||||
<div class="sg-md-subhead">
|
||||
<div>
|
||||
<span ng-show="::task.c_priority" class="sg-priority">{{::task.c_priority}}</span>
|
||||
{{::task.c_title}}
|
||||
<span ng-show="::task.c_priority" class="sg-priority" ng-bind="::task.c_priority"><!-- priority --></span>
|
||||
<span ng-bind="::task.c_title"><!-- title --></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sg-md-body">
|
||||
|
|
|
@ -419,12 +419,27 @@
|
|||
fields.splice(_.indexOf(fields, 'c_recurrence_id'), 1, 'occurrenceId');
|
||||
|
||||
// Instanciate Component objects
|
||||
_.reduce(data[type], function(components, componentData, i) {
|
||||
var data = _.zipObject(fields, componentData), component;
|
||||
component = new Component(data);
|
||||
components.push(component);
|
||||
return components;
|
||||
}, components);
|
||||
|
||||
if (type == 'events') {
|
||||
_.forEach(data[type], function(monthData, month) {
|
||||
_.forEach(monthData.days, function(dayData, day) {
|
||||
_.forEach(dayData.events, function(componentData, i) {
|
||||
var data = _.zipObject(fields, componentData), component;
|
||||
component = new Component(data);
|
||||
dayData.events[i] = component;
|
||||
});
|
||||
});
|
||||
});
|
||||
components = data[type];
|
||||
}
|
||||
else if (type == 'tasks') {
|
||||
_.reduce(data[type], function(components, componentData, i) {
|
||||
var data = _.zipObject(fields, componentData), component;
|
||||
component = new Component(data);
|
||||
components.push(component);
|
||||
return components;
|
||||
}, components);
|
||||
}
|
||||
|
||||
Component.$log.debug('list of ' + type + ' ready (' + components.length + ')');
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* sgCalendarListEvent - An event block to be displayed in a list
|
||||
* @memberof SOGo.SchedulerUI
|
||||
* @restrict element
|
||||
* @param {object} sgComponent - the Component object.
|
||||
* @param {function} sgClick - the function to call when clicking on the event.
|
||||
* Two variables are available: clickEvent (the event that triggered the mouse click),
|
||||
* and clickComponent (a Component object)
|
||||
*
|
||||
* @example:
|
||||
|
||||
<sg-calendar-list-event
|
||||
ng-repeat="event in dayData.events"
|
||||
sg-component="event"
|
||||
sg-click="list.openEvent($event, clickComponent)" />
|
||||
*/
|
||||
sgCalendarListEvent.$inject = ['CalendarSettings'];
|
||||
function sgCalendarListEvent(CalendarSettings) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
component: '=sgComponent',
|
||||
clickComponent: '&sgClick'
|
||||
},
|
||||
replace: true,
|
||||
template: template,
|
||||
link: link
|
||||
};
|
||||
|
||||
function template(tElem, tAttrs) {
|
||||
return [
|
||||
'<div class="sg-event"',
|
||||
' ng-click="clickComponent({clickEvent: $event, clickComponent: component})">',
|
||||
// Categories color stripes
|
||||
' <div class="sg-category" ng-repeat="category in ::component.categories"',
|
||||
' ng-class="::(\'bg-category\' + category)"',
|
||||
' ng-style="::{ right: ($index * 3) + \'px\' }"></div>',
|
||||
// Priority
|
||||
' <span ng-show="::component.c_priority" class="sg-priority" ng-bind="::component.c_priority"></span>',
|
||||
// Summary
|
||||
' {{ ::component.c_title }}',
|
||||
' <span class="icons">',
|
||||
// Component is reccurent
|
||||
' <md-icon ng-if="::component.occurrenceId" class="material-icons icon-repeat"></md-icon>',
|
||||
// Component has an alarm
|
||||
' <md-icon ng-if="::component.c_nextalarm" class="material-icons icon-alarm"></md-icon>',
|
||||
// Component is confidential
|
||||
' <md-icon ng-if="::component.c_classification == 2" class="material-icons icon-visibility-off"></md-icon>',
|
||||
// Component is private
|
||||
' <md-icon ng-if="::component.c_classification == 1" class="material-icons icon-vpn-key"></md-icon>',
|
||||
' </span>',
|
||||
// Time
|
||||
' <div class="secondary" ng-if="::!component.c_isallday">',
|
||||
' <md-icon>access_time</md-icon> {{::component.starthour}}',
|
||||
' </div>',
|
||||
// Location
|
||||
' <div class="secondary" ng-if="::component.c_location">',
|
||||
' <md-icon>place</md-icon> {{::component.c_location}}',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
function link(scope, iElement, attrs) {
|
||||
/**
|
||||
* No data binding here since the view is completely redraw when
|
||||
* a change is detected.
|
||||
*/
|
||||
|
||||
if (scope.component.viewable)
|
||||
iElement.addClass('md-clickable');
|
||||
|
||||
// Add class for user's participation state
|
||||
if (scope.component.userstate)
|
||||
iElement.addClass('sg-event--' + scope.component.userstate);
|
||||
|
||||
// Set background color
|
||||
iElement.addClass('bg-folder' + scope.component.pid);
|
||||
iElement.addClass('contrast-bdr-folder' + scope.component.pid);
|
||||
|
||||
// Add class for transparency
|
||||
if (scope.component.c_isopaque === 0)
|
||||
iElement.addClass('sg-event--transparent');
|
||||
|
||||
// Add class for cancelled event
|
||||
if (scope.component.c_status === 0)
|
||||
iElement.addClass('sg-event--cancelled');
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
.module('SOGo.SchedulerUI')
|
||||
.directive('sgCalendarListEvent', sgCalendarListEvent);
|
||||
})();
|
|
@ -337,6 +337,10 @@ html p {
|
|||
line-height: $lineHeight;
|
||||
font-weight: $sg-font-regular;
|
||||
}
|
||||
.#{$md}-display-1--narrow {
|
||||
@extend .#{$md}-display-1;
|
||||
letter-spacing: -0.1em;
|
||||
}
|
||||
.#{$md}-display-2 {
|
||||
$lineHeight: $sg-line-height-7;
|
||||
font-size: $sg-font-size-7;
|
||||
|
|
|
@ -494,6 +494,43 @@ $quarter_height: 10px;
|
|||
.ghostEndHour {
|
||||
bottom: -14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Middle list view of events
|
||||
|
||||
.view-list {
|
||||
.sg-calendar-date {
|
||||
white-space: nowrap;
|
||||
width: 72px;
|
||||
min-width: 72px;
|
||||
> * {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.sg-calendar-day {
|
||||
font-weight: 200;
|
||||
padding-right: 3px;
|
||||
}
|
||||
.sg-calendar-list {
|
||||
padding-bottom: 16px;
|
||||
md-divider {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
.sg-event {
|
||||
margin: 0 0 4px 0;
|
||||
padding: $bl;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
.eventInside {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.text {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Multicolumn day cell that contains the calendar name
|
||||
|
|
Loading…
Reference in New Issue