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 '' + Messages._getKey('calendar_more', [hiddenSchedules]) + ''; }, 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 ''+Messages.calendar_allDay+''; }, 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; }); });