Merge branch 'exportCal' into reminders

pull/1/head
yflory 4 years ago
commit 9e47383083

@ -0,0 +1,200 @@
// This file is used when a user tries to export the entire CryptDrive.
// Calendars will be exported using this format instead of plain text.
define([
'/customize/pages.js',
], function (Pages) {
var module = {};
var getICSDate = function (str) {
var date = new Date(str);
var m = date.getUTCMonth() + 1;
var d = date.getUTCDate();
var h = date.getUTCHours();
var min = date.getUTCMinutes();
var year = date.getUTCFullYear().toString();
var month = m < 10 ? "0" + m : m.toString();
var day = d < 10 ? "0" + d : d.toString();
var hours = h < 10 ? "0" + h : h.toString();
var minutes = min < 10 ? "0" + min : min.toString();
return year + month + day + "T" + hours + minutes + "00Z";
}
var getDate = function (str, end) {
var date = new Date(str);
if (end) {
date.setDate(date.getDate() + 1);
}
var m = date.getUTCMonth() + 1;
var d = date.getUTCDate();
var year = date.getUTCFullYear().toString();
var month = m < 10 ? "0" + m : m.toString();
var day = d < 10 ? "0" + d : d.toString();
return year+month+day;
};
var MINUTE = 60;
var HOUR = MINUTE * 60;
var DAY = HOUR * 24;
module.main = function (userDoc) {
var content = userDoc.content;
var md = userDoc.metadata;
var ICS = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//CryptPad//CryptPad Calendar '+Pages.versionString+'//EN',
'METHOD:PUBLISH',
];
Object.keys(content).forEach(function (uid) {
var data = content[uid];
// DTSTAMP: now...
// UID: uid
var start, end;
if (data.isAllDay && data.startDay && data.endDay) {
start = "DTSTART;VALUE=DATE:" + getDate(data.startDay);
end = "DTEND;VALUE=DATE:" + getDate(data.endDay, true);
} else {
start = "DTSTART:"+getICSDate(data.start);
end = "DTEND:"+getICSDate(data.end);
}
Array.prototype.push.apply(ICS, [
'BEGIN:VEVENT',
'DTSTAMP:'+getICSDate(+new Date()),
'UID:'+uid,
start,
end,
'SUMMARY:'+ data.title,
'LOCATION:'+ data.location,
]);
if (Array.isArray(data.reminders)) {
data.reminders.forEach(function (valueMin) {
var time = valueMin * 60;
var days = Math.floor(time / DAY);
time -= days * DAY;
var hours = Math.floor(time / HOUR);
time -= hours * HOUR;
var minutes = Math.floor(time / MINUTE);
time -= minutes * MINUTE;
var seconds = time;
var str = "-P" + days + "D";
if (hours || minutes || seconds) {
str += "T" + hours + "H" + minutes + "M" + seconds + "S";
}
Array.prototype.push.apply(ICS, [
'BEGIN:VALARM',
'ACTION:DISPLAY',
'DESCRIPTION:This is an event reminder',
'TRIGGER:'+str,
'END:VALARM'
]);
});
}
if (Array.isArray(data.cp_hidden)) {
Array.prototype.push.apply(ICS, data.cp_hidden);
}
ICS.push('END:VEVENT');
});
ICS.push('END:VCALENDAR');
return new Blob([ ICS.join('\n') ], { type: 'text/calendar;charset=utf-8' });
};
module.import = function (content, id, cb) {
require(['/lib/ical.min.js'], function () {
var ICAL = window.ICAL;
var res = {};
try {
var jcalData = ICAL.parse(content);
var vcalendar = new ICAL.Component(jcalData);
} catch (e) {
return void cb(e);
}
var method = vcalendar.getFirstPropertyValue('method');
if (method !== "PUBLISH") { return void cb('NOT_SUPPORTED'); }
var events = vcalendar.getAllSubcomponents('vevent');
events.forEach(function (ev) {
var uid = ev.getFirstPropertyValue('uid');
if (!uid) { return; }
// Get start and end time
var isAllDay = false;
var start = ev.getFirstPropertyValue('dtstart');
var end = ev.getFirstPropertyValue('dtend');
if (start.isDate && end.isDate) {
isAllDay = true;
start = String(start);
end.adjust(-1); // Substract one day
end = String(end);
} else {
start = +start.toJSDate();
end = +end.toJSDate();
}
// Store other properties
var used = ['dtstart', 'dtend', 'uid', 'summary', 'location', 'dtstamp'];
var hidden = [];
ev.getAllProperties().forEach(function (p) {
if (used.indexOf(p.name) !== -1) { return; }
// This is an unused property
hidden.push(p.toICALString());
});
// Get reminders
var reminders = [];
ev.getAllSubcomponents('valarm').forEach(function (al) {
var action = al.getFirstPropertyValue('action');
if (action !== 'DISPLAY') {
// XXX email: maybe keep a notification in CryptPad?
hidden.push(al.toString());
return;
}
var trigger = al.getFirstPropertyValue('trigger');
var minutes = -trigger.toSeconds() / 60;
if (reminders.indexOf(minutes) === -1) { reminders.push(minutes); }
});
// Create event
res[uid] = {
calendarId: id,
id: uid,
category: 'time',
title: ev.getFirstPropertyValue('summary'),
location: ev.getFirstPropertyValue('location'),
isAllDay: isAllDay,
start: start,
end: end,
reminders: reminders,
cp_hidden: hidden
};
if (!hidden.length) { delete res[uid].cp_hidden; }
if (!reminders.length) { delete res[uid].reminders; }
});
cb(null, res);
});
};
return module;
});

