You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpad/www/calendar/inner.js

1325 lines
50 KiB
JavaScript

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

define([
'jquery',
'json.sortify',
'/bower_components/chainpad-crypto/crypto.js',
'/common/toolbar.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/common-realtime.js',
'/common/clipboard.js',
'/common/inner/common-mediatag.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/customize/application_config.js',
'/lib/calendar/tui-calendar.min.js',
'/calendar/export.js',
'/lib/datepicker/flatpickr.js',
'/common/inner/share.js',
'/common/inner/access.js',
'/common/inner/properties.js',
'/common/jscolor.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/lib/calendar/tui-calendar.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/calendar/app-calendar.less',
], function (
$,
JSONSortify,
Crypto,
Toolbar,
nThen,
SFCommon,
Util,
Hash,
UI,
UIElements,
Realtime,
Clipboard,
MT,
h,
Messages,
AppConfig,
Calendar,
Export,
Flatpickr,
Share, Access, Properties
)
{
var SaveAs = window.saveAs;
var APP = window.APP = {
calendars: {}
};
var common;
var metadataMgr;
var sframeChan;
var onCalendarsUpdate = Util.mkEvent();
var newCalendar = function (data, cb) {
APP.module.execCommand('CREATE', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var updateCalendar = function (data, cb) {
APP.module.execCommand('UPDATE', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var deleteCalendar = function (data, cb) {
APP.module.execCommand('DELETE', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var importCalendar = function (data, cb) {
APP.module.execCommand('IMPORT', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
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) {
APP.module.execCommand('CREATE_EVENT', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var updateEvent = function (data, cb) {
APP.module.execCommand('UPDATE_EVENT', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var deleteEvent = function (data, cb) {
APP.module.execCommand('DELETE_EVENT', data, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
cb(null, obj);
});
};
var getContrast = function (color) {
var rgb = Util.hexToRGB(color);
// http://www.w3.org/TR/AERT#color-contrast
var brightness = Math.round(((parseInt(rgb[0]) * 299) +
(parseInt(rgb[1]) * 587) +
(parseInt(rgb[2]) * 114)) / 1000);
return (brightness > 125) ? '#424242' : '#EEEEEE';
};
var getWeekDays = function (large) {
var baseDate = new Date(2017, 0, 1); // just a Sunday
var weekDays = [];
for(var i = 0; i < 7; i++) {
weekDays.push(baseDate.toLocaleDateString(undefined, { weekday: 'long' }));
baseDate.setDate(baseDate.getDate() + 1);
}
if (!large) {
weekDays = weekDays.map(function (day) { return day.slice(0,3); });
}
return weekDays.map(function (day) { return day.replace(/^./, function (str) { return str.toUpperCase(); }); });
};
var getCalendars = function () {
var LOOKUP = {};
var TEAMS = {};
return Object.keys(APP.calendars).map(function (id) {
var c = APP.calendars[id];
if (c.hidden || c.restricted || c.loading) { return; }
var md = Util.find(c, ['content', 'metadata']);
if (!md) { return void console.error('Ignore calendar without metadata'); }
return {
id: id,
name: md.title,
color: getContrast(md.color),
bgColor: md.color,
dragBgColor: md.color,
borderColor: md.color,
};
}).filter(Boolean).map(function (obj) {
var id = obj.id;
var cal = APP.calendars[id];
var team = cal.teams.sort()[0] || cal.roTeams.sort()[0];
var title = Util.find(cal, ['content', 'metadata', 'title']) || '';
LOOKUP[id] = title;
TEAMS[id] = team;
return obj;
}).sort(function (a, b) {
var team1 = TEAMS[a.id];
var team2 = TEAMS[b.id];
var t1 = LOOKUP[a.id];
var t2 = LOOKUP[b.id];
return team1 > team2 ? 1 :
(team1 < team2 ? -1 : (
t1 > t2 ? 1 : (t1 < t2 ? -1 : 0)));
});
};
var getSchedules = function () {
var s = [];
var calendars = Object.keys(APP.calendars);
if (APP.currentCalendar) {
var currentCal = calendars.filter(function (id) {
var c = APP.calendars[id];
var t = c.teams || [];
if (id !== APP.currentCalendar) { return; }
return t.length === 1 && t[0] === 0;
});
if (currentCal.length) { calendars = currentCal; }
}
calendars.forEach(function (id) {
var c = APP.calendars[id];
if (c.hidden || c.restricted || c.loading) { return; }
var data = c.content || {};
Object.keys(data.content || {}).forEach(function (uid) {
var obj = data.content[uid];
obj.title = obj.title || "";
obj.location = obj.location || "";
if (obj.isAllDay && obj.startDay) { obj.start = +Flatpickr.parseDate((obj.startDay)); }
if (obj.isAllDay && obj.endDay) {
var endDate = Flatpickr.parseDate(obj.endDay);
endDate.setHours(23);
endDate.setMinutes(59);
endDate.setSeconds(59);
obj.end = +endDate;
}
if (c.readOnly) {
obj.isReadOnly = true;
}
s.push(data.content[uid]);
});
});
return s;
};
var renderCalendar = function () {
var cal = APP.calendar;
if (!cal) { return; }
cal.clear();
cal.setCalendars(getCalendars());
cal.createSchedules(getSchedules(), true);
cal.render();
};
var onCalendarUpdate = function (data) {
var cal = APP.calendar;
if (data.deleted) {
// Remove this calendar
delete APP.calendars[data.id];
} else {
// Update local data
APP.calendars[data.id] = data;
}
// If calendar if initialized, update it
if (!cal) { return; }
onCalendarsUpdate.fire();
renderCalendar();
};
var getTime = function (time) {
var d = new Date();
d.setHours(time.hour);
d.setMinutes(time.minutes);
return d.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
};
// If this browser doesn't support options to toLocaleTimeString, use default layout
if (!(function () {
// Modern browser will return a RangeError if the "locale" argument is invalid.
// Note: the "locale" argument has the same browser compatibility table as the "options"
try {
new Date().toLocaleTimeString('i');
} catch (e) {
return e.name === 'RangeError';
}
})()) { getTime = undefined; }
var templates = {
popupSave: function (obj) {
APP.editModalData = obj.data && obj.data.root;
return Messages.settings_save;
},
popupUpdate: function(obj) {
APP.editModalData = obj.data && obj.data.root;
return Messages.calendar_update;
},
monthGridHeaderExceed: function(hiddenSchedules) {
return '<span class="tui-full-calendar-weekday-grid-more-schedules">' + Messages._getKey('calendar_more', [hiddenSchedules]) + '</span>';
},
popupEdit: function() { return Messages.poll_edit; },
popupDelete: function() { return Messages.kanban_delete; },
popupDetailLocation: function(schedule) {
// TODO detect url and create 'a' tag
return Messages._getKey('calendar_location', [Util.fixHTML(schedule.location)]);
},
popupIsAllDay: function() { return Messages.calendar_allDay; },
titlePlaceholder: function() { return Messages.calendar_title; },
locationPlaceholder: function() { return Messages.calendar_loc; },
alldayTitle: function() {
return '<span class="tui-full-calendar-left-content">'+Messages.calendar_allDay+'</span>';
},
timegridDisplayTime: getTime,
timegridDisplayPrimaryTime: getTime,
popupDetailDate: function(isAllDay, start, end) {
var startDate = start._date.toLocaleDateString();
var endDate = end._date.toLocaleDateString();
if (isAllDay) {
if (startDate === endDate) { return startDate; }
return Messages._getKey('calendar_dateRange', [startDate, endDate]);
}
var startTime = getTime({
hour: start._date.getHours(),
minutes: start._date.getMinutes(),
});
var endTime = getTime({
hour: end._date.getHours(),
minutes: end._date.getMinutes(),
});
if (startDate === endDate && startTime === endTime) {
return start._date.toLocaleString();
}
if (startDate === endDate) {
return Messages._getKey('calendar_dateTimeRange', [startDate, startTime, endTime]);
}
return Messages._getKey('calendar_dateRange', [start._date.toLocaleString(), end._date.toLocaleString()]);
}
};
var editCalendar = function (id) {
var isNew = !id;
var data = APP.calendars[id];
if (id && !data) { return; }
var md = {};
if (!isNew) { md = Util.find(data, ['content', 'metadata']); }
if (!md) { return; }
// Create form data
var labelTitle = h('label', Messages.kanban_title);
var title = h('input');
var $title = $(title);
$title.val(md.title || Messages.calendar_new);
var labelColor = h('label', Messages.kanban_color);
var $colorPicker = $(h('div.cp-calendar-colorpicker'));
var jscolorL = new window.jscolor($colorPicker[0], { showOnClick: false, valueElement: undefined, zIndex: 100000 });
$colorPicker.click(function() {
jscolorL.show();
});
if (md.color) { jscolorL.fromString(md.color); }
else { jscolorL.fromString(Util.getRandomColor()); }
var form = h('div', [
labelTitle,
title,
labelColor,
$colorPicker[0]
]);
var send = function (obj) {
if (isNew) {
return void newCalendar(obj, function (err) {
if (err) { console.error(err); return void UI.warn(Messages.error); }
UI.log(Messages.saved);
});
}
obj.id = id;
updateCalendar(obj, function (err) {
if (err) { console.error(err); return void UI.warn(Messages.error); }
UI.log(Messages.saved);
});
};
var m = UI.dialog.customModal(form, {
buttons: [{
className: 'cancel',
name: Messages.cancel,
onClick: function () {},
keys: [27]
}, {
className: 'primary',
name: Messages.settings_save,
onClick: function () {
var color = jscolorL.toHEXString();
var title = $title.val();
var obj = {
color: color,
title: title
};
if (!title || !title.trim() ||!/^#[0-9a-fA-F]{6}$/.test(color)) {
return true;
}
send(obj);
},
keys: [13]
}]
});
UI.openCustomModal(m);
};
var isReadOnly = function (id, teamId) {
var data = APP.calendars[id];
return data.readOnly || (data.roTeams && data.roTeams.indexOf(teamId) !== -1);
};
var makeEditDropdown = function (id, teamId) {
var options = [];
var privateData = metadataMgr.getPrivateData();
var cantRemove = teamId === 0 || (teamId !== 1 && privateData.teams[teamId].viewer);
var data = APP.calendars[id];
if (!data.readOnly) {
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-pencil',
},
content: h('span', Messages.tag_edit),
action: function (e) {
e.stopPropagation();
editCalendar(id);
return true;
}
});
}
if (APP.loggedIn && (data.teams.indexOf(1) === -1 || teamId === 0)) {
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-clone',
},
content: h('span', Messages.calendar_import),
action: function (e) {
e.stopPropagation();
importCalendar({
id: id,
teamId: teamId
}, function (err) {
if (err) {
console.error(err);
return void UI.warn(Messages.error);
}
});
return true;
}
});
}
if (!data.restricted) {
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-shhare-alt',
},
content: h('span', Messages.shareButton),
action: function (e) {
e.stopPropagation();
var friends = common.getFriends();
var cal = APP.calendars[id];
var title = Util.find(cal, ['content', 'metadata', 'title']);
var color = Util.find(cal, ['content', 'metadata', 'color']);
Share.getShareModal(common, {
teamId: teamId === 1 ? undefined : teamId,
origin: APP.origin,
pathname: "/calendar/",
friends: friends,
title: title,
password: cal.password,
calendar: {
title: title,
color: color,
channel: id,
},
common: common,
hashes: cal.hashes
});
return true;
}
});
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-lock',
},
content: h('span', Messages.accessButton),
action: function (e) {
e.stopPropagation();
var cal = APP.calendars[id];
var title = Util.find(cal, ['content', 'metadata', 'title']);
var color = Util.find(cal, ['content', 'metadata', 'color']);
var h = cal.hashes || {};
var href = Hash.hashToHref(h.editHash || h.viewHash, 'calendar');
Access.getAccessModal(common, {
title: title,
password: cal.password,
calendar: {
title: title,
color: color,
channel: id,
},
common: common,
noExpiration: true,
noEditPassword: true,
channel: id,
href: href
});
return true;
}
});
if (!data.readOnly) {
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-upload',
},
content: h('span', Messages.importButton),
action: function () {
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,
buttonCls: 'btn',
};
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({
tag: 'a',
attributes: {
'class': 'fa fa-info-circle',
},
content: h('span', Messages.propertiesButton),
action: function (e) {
e.stopPropagation();
var cal = APP.calendars[id];
var title = Util.find(cal, ['content', 'metadata', 'title']);
var color = Util.find(cal, ['content', 'metadata', 'color']);
var h = cal.hashes || {};
var href = Hash.hashToHref(h.editHash || h.viewHash, 'calendar');
Properties.getPropertiesModal(common, {
calendar: {
title: title,
color: color,
channel: id,
},
common: common,
channel: id,
href: href
});
return true;
}
});
}
if (!cantRemove) {
options.push({
tag: 'a',
attributes: {
'class': 'fa fa-trash-o',
},
content: h('span', Messages.poll_remove),
action: function (e) {
e.stopPropagation();
var cal = APP.calendars[id];
var teams = (cal && cal.teams) || [];
var text = [ Messages.calendar_deleteConfirm ];
if (teams.length === 1 && teams[0] !== 1) {
text[0] = Messages.calendar_deleteTeamConfirm;
}
if (cal.owned) {
text = text.concat([' ', Messages.calendar_deleteOwned]);
}
UI.confirm(h('span', text), function (yes) {
if (!yes) { return; }
deleteCalendar({
id: id,
teamId: teamId,
}, function (err) {
if (err) {
console.error(err);
return void UI.warn(Messages.error);
}
renderCalendar();
});
});
}
});
}
var dropdownConfig = {
text: '',
options: options, // Entries displayed in the menu
common: common,
buttonCls: 'btn btn-cancel fa fa-ellipsis-h small'
};
return UIElements.createDropdown(dropdownConfig)[0];
};
var makeCalendarEntry = function (id, teamId) {
var data = APP.calendars[id];
var edit;
if (data.loading) {
edit = h('i.fa.fa-spinner.fa-spin');
} else {
edit = makeEditDropdown(id, teamId);
}
var md = Util.find(data, ['content', 'metadata']);
if (!md) { return; }
var active = data.hidden ? '' : '.cp-active';
var restricted = data.restricted ? '.cp-restricted' : '';
var temp = teamId === 0 ? '.cp-unclickable' : '';
var calendar = h('div.cp-calendar-entry'+active+restricted+temp, {
'data-uid': id
}, [
h('span.cp-calendar-icon', {
style: 'background-color: '+md.color+';'
}, [
h('i.cp-calendar-active.fa.fa-calendar', {
style: 'color: '+getContrast(md.color)+';'
}),
h('i.cp-calendar-inactive.fa.fa-calendar-o')
]),
h('span.cp-calendar-title', md.title),
data.restricted ? h('i.fa.fa-ban', {title: Messages.fm_restricted}) :
(isReadOnly(id, teamId) ? h('i.fa.fa-eye', {title: Messages.readonly}) : undefined),
edit
]);
var $calendar = $(calendar).click(function () {
if (teamId === 0) { return; }
data.hidden = !data.hidden;
if (APP.$calendars) {
APP.$calendars.find('[data-uid="'+id+'"]').toggleClass('cp-active', !data.hidden);
} else {
$(calendar).toggleClass('cp-active', !data.hidden);
}
renderCalendar();
});
if (!data.loading) {
$calendar.contextmenu(function (ev) {
ev.preventDefault();
$(edit).click();
});
}
if (APP.$calendars) { APP.$calendars.append(calendar); }
return calendar;
};
var makeLeftside = function (calendar, $container) {
// Show calendars
var calendars = h('div.cp-calendar-list');
var $calendars = APP.$calendars = $(calendars).appendTo($container);
onCalendarsUpdate.reg(function () {
$calendars.empty();
var privateData = metadataMgr.getPrivateData();
var filter = function (teamId) {
var LOOKUP = {};
return Object.keys(APP.calendars || {}).filter(function (id) {
var cal = APP.calendars[id] || {};
var teams = (cal.teams || []).map(function (tId) { return Number(tId); });
return teams.indexOf(typeof(teamId) !== "undefined" ? Number(teamId) : 1) !== -1;
}).map(function (k) {
// nearly constant-time pre-sort
var cal = APP.calendars[k] || {};
var title = Util.find(cal, ['content', 'metadata', 'title']) || '';
LOOKUP[k] = title;
return k;
}).sort(function (a, b) {
var t1 = LOOKUP[a];
var t2 = LOOKUP[b];
return t1 > t2 ? 1 : (t1 === t2 ? 0 : -1);
});
};
var tempCalendars = filter(0);
if (tempCalendars.length && tempCalendars[0] === APP.currentCalendar) {
APP.$calendars.append(h('div.cp-calendar-team', [
h('span', Messages.calendar_tempCalendar)
]));
makeCalendarEntry(tempCalendars[0], 0);
var importTemp = h('button', [
h('i.fa.fa-calendar-plus-o'),
h('span', Messages.calendar_import_temp),
h('span')
]);
$(importTemp).click(function () {
importCalendar({
id: tempCalendars[0],
teamId: 0
}, function (err) {
if (err) {
console.error(err);
return void UI.warn(Messages.error);
}
});
});
if (APP.loggedIn) {
APP.$calendars.append(h('div.cp-calendar-entry.cp-ghost', importTemp));
}
return;
}
var myCalendars = filter(1);
if (myCalendars.length) {
var user = metadataMgr.getUserData();
var avatar = h('span.cp-avatar');
var name = user.name || Messages.anonymous;
common.displayAvatar($(avatar), user.avatar, name);
APP.$calendars.append(h('div.cp-calendar-team', [
avatar,
h('span.cp-name', {title: name}, name)
]));
}
myCalendars.forEach(function (id) {
makeCalendarEntry(id, 1);
});
// Add new button
var $newContainer = $(h('div.cp-calendar-entry.cp-ghost')).appendTo($calendars);
var newButton = h('button', [
h('i.fa.fa-calendar-plus-o'),
h('span', Messages.calendar_new),
h('span')
]);
$(newButton).click(function () {
editCalendar();
}).appendTo($newContainer);
Object.keys(privateData.teams).sort().forEach(function (teamId) {
var calendars = filter(teamId);
if (!calendars.length) { return; }
var team = privateData.teams[teamId];
var avatar = h('span.cp-avatar');
common.displayAvatar($(avatar), team.avatar, team.displayName || team.name);
APP.$calendars.append(h('div.cp-calendar-team', [
avatar,
h('span.cp-name', {title: team.name}, team.name)
]));
calendars.forEach(function (id) {
makeCalendarEntry(id, teamId);
});
});
});
onCalendarsUpdate.fire();
};
var ISO8601_week_no = function (dt) {
var tdt = new Date(dt.valueOf());
var dayn = (dt.getDay() + 6) % 7;
tdt.setDate(tdt.getDate() - dayn + 3);
var firstThursday = tdt.valueOf();
tdt.setMonth(0, 1);
if (tdt.getDay() !== 4) {
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
}
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
};
var updateDateRange = function () {
var range = APP.calendar._renderRange;
var start = range.start._date.toLocaleDateString();
var end = range.end._date.toLocaleDateString();
var week = ISO8601_week_no(range.start._date);
var date = [
h('b.cp-small', Messages._getKey('calendar_weekNumber', [week])),
h('b', start),
h('span', ' - '),
h('b', end),
];
if (APP.calendar._viewName === "day") {
date = h('b', start);
} else if (APP.calendar._viewName === "month") {
var month;
var mid = new Date(Math.floor(((+range.start._date) + (+range.end._date)) / 2));
try {
month = mid.toLocaleString('default', {
month: 'long',
year:'numeric'
});
month = month.replace(/^./, function (str) { return str.toUpperCase(); });
date = h('b', month);
} catch (e) {
// Use same as week range: first day of month to last day of month
}
}
APP.toolbar.$bottomM.empty().append(h('div', date));
};
var makeCalendar = function (view) {
var store = window.cryptpadStore;
var $container = $('#cp-sidebarlayout-container');
var leftside;
$container.append([
leftside = h('div#cp-sidebarlayout-leftside'),
h('div#cp-sidebarlayout-rightside')
]);
var large = $(window).width() >= 800;
var cal = APP.calendar = new Calendar('#cp-sidebarlayout-rightside', {
defaultView: view || 'week', // weekly view option
taskView: false,
useCreationPopup: true,
useDetailPopup: true,
usageStatistics: false,
calendars: getCalendars(),
template: templates,
month: {
daynames: getWeekDays(large),
startDayOfWeek: 1,
},
week: {
daynames: getWeekDays(large),
startDayOfWeek: 1,
}
});
$(window).on('resize', function () {
var _large = $(window).width() >= 800;
if (large !== _large) {
large = _large;
cal.setOptions({
month: {
daynames: getWeekDays(_large),
startDayOfWeek: 1,
},
week: {
daynames: getWeekDays(_large),
startDayOfWeek: 1,
}
});
}
});
makeLeftside(cal, $(leftside));
cal.on('beforeCreateSchedule', function(event) {
// TODO Recurrence (later)
// On creation, select a recurrence rule (daily / weekly / monthly / more weird rules)
// then mark it under recurrence rule with a uid (the same for all the recurring events)
// ie: recurrenceRule: DAILY|{uid}
// Use template to hide "recurrenceRule" from the detailPopup or at least to use
// a non technical value
var reminders = APP.notificationsEntries;
var startDate = event.start._date;
var endDate = event.end._date;
var schedule = {
id: Util.uid(),
calendarId: event.calendarId,
title: Util.fixHTML(event.title),
category: "time",
location: Util.fixHTML(event.location),
start: +startDate,
isAllDay: event.isAllDay,
end: +endDate,
reminders: reminders,
};
newEvent(schedule, function (err) {
if (err) {
console.error(err);
return void UI.warn(err);
}
cal.createSchedules([schedule]);
});
});
cal.on('beforeUpdateSchedule', function(event) {
var changes = event.changes || {};
delete changes.state;
if (changes.end) { changes.end = +new Date(changes.end._date); }
if (changes.start) { changes.start = +new Date(changes.start._date); }
var old = event.schedule;
var oldReminders = Util.find(APP.calendars, [old.calendarId, 'content', 'content', old.id, 'reminders']);
var reminders = APP.notificationsEntries;
if (JSONSortify(oldReminders || []) !== JSONSortify(reminders)) {
changes.reminders = reminders;
}
updateEvent({
ev: old,
changes: changes
}, function (err) {
if (err) {
console.error(err);
return void UI.warn(err);
}
cal.updateSchedule(old.id, old.calendarId, changes);
});
});
cal.on('beforeDeleteSchedule', function(event) {
var data = event.schedule;
deleteEvent(event.schedule, function (err) {
if (err) {
console.error(err);
return void UI.warn(err);
}
cal.deleteSchedule(data.id, data.calendarId);
});
});
$('body').on('keydown', function (e) {
if (e.which === 27) {
$('.tui-full-calendar-floating-layer').hide();
}
});
updateDateRange();
renderCalendar();
// Toolbar
// Change view mode
var options = ['day', 'week', 'month'].map(function (k) {
return {
tag: 'a',
attributes: {
'class': 'cp-calendar-view',
'data-value': k,
'href': '#',
},
content: Messages['calendar_'+k]
// Messages.calendar_day
// Messages.calendar_week
// Messages.calendar_month
};
});
var dropdownConfig = {
text: Messages.calendar_week,
options: options, // Entries displayed in the menu
isSelect: true,
common: common,
caretDown: true,
left: true,
};
var $block = UIElements.createDropdown(dropdownConfig);
$block.setValue(view || 'week');
var $views = $block.find('a');
$views.click(function () {
var mode = $(this).attr('data-value');
cal.changeView(mode);
updateDateRange();
store.put('calendarView', mode, function () {});
});
APP.toolbar.$bottomR.append($block);
// New event button
var newEventBtn = h('button.cp-calendar-newevent', [
h('i.fa.fa-plus'),
h('span', Messages.calendar_newEvent)
]);
$(newEventBtn).click(function (e) {
e.preventDefault();
cal.openCreationPopup({isAllDay:false});
}).appendTo(APP.toolbar.$bottomL);
// Change page
var goLeft = h('button.fa.fa-chevron-left');
var goRight = h('button.fa.fa-chevron-right');
var goToday = h('button', Messages.calendar_today);
$(goLeft).click(function () {
cal.prev();
updateDateRange();
});
$(goRight).click(function () {
cal.next();
updateDateRange();
});
$(goToday).click(function () {
cal.today();
updateDateRange();
});
APP.toolbar.$bottomL.append(h('div.cp-calendar-browse', [
goLeft, goToday, goRight
]));
};
var parseNotif = function (minutes) {
var res = {
unit: 'minutes',
value: minutes
};
var hours = minutes / 60;
if (!Number.isInteger(hours)) { return res; }
res.unit = 'hours';
res.value = hours;
var days = hours / 24;
if (!Number.isInteger(days)) { return res; }
res.unit = 'days';
res.value = days;
return res;
};
var getNotificationDropdown = function () {
var ev = APP.editModalData;
var calId = ev.selectedCal.id;
// DEFAULT HERE [10] ==> 10 minutes before the event
var oldReminders = Util.find(APP.calendars, [calId, 'content', 'content', ev.id, 'reminders']) || [10];
APP.notificationsEntries = [];
var number = h('input.tui-full-calendar-content', {
type: "number",
value: 10,
min: 1,
max: 60
});
var $number = $(number);
var options = ['minutes', 'hours', 'days'].map(function (k) {
return {
tag: 'a',
attributes: {
'class': 'cp-calendar-reminder',
'data-value': k,
'href': '#',
},
content: Messages['calendar_'+k]
// Messages.calendar_minutes
// Messages.calendar_hours
// Messages.calendar_days
};
});
var dropdownConfig = {
text: Messages.calendar_minutes,
options: options, // Entries displayed in the menu
isSelect: true,
common: common,
buttonCls: 'btn btn-secondary',
caretDown: true,
};
var $block = UIElements.createDropdown(dropdownConfig);
$block.setValue('minutes');
var $types = $block.find('a');
$types.click(function () {
var mode = $(this).attr('data-value');
var max = mode === "minutes" ? 60 : 24;
$number.attr('max', max);
if ($number.val() > max) { $number.val(max); }
});
var addNotif = h('button.btn.btn-primary-outline.fa.fa-plus');
var $list = $(h('div.cp-calendar-notif-list'));
var listContainer = h('div.cp-calendar-notif-list-container', [
h('span.cp-notif-label', Messages.calendar_notifications),
$list[0],
h('span.cp-notif-empty', Messages.calendar_noNotification)
]);
var addNotification = function (unit, value) {
var unitValue = (unit === "minutes") ? 1 : (unit === "hours" ? 60 : (60*24));
var del = h('button.btn.btn-danger-outline.small.fa.fa-times');
var minutes = value * unitValue;
if ($list.find('[data-minutes="'+minutes+'"]').length) { return; }
var span = h('span.cp-notif-entry', {
'data-minutes': minutes
}, [
h('span.cp-notif-value', [
h('span', value),
h('span', Messages['calendar_'+unit]),
h('span.cp-before', Messages.calendar_before)
]),
del
]);
$(del).click(function () {
$(span).remove();
var idx = APP.notificationsEntries.indexOf(minutes);
APP.notificationsEntries.splice(idx, 1);
});
$list.append(span);
APP.notificationsEntries.push(minutes);
};
$(addNotif).click(function () {
var unit = $block.getValue();
var value = $number.val();
addNotification(unit, value);
});
oldReminders.forEach(function (minutes) {
var p = parseNotif(minutes);
addNotification(p.unit, p.value);
});
return h('div.tui-full-calendar-popup-section.cp-calendar-add-notif', [
listContainer,
h('div.cp-calendar-notif-form', [
h('span.cp-notif-label', Messages.calendar_addNotification),
number,
$block[0],
addNotif
])
]);
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
var configTb = {
displayed: displayed,
sfCommon: common,
$container: APP.$toolbar,
pageTitle: Messages.calendar,
metadataMgr: common.getMetadataMgr(),
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
};
var onEvent = function (obj) {
var ev = obj.ev;
var data = obj.data;
if (ev === 'UPDATE') {
onCalendarUpdate(data);
return;
}
};
nThen(function (waitFor) {
$(waitFor(UI.addLoadingScreen));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
APP.$toolbar = $('#cp-toolbar');
sframeChan = common.getSframeChannel();
sframeChan.onReady(waitFor());
}).nThen(function (/*waitFor*/) {
createToolbar();
metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
APP.loggedIn = common.isLoggedIn();
common.setTabTitle(Messages.calendar);
// Fix flatpickr selection
var MutationObserver = window.MutationObserver;
var onFlatPickr = function (el) {
// Don't close event creation popup when clicking on flatpickr
$(el).mousedown(function (e) {
e.stopPropagation();
});
};
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var node;
for (var i = 0; i < mutation.addedNodes.length; i++) {
node = mutation.addedNodes[i];
if (node.classList && node.classList.contains('flatpickr-calendar')) {
onFlatPickr(node);
}
}
});
});
observer.observe($('body')[0], {
childList: true,
subtree: false
});
// Customize creation/update popup
var onCalendarPopup = function (el) {
var $el = $(el);
$el.find('.tui-full-calendar-confirm').addClass('btn btn-primary').prepend(h('i.fa.fa-floppy-o'));
$el.find('input').attr('autocomplete', 'off');
$el.find('.tui-full-calendar-dropdown-button').addClass('btn btn-secondary');
$el.find('.tui-full-calendar-popup-close').addClass('btn btn-cancel fa fa-times cp-calendar-close').empty();
var calendars = APP.calendars || {};
var show = false;
$el.find('.tui-full-calendar-dropdown-menu li').each(function (i, li) {
var $li = $(li);
var id = $li.attr('data-calendar-id');
var c = calendars[id];
if (!c || c.readOnly) {
return void $li.remove();
}
// If at least one calendar is editable, show the popup
show = true;
});
if ($el.find('.tui-full-calendar-hide.tui-full-calendar-dropdown').length || !show) {
$el.hide();
UI.warn(Messages.calendar_errorNoCalendar);
return;
}
var isUpdate = Boolean($el.find('#tui-full-calendar-schedule-title').val());
if (!isUpdate) { $el.find('.tui-full-calendar-dropdown-menu li').first().click(); }
var $button = $el.find('.tui-full-calendar-section-button-save');
var div = getNotificationDropdown();
$button.before(div);
var $cbox = $el.find('#tui-full-calendar-schedule-allday');
var $start = $el.find('.tui-full-calendar-section-start-date');
var $dash = $el.find('.tui-full-calendar-section-date-dash');
var $end = $el.find('.tui-full-calendar-section-end-date');
var allDay = $cbox.is(':checked');
if (allDay) {
$start.hide();
$dash.hide();
$end.hide();
}
$el.find('.tui-full-calendar-section-allday').click(function () {
setTimeout(function () {
var allDay = $cbox.is(':checked');
if (allDay) {
$start.hide();
$dash.hide();
$end.hide();
return;
}
$start.show();
$dash.show();
$end.show();
});
});
};
var onCalendarEditPopup = function (el) {
var $el = $(el);
$el.find('.tui-full-calendar-popup-edit').addClass('btn btn-primary');
$el.find('.tui-full-calendar-popup-edit .tui-full-calendar-icon').addClass('fa fa-pencil').removeClass('tui-full-calendar-icon');
$el.find('.tui-full-calendar-popup-delete').addClass('btn btn-danger');
$el.find('.tui-full-calendar-popup-delete .tui-full-calendar-icon').addClass('fa fa-trash').removeClass('tui-full-calendar-icon');
$el.find('.tui-full-calendar-content').removeClass('tui-full-calendar-content');
};
var onPopupRemoved = function () {
var start, end;
if (window.CP_startPickr) { start = window.CP_startPickr.calendarContainer; }
if (window.CP_endPickr) { end = window.CP_endPickr.calendarContainer; }
$('.flatpickr-calendar').each(function (i, el) {
if (el === start || el === end) { return; }
$(el).remove();
});
};
var observer2 = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var node, _node;
for (var i = 0; i < mutation.addedNodes.length; i++) {
node = mutation.addedNodes[i];
try {
if (node.classList && node.classList.contains('tui-full-calendar-popup')
&& !node.classList.contains('tui-full-calendar-popup-detail')) {
onCalendarPopup(node);
}
if (node.classList && node.classList.contains('tui-full-calendar-popup')
&& node.classList.contains('tui-full-calendar-popup-detail')) {
onCalendarEditPopup(node);
}
} catch (e) {}
}
for (var j = 0; j < mutation.removedNodes.length; j++) {
_node = mutation.addedNodes[j];
try {
if (_node.classList && _node.classList.contains('tui-full-calendar-popup')) {
onPopupRemoved();
}
} catch (e) {}
}
});
});
observer2.observe($('body')[0], {
childList: true,
subtree: true
});
APP.module = common.makeUniversal('calendar', {
onEvent: onEvent
});
var store = window.cryptpadStore;
APP.module.execCommand('SUBSCRIBE', null, function (obj) {
if (obj.empty && !privateData.calendarHash) {
if (!privateData.loggedIn) {
return void UI.errorLoadingScreen(Messages.mustLogin, false, function () {
common.setLoginRedirect('login');
});
}
// No calendar yet, create one
newCalendar({
teamId: 1,
initialCalendar: true,
color: user.color,
title: Messages.calendar_default
}, function (err) {
if (err) { return void UI.errorLoadingScreen(Messages.error); }
store.get('calendarView', makeCalendar);
UI.removeLoadingScreen();
});
return;
}
if (privateData.calendarHash) {
var hash = privateData.hashes.editHash || privateData.hashes.viewHash;
var secret = Hash.getSecrets('calendar', hash, privateData.password);
APP.currentCalendar = secret.channel;
APP.module.execCommand('OPEN', {
hash: hash,
password: privateData.password
}, function (obj) {
if (obj && obj.error) { console.error(obj.error); }
});
}
store.get('calendarView', makeCalendar);
UI.removeLoadingScreen();
});
APP.origin = privateData.origin;
});
});