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

1311 lines
49 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',
'/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,
Share, Access, Properties
)
{
var SaveAs = window.saveAs;
var APP = window.APP = {
calendars: {}
};
var common;
var metadataMgr;
var sframeChan;
Messages.calendar = "BETA Calendar"; // XXX
Messages.calendar_default = "My calendar"; // XXX
Messages.calendar_new = "New calendar"; // XXX
Messages.calendar_day = "Day";
Messages.calendar_week = "Week";
Messages.calendar_month = "Month";
Messages.calendar_today = "Today";
Messages.calendar_more = "{0} more";
Messages.calendar_deleteConfirm = "Are you sure you want to delete this calendar from your account?";
Messages.calendar_deleteTeamConfirm = "Are you sure you want to delete this calendar from this team?";
Messages.calendar_deleteOwned = " It will still be visible for the users it has been shared with.";
Messages.calendar_errorNoCalendar = "No editable calendar selected!";
Messages.calendar_myCalendars = "My calendars";
Messages.calendar_tempCalendar = "Viewing";
Messages.calendar_import = "Import to my calendars";
Messages.calendar_import_temp = "Import this calendar";
Messages.calendar_newEvent = "New event";
Messages.calendar_new = "New calendar";
Messages.calendar_dateRange = "{0} - {1}";
Messages.calendar_dateTimeRange = "{0} {1} - {2}";
Messages.calendar_weekNumber = "Week {0}";
Messages.calendar_update = "Update";
Messages.calendar_title = "Title";
Messages.calendar_loc = "Location";
Messages.calendar_location = "Location: {0}";
Messages.calendar_allDay = "All day";
Messages.calendar_minutes = "Minutes";
Messages.calendar_hours = "Hours";
Messages.calendar_days = "Days";
Messages.calendar_before = "before";
Messages.calendar_notifications = "Reminders";
Messages.calendar_addNotification = "Add reminder";
Messages.calendar_noNotification = "None";
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(Date.UTC(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 () {
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);
};
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 = +new Date(obj.startDay); }
if (obj.isAllDay && obj.endDay) {
var endDate = new Date(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()]);
}
};
// XXX Note: always create calendars in your own proxy. If you want a team calendar, you can share it with the team later.
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 (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 (obj) {
if (obj && obj.error) {
console.error(obj.error);
return void UI.warn(obj.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, // XXX support passwords
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, // XXX support passwords
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
};
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.kanban_delete),
action: function (e) {
e.stopPropagation();
var cal = APP.calendars[id];
var key = Messages.calendar_deleteConfirm;
var teams = (cal && cal.teams) || [];
if (teams.length === 1 && teams[0] !== 1) {
key = Messages.calendar_deleteTeamConfirm;
}
if (cal.owned) {
key += Messages.calendar_deleteOwned;
}
UI.confirm(key, function (yes) {
if (!yes) { return; }
deleteCalendar({
id: id,
teamId: teamId,
}, function (err) {
if (err) {
console.error(err);
UI.warn(Messages.error);
}
});
});
}
});
}
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) {
// XXX handle RESTRICTED calendars (data.restricted)
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
]);
$(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 (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) {
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;
});
};
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 (obj) {
if (obj && obj.error) {
console.error(obj.error);
return void UI.warn(obj.error);
}
});
});
APP.$calendars.append(h('div.cp-calendar-entry.cp-ghost', importTemp));
return;
}
var myCalendars = filter(1);
if (myCalendars.length) {
APP.$calendars.append(h('div.cp-calendar-team', [
h('span', Messages.calendar_myCalendars)
]));
}
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).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);
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) {
// XXX 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;
// XXX 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();
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) {
// 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); } // XXX
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;
});
});