@ -16,12 +16,14 @@ define([
'/customize/messages.js', '/customize/messages.js',
'/customize/application_config.js', '/customize/application_config.js',
'/lib/calendar/tui-calendar.min.js', '/lib/calendar/tui-calendar.min.js',
'/calendar/export.js',
'/common/inner/share.js', '/common/inner/share.js',
'/common/inner/access.js', '/common/inner/access.js',
'/common/inner/properties.js', '/common/inner/properties.js',
'/common/jscolor.js', '/common/jscolor.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/lib/calendar/tui-calendar.min.css', 'css!/lib/calendar/tui-calendar.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/calendar/app-calendar.less', 'less!/calendar/app-calendar.less',
@ -43,9 +45,11 @@ define([
Messages, Messages,
AppConfig, AppConfig,
Calendar, Calendar,
Export,
Share, Access, Properties Share, Access, Properties
) )
{ {
var SaveAs = window.saveAs;
var APP = window.APP = { var APP = window.APP = {
calendars: {} calendars: {}
}; };
@ -113,6 +117,12 @@ Messages.calendar_noNotification = "None";
cb(null, obj); cb(null, obj);
}); });
}; };
var importICSCalendar = function (data, cb) {
APP.module.execCommand('IMPORT_ICS', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var newEvent = function (data, cb) { var newEvent = function (data, cb) {
APP.module.execCommand('CREATE_EVENT', data, function (obj) { APP.module.execCommand('CREATE_EVENT', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); } if (obj && obj.error) { return void cb(obj.error); }
@ -469,6 +479,79 @@ Messages.calendar_noNotification = "None";
return true; return true;
} }
}); });
if (!data.readOnly) {
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-upload',
},
content: h('span', Messages.importButton),
action: function (e) {
UIElements.importContent('text/calendar', function (res) {
Export.import(res, id, function (err, json) {
if (err) { return void UI.warn(Messages.importError); }
importICSCalendar({
id: id,
json: json
}, function (err) {
if (err) { return void UI.warn(Messages.error); }
UI.log(Messages.saved);
});
});
}, {
accept: ['.ics']
})();
return true;
}
});
}
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-download',
},
content: h('span', Messages.exportButton),
action: function (e) {
e.stopPropagation();
var cal = APP.calendars[id];
var suggestion = Util.find(cal, ['content', 'metadata', 'title']);
var types = [];
types.push({
tag: 'a',
attributes: {
'data-value': '.ics',
'href': '#'
},
content: '.ics'
});
var dropdownConfig = {
text: '.ics', // Button initial text
caretDown: true,
options: types, // Entries displayed in the menu
isSelect: true,
initialValue: '.ics',
common: common
};
var $select = UIElements.createDropdown(dropdownConfig);
UI.prompt(Messages.exportPrompt,
Util.fixFileName(suggestion), function (filename)
{
if (!(typeof(filename) === 'string' && filename)) { return; }
var ext = $select.getValue();
filename = filename + ext;
var blob = Export.main(cal.content);
SaveAs(blob, filename);
}, {
typeInput: $select[0]
});
return true;
}
});
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {

@ -127,7 +127,7 @@ define([
dcAlert = undefined; dcAlert = undefined;
}; };
var importContent = function (type, f, cfg) { var importContent = UIElements.importContent = function (type, f, cfg) {
return function () { return function () {
var $files = $('<input>', {type:"file"}); var $files = $('<input>', {type:"file"});
if (cfg && cfg.accept) { if (cfg && cfg.accept) {

@ -516,6 +516,22 @@ define([
}); });
}; };
var importICSCalendar = function (ctx, data, cId, cb) {
var id = data.id;
var c = ctx.calendars[id];
if (!c || !c.proxy) { return void cb({error: "ENOENT"}); }
var json = data.json;
c.proxy.content = c.proxy.content || {};
Object.keys(json).forEach(function (uid) {
c.proxy.content[uid] = json[uid];
});
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
sendUpdate(ctx, c);
cb();
});
};
var openCalendar = function (ctx, data, cId, cb) { var openCalendar = function (ctx, data, cId, cb) {
var secret = Hash.getSecrets('calendar', data.hash, data.password); var secret = Hash.getSecrets('calendar', data.hash, data.password);
var hash = Hash.getEditHashFromKeys(secret); var hash = Hash.getEditHashFromKeys(secret);
@ -884,6 +900,10 @@ define([
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); } if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
return void importCalendar(ctx, data, clientId, cb); return void importCalendar(ctx, data, clientId, cb);
} }
if (cmd === 'IMPORT_ICS') {
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
return void importICSCalendar(ctx, data, clientId, cb);
}
if (cmd === 'ADD') { if (cmd === 'ADD') {
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); } if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
return void addCalendar(ctx, data, clientId, cb); return void addCalendar(ctx, data, clientId, cb);

Loading…
Cancel
Save