mirror of https://github.com/nextcloud/calendar
620 lines
18 KiB
JavaScript
620 lines
18 KiB
JavaScript
/**
|
|
* ownCloud - Calendar App
|
|
*
|
|
* @author Raghu Nayyar
|
|
* @author Georg Ehrke
|
|
* @copyright 2016 Raghu Nayyar <beingminimal@gmail.com>
|
|
* @copyright 2016 Georg Ehrke <oc.list@georgehrke.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
* License as published by the Free Software Foundation; either
|
|
* version 3 of the License, or any later version.
|
|
*
|
|
* This library 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 AFFERO GENERAL PUBLIC LICENSE for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
app.factory('objectConverter', function () {
|
|
'use strict';
|
|
|
|
/**
|
|
* structure of simple data
|
|
*/
|
|
var defaults = {
|
|
'summary': null,
|
|
'x-oc-calid': null,
|
|
'location': null,
|
|
'created': null,
|
|
'last-modified': null,
|
|
'organizer': null,
|
|
'x-oc-cruds': null,
|
|
'class': null,
|
|
'description': null,
|
|
'url': null,
|
|
'status': null,
|
|
'resources': null,
|
|
'alarm': null,
|
|
'attendee': null,
|
|
'categories': null,
|
|
'dtstart': null,
|
|
'dtend': null,
|
|
'repeating': null,
|
|
'rdate': null,
|
|
'rrule': null,
|
|
'exdate': null
|
|
};
|
|
|
|
var attendeeParameters = [
|
|
'role',
|
|
'rvsp',
|
|
'partstat',
|
|
'cutype',
|
|
'cn',
|
|
'delegated-from',
|
|
'delegated-to'
|
|
];
|
|
|
|
/**
|
|
* parsers of supported properties
|
|
*/
|
|
var simpleParser = {
|
|
date: function(data, vevent, key, parameters) {
|
|
parameters = (parameters || []).concat(['tzid']);
|
|
simpleParser._parseSingle(data, vevent, key, parameters, function(p) {
|
|
return (p.type === 'duration') ?
|
|
p.getFirstValue().toSeconds() :
|
|
p.getFirstValue().toJSDate();
|
|
});
|
|
},
|
|
dates: function(data, vevent, key, parameters) {
|
|
parameters = (parameters || []).concat(['tzid']);
|
|
simpleParser._parseMultiple(data, vevent, key, parameters, function(p) {
|
|
var values = p.getValues(),
|
|
usableValues = [];
|
|
for (var vKey in values) {
|
|
if (!values.hasOwnProperty(vKey)) {
|
|
continue;
|
|
}
|
|
|
|
usableValues.push(
|
|
(p.type === 'duration') ?
|
|
values[vKey].toSeconds() :
|
|
values[vKey].toJSDate()
|
|
);
|
|
}
|
|
|
|
return usableValues;
|
|
});
|
|
},
|
|
string: function(data, vevent, key, parameters) {
|
|
simpleParser._parseSingle(data, vevent, key, parameters, function(p) {
|
|
return p.isMultiValue ? p.getValues() : p.getFirstValue();
|
|
});
|
|
},
|
|
strings: function(data, vevent, key, parameters) {
|
|
simpleParser._parseMultiple(data, vevent, key, parameters, function(p) {
|
|
return p.isMultiValue ? p.getValues() : p.getFirstValue();
|
|
});
|
|
},
|
|
_parseSingle: function(data, vevent, key, parameters, valueParser) {
|
|
var prop = vevent.getFirstProperty(key);
|
|
if (!prop) {
|
|
return;
|
|
}
|
|
|
|
data[key] = {
|
|
parameters: simpleParser._parseParameters(prop, parameters),
|
|
type: prop.type
|
|
};
|
|
|
|
if (prop.isMultiValue) {
|
|
angular.extend(data[key], {
|
|
values: valueParser(prop)
|
|
});
|
|
} else {
|
|
angular.extend(data[key], {
|
|
value: valueParser(prop)
|
|
});
|
|
}
|
|
},
|
|
_parseMultiple: function(data, vevent, key, parameters, valueParser) {
|
|
data[key] = data[key] || [];
|
|
|
|
var properties = vevent.getAllProperties(key),
|
|
group = 0;
|
|
|
|
for (var pKey in properties) {
|
|
if (!properties.hasOwnProperty(pKey)) {
|
|
continue;
|
|
}
|
|
|
|
var values = valueParser(properties[pKey]);
|
|
var currentElement = {
|
|
group: group,
|
|
parameters: simpleParser._parseParameters(properties[pKey], parameters),
|
|
type: properties[pKey].type,
|
|
values: values
|
|
};
|
|
|
|
if (properties[pKey].isMultiValue) {
|
|
angular.extend(currentElement, {
|
|
values: valueParser(properties[pKey])
|
|
});
|
|
} else {
|
|
angular.extend(currentElement, {
|
|
value: valueParser(properties[pKey])
|
|
});
|
|
}
|
|
|
|
data[key].push(currentElement);
|
|
properties[pKey].setParameter('x-oc-group-id', group.toString());
|
|
group++;
|
|
}
|
|
},
|
|
_parseParameters: function(prop, para) {
|
|
var parameters = {};
|
|
|
|
if (!para) {
|
|
return parameters;
|
|
}
|
|
|
|
for (var i=0,l=para.length; i < l; i++) {
|
|
parameters[para[i]] = prop.getParameter(para[i]);
|
|
}
|
|
|
|
return parameters;
|
|
}
|
|
};
|
|
|
|
var simpleReader = {
|
|
date: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
|
|
parameters = (parameters || []).concat(['tzid']);
|
|
simpleReader._readSingle(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
|
|
if (v.type === 'duration') {
|
|
return ICAL.Duration.fromSeconds(v.value);
|
|
} else {
|
|
return ICAL.Time.fromJSDate(v.value);
|
|
}
|
|
});
|
|
},
|
|
dates: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
|
|
parameters = (parameters || []).concat(['tzid']);
|
|
simpleReader._readMultiple(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
|
|
var values = [];
|
|
|
|
for (var i=0, length=v.values.length; i < length; i++) {
|
|
if (v.type === 'duration') {
|
|
values.push(ICAL.Duration.fromSeconds(v.values[i]));
|
|
} else {
|
|
values.push(ICAL.Time.fromJSDate(v.values[i]));
|
|
}
|
|
}
|
|
|
|
return values;
|
|
});
|
|
},
|
|
string: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
|
|
simpleReader._readSingle(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
|
|
return isMultiValue ? v.values : v.value;
|
|
});
|
|
},
|
|
strings: function(vevent, oldSimpleData, newSimpleData, key, parameters) {
|
|
simpleReader._readMultiple(vevent, oldSimpleData, newSimpleData, key, parameters, function(v, isMultiValue) {
|
|
return isMultiValue ? v.values : v.value;
|
|
});
|
|
},
|
|
_readSingle: function(vevent, oldSimpleData, newSimpleData, key, parameters, valueReader) {
|
|
if (!newSimpleData[key]) {
|
|
return;
|
|
}
|
|
if (!newSimpleData[key].hasOwnProperty('value') && !newSimpleData[key].hasOwnProperty('values')) {
|
|
return;
|
|
}
|
|
var isMultiValue = newSimpleData[key].hasOwnProperty('values');
|
|
|
|
var prop = vevent.updatePropertyWithValue(key, valueReader(newSimpleData[key], isMultiValue));
|
|
simpleReader._readParameters(prop, newSimpleData[key], parameters);
|
|
},
|
|
_readMultiple: function(vevent, oldSimpleData, newSimpleData, key, parameters, valueReader) {
|
|
var oldGroups=[], properties=null, pKey=null, groupId;
|
|
|
|
oldSimpleData[key] = oldSimpleData[key] || [];
|
|
for (var i=0, oldLength=oldSimpleData[key].length; i < oldLength; i++) {
|
|
oldGroups.push(oldSimpleData[key][i].group);
|
|
}
|
|
|
|
newSimpleData[key] = newSimpleData[key] || [];
|
|
for (var j=0, newLength=newSimpleData[key].length; j < newLength; j++) {
|
|
var isMultiValue = newSimpleData[key][j].hasOwnProperty('values');
|
|
var value = valueReader(newSimpleData[key][j], isMultiValue);
|
|
|
|
if (oldGroups.indexOf(newSimpleData[key][j].group) === -1) {
|
|
var property = new ICAL.Property(key);
|
|
simpleReader._setProperty(property, value, isMultiValue);
|
|
simpleReader._readParameters(property, newSimpleData[key][j], parameters);
|
|
vevent.addProperty(property);
|
|
} else {
|
|
oldGroups.splice(oldGroups.indexOf(newSimpleData[key][j].group), 1);
|
|
|
|
properties = vevent.getAllProperties(key);
|
|
for (pKey in properties) {
|
|
if (!properties.hasOwnProperty(pKey)) {
|
|
continue;
|
|
}
|
|
|
|
groupId = properties[pKey].getParameter('x-oc-group-id');
|
|
if (groupId === null) {
|
|
continue;
|
|
}
|
|
if (groupId === newSimpleData[key][j].group) {
|
|
simpleReader._setProperty(properties[pKey], value, isMultiValue);
|
|
simpleReader._readParameters(properties[pKey], newSimpleData[key][j], parameters);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
properties = vevent.getAllProperties(key);
|
|
for (pKey in properties) {
|
|
if (!properties.hasOwnProperty(pKey)) {
|
|
continue;
|
|
}
|
|
|
|
groupId = properties[pKey].getParameter('x-oc-group-id');
|
|
if (groupId === null) {
|
|
continue;
|
|
}
|
|
if (oldGroups.indexOf(groupId) !== -1) {
|
|
delete properties[pKey];
|
|
}
|
|
}
|
|
},
|
|
_readParameters: function(prop, simple, para) {
|
|
if (!para) {
|
|
return;
|
|
}
|
|
if (!simple.parameters) {
|
|
return;
|
|
}
|
|
|
|
for (var i=0,l=para.length; i < l; i++) {
|
|
if (simple.parameters[para[i]]) {
|
|
prop.setParameter(para[i], simple.parameters[para[i]]);
|
|
} else {
|
|
prop.removeParameter(simple.parameters[para[i]]);
|
|
}
|
|
}
|
|
},
|
|
_setProperty: function(prop, value, isMultiValue) {
|
|
if (isMultiValue) {
|
|
prop.setValues(value);
|
|
} else {
|
|
prop.setValue(value);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* properties supported by event editor
|
|
*/
|
|
var simpleProperties = {
|
|
//General
|
|
'summary': {parser: simpleParser.string, reader: simpleReader.string},
|
|
'x-oc-calid': {parser: simpleParser.string, reader: simpleReader.string},
|
|
'location': {parser: simpleParser.string, reader: simpleReader.string},
|
|
'created': {parser: simpleParser.date, reader: simpleReader.date},
|
|
'last-modified': {parser: simpleParser.date, reader: simpleReader.date},
|
|
'categories': {parser: simpleParser.strings, reader: simpleReader.strings},
|
|
//attendees
|
|
'attendee': {parser: simpleParser.strings, reader: simpleReader.strings, parameters: attendeeParameters},
|
|
'organizer': {parser: simpleParser.string, reader: simpleReader.string},
|
|
//sharing
|
|
'x-oc-cruds': {parser: simpleParser.string, reader: simpleReader.string},
|
|
'class': {parser: simpleParser.string, reader: simpleReader.string},
|
|
//other
|
|
'description': {parser: simpleParser.string, reader: simpleReader.string},
|
|
'url': {parser: simpleParser.string, reader: simpleReader.string},
|
|
'status': {parser: simpleParser.string, reader: simpleReader.string},
|
|
'resources': {parser: simpleParser.strings, reader: simpleReader.strings}
|
|
};
|
|
|
|
function addZero(t) {
|
|
if (t < 10) {
|
|
t = '0' + t;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
function formatDate(d) {
|
|
return d.getFullYear() + '-' +
|
|
addZero(d.getMonth()) + '-' +
|
|
addZero(d.getDate());
|
|
}
|
|
|
|
function formatTime(d) {
|
|
return addZero(d.getHours()) + ':' +
|
|
addZero(d.getMinutes()) + ':' +
|
|
addZero(d.getSeconds());
|
|
|
|
}
|
|
|
|
/**
|
|
* specific parsers that check only one property
|
|
*/
|
|
var specificParser = {
|
|
alarm: function(data, vevent) {
|
|
data.alarm = data.alarm || [];
|
|
|
|
var alarms = vevent.getAllSubcomponents('valarm'),
|
|
group = 0;
|
|
for (var key in alarms) {
|
|
if (!alarms.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
|
|
var alarm = alarms[key];
|
|
var alarmData = {
|
|
group: group,
|
|
action: {},
|
|
trigger: {},
|
|
repeat: {},
|
|
duration: {},
|
|
attendee: []
|
|
};
|
|
|
|
simpleParser.string(alarmData, alarm, 'action');
|
|
simpleParser.date(alarmData, alarm, 'trigger');
|
|
simpleParser.string(alarmData, alarm, 'repeat');
|
|
simpleParser.string(alarmData, alarm, 'duration');
|
|
simpleParser.strings(alarmData, alarm, 'attendee', attendeeParameters);
|
|
|
|
if (alarm.hasProperty('trigger')) {
|
|
var trigger = alarm.getFirstProperty('trigger');
|
|
var related = trigger.getParameter('related');
|
|
if (related) {
|
|
alarmData.trigger.related = related;
|
|
} else {
|
|
alarmData.trigger.related = 'start';
|
|
}
|
|
}
|
|
|
|
data.alarm.push(alarmData);
|
|
|
|
alarm.getFirstProperty('action')
|
|
.setParameter('x-oc-group-id', group.toString());
|
|
group++;
|
|
}
|
|
},
|
|
date: function(data, vevent) {
|
|
var dtstart = vevent.getFirstPropertyValue('dtstart');
|
|
var dtend;
|
|
|
|
if (vevent.hasProperty('dtend')) {
|
|
dtend = vevent.getFirstPropertyValue('dtend');
|
|
} else if (vevent.hasProperty('duration')) {
|
|
dtend = dtstart.clone();
|
|
dtend.addDuration(vevent.getFirstPropertyValue('dtstart'));
|
|
} else {
|
|
dtend = dtstart.clone();
|
|
}
|
|
|
|
data.dtstart = {
|
|
date: formatDate(dtstart.toJSDate()),
|
|
time: formatTime(dtstart.toJSDate()),
|
|
type: dtstart.icaltype,
|
|
zone: dtstart.zone.toString()
|
|
};
|
|
data.dtend = {
|
|
date: formatDate(dtend.toJSDate()),
|
|
time: formatTime(dtend.toJSDate()),
|
|
type: dtend.icaltype,
|
|
zone: dtend.zone.toString()
|
|
};
|
|
data.allDay = (dtstart.icaltype === 'date' && dtend.icaltype === 'date');
|
|
},
|
|
repeating: function(data, vevent) {
|
|
var iCalEvent = new ICAL.Event(vevent);
|
|
|
|
data.repeating = iCalEvent.isRecurring();
|
|
simpleParser.dates(data, vevent, 'rdate');
|
|
simpleParser.string(data, vevent, 'rrule');
|
|
|
|
simpleParser.dates(data, vevent, 'exdate');
|
|
}
|
|
};
|
|
|
|
var specificReader = {
|
|
alarm: function(vevent, oldSimpleData, newSimpleData) {
|
|
var oldGroups=[], components=null, cKey=null, groupId, key='alarm';
|
|
|
|
oldSimpleData[key] = oldSimpleData[key] || [];
|
|
for (var i=0, oldLength=oldSimpleData[key].length; i < oldLength; i++) {
|
|
oldGroups.push(oldSimpleData[key][i].group);
|
|
}
|
|
|
|
newSimpleData[key] = newSimpleData[key] || [];
|
|
for (var j=0, newLength=newSimpleData[key].length; j < newLength; j++) {
|
|
var valarm;
|
|
if (oldGroups.indexOf(newSimpleData[key][j].group) === -1) {
|
|
valarm = new ICAL.Component('VALARM');
|
|
vevent.addSubcomponent(valarm);
|
|
} else {
|
|
oldGroups.splice(oldGroups.indexOf(newSimpleData[key][j].group), 1);
|
|
|
|
|
|
components = vevent.getAllSubcomponents('VALARM');
|
|
for (cKey in components) {
|
|
if (!components.hasOwnProperty(cKey)) {
|
|
continue;
|
|
}
|
|
|
|
groupId = components[cKey].getFirstProperty('action').getParameter('x-oc-group-id');
|
|
if (groupId === null) {
|
|
continue;
|
|
}
|
|
if (groupId === newSimpleData[key][j].group) {
|
|
valarm = components[cKey];
|
|
}
|
|
}
|
|
}
|
|
|
|
simpleReader.string(valarm, {}, newSimpleData[key][j], 'action', []);
|
|
simpleReader.date(valarm, {}, newSimpleData[key][j], 'trigger', []);
|
|
simpleReader.string(valarm, {}, newSimpleData[key][j], 'repeat', []);
|
|
simpleReader.string(valarm, {}, newSimpleData[key][j], 'duration', []);
|
|
simpleReader.strings(valarm, {}, newSimpleData[key][j], 'attendee', attendeeParameters);
|
|
}
|
|
},
|
|
date: function(vevent, oldSimpleData, newSimpleData) {
|
|
delete vevent.dstart;
|
|
delete vevent.dtend;
|
|
delete vevent.duration;
|
|
|
|
var parseIntWrapper = function(str) {
|
|
return parseInt(str);
|
|
};
|
|
|
|
if (!ICAL.TimezoneService.has(newSimpleData.dtstart.zone)) {
|
|
throw {
|
|
kind: 'timezone_missing',
|
|
missing_timezone: newSimpleData.dtstart.zone
|
|
};
|
|
}
|
|
if (!ICAL.TimezoneService.has(newSimpleData.dtend.zone)) {
|
|
throw {
|
|
kind: 'timezone_missing',
|
|
missing_timezone: newSimpleData.dtend.zone
|
|
};
|
|
}
|
|
|
|
var dtstartDateParts = newSimpleData.dtstart.date.split('-').map(parseIntWrapper);
|
|
var dtstartTimeParts = newSimpleData.dtstart.time.split(':').map(parseIntWrapper);
|
|
var dtstartTz = ICAL.TimezoneService.get(newSimpleData.dtstart.zone);
|
|
var start = new ICAL.Time({
|
|
year: dtstartDateParts[0],
|
|
month: dtstartDateParts[1],
|
|
day: dtstartDateParts[2],
|
|
hour: dtstartTimeParts[0],
|
|
minute: dtstartTimeParts[1],
|
|
second: dtstartTimeParts[2],
|
|
isDate: newSimpleData.allDay
|
|
}, dtstartTz);
|
|
|
|
var dtendDateParts = newSimpleData.dtend.date.split('-').map(parseIntWrapper);
|
|
var dtendTimeParts = newSimpleData.dtend.time.split(':').map(parseIntWrapper);
|
|
var dtendTz = ICAL.TimezoneService.get(newSimpleData.dtend.zone);
|
|
var end = new ICAL.Time({
|
|
year: dtendDateParts[0],
|
|
month: dtendDateParts[1],
|
|
day: dtendDateParts[2],
|
|
hour: dtendTimeParts[0],
|
|
minute: dtendTimeParts[1],
|
|
second: dtendTimeParts[2],
|
|
isDate: newSimpleData.allDay
|
|
}, dtendTz);
|
|
|
|
var dtstart = new ICAL.Property('dtstart', vevent);
|
|
dtstart.setValue(start);
|
|
dtstart.setParameter('tzid', dtstartTz.tzid);
|
|
var dtend = new ICAL.Property('dtend', vevent);
|
|
dtend.setValue(end);
|
|
dtend.setParameter('tzid', dtendTz.tzid);
|
|
|
|
vevent.addProperty(dtstart);
|
|
vevent.addProperty(dtend);
|
|
},
|
|
repeating: function(vevent, oldSimpleData, newSimpleData) {
|
|
// We won't support exrule, because it's deprecated and barely used in the wild
|
|
if (newSimpleData.repeating === false) {
|
|
delete vevent.rdate;
|
|
delete vevent.rrule;
|
|
delete vevent.exdate;
|
|
|
|
return;
|
|
}
|
|
|
|
simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'rdate');
|
|
simpleReader.string(vevent, oldSimpleData, newSimpleData, 'rrule');
|
|
simpleReader.dates(vevent, oldSimpleData, newSimpleData, 'exdate');
|
|
}
|
|
};
|
|
|
|
return {
|
|
/**
|
|
* parse and expand jCal data to simple structure
|
|
* @param vevent object to be parsed
|
|
* @returns {{}}
|
|
*/
|
|
parse: function(vevent) {
|
|
var data=angular.extend({}, defaults), parser, parameters;
|
|
|
|
for (parser in specificParser) {
|
|
if (!specificParser.hasOwnProperty(parser)) {
|
|
continue;
|
|
}
|
|
|
|
specificParser[parser](data, vevent);
|
|
}
|
|
|
|
for (var key in simpleProperties) {
|
|
if (!simpleProperties.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
|
|
parser = simpleProperties[key].parser;
|
|
parameters = simpleProperties[key].parameters;
|
|
if (vevent.hasProperty(key)) {
|
|
parser(data, vevent, key, parameters);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
/**
|
|
* patch vevent with data from event editor
|
|
* @param vevent object to update
|
|
* @param oldSimpleData
|
|
* @param newSimpleData
|
|
* @returns {*}
|
|
*/
|
|
patch: function(vevent, oldSimpleData, newSimpleData) {
|
|
var key, reader, parameters;
|
|
|
|
oldSimpleData = angular.extend({}, defaults, oldSimpleData);
|
|
newSimpleData = angular.extend({}, defaults, newSimpleData);
|
|
|
|
for (key in simpleProperties) {
|
|
if (!simpleProperties.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
|
|
reader = simpleProperties[key].reader;
|
|
parameters = simpleProperties[key].parameters;
|
|
if (oldSimpleData[key] !== newSimpleData[key]) {
|
|
if (newSimpleData === null) {
|
|
delete vevent[key];
|
|
} else {
|
|
reader(vevent, oldSimpleData, newSimpleData, key, parameters);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (key in specificReader) {
|
|
if (!specificReader.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
|
|
reader = specificReader[key];
|
|
reader(vevent, oldSimpleData, newSimpleData);
|
|
}
|
|
}
|
|
};
|
|
});
|