mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
Calendars improvements
This commit is contained in:
parent
9f4144703c
commit
2987e468a9
@ -117,6 +117,11 @@
|
||||
&.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
&.small {
|
||||
line-height: initial;
|
||||
padding: 5px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&:hover, &:not(:disabled):not(.disabled):active, &:focus {
|
||||
color: @cp_buttons-fg;
|
||||
|
||||
@ -1,16 +1,34 @@
|
||||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
@import (reference) '../../customize/src/less2/include/sidebar-layout.less';
|
||||
@import (reference) '../../customize/src/less2/include/tools.less';
|
||||
|
||||
&.cp-app-calendar {
|
||||
|
||||
.framework_min_main();
|
||||
.sidebar-layout_main();
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
#cp-calendar {
|
||||
#cp-sidebarlayout-container #cp-sidebarlayout-rightside {
|
||||
padding: 0;
|
||||
& > div {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tui-full-calendar-layout {
|
||||
background-color: @cp_sidebar-right-bg !important;
|
||||
color: @cryptpad_text_col;
|
||||
.tui-full-calendar-month * {
|
||||
color: @cryptpad_text_col !important; // XXX
|
||||
}
|
||||
}
|
||||
.tui-full-calendar-timegrid-timezone {
|
||||
background-color: @cp_sidebar-right-bg !important;
|
||||
.tui-full-calendar-timegrid-hour {
|
||||
color: @cryptpad_text_col !important;
|
||||
}
|
||||
color: @cryptpad_text_col;
|
||||
}
|
||||
.tui-full-calendar-timegrid-gridline, .tui-full-calendar-time-date {
|
||||
border-color: @cp_calendar-border !important;
|
||||
@ -62,5 +80,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
#cp-sidebarlayout-leftside {
|
||||
& > div {
|
||||
padding: 10px
|
||||
}
|
||||
.cp-calendar-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.cp-calendar-browse {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.cp-calendar-list {
|
||||
.cp-calendar-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
&:hover {
|
||||
background: fade(@cryptpad_text_col, 10%);
|
||||
}
|
||||
&.cp-active {
|
||||
background: @cp_sidebar-left-active;
|
||||
}
|
||||
.tools_unselectable();
|
||||
.cp-calendar-color {
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-calendar-colorpicker {
|
||||
width: 100px;
|
||||
height: 25px;
|
||||
cursor: pointer;
|
||||
border: 1px solid @cp_forms-border;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
</head>
|
||||
<body class="cp-app-calendar">
|
||||
<div id="cp-toolbar" class="cp-toolbar-container"></div>
|
||||
<div id="cp-container"></div>
|
||||
<div id="cp-sidebarlayout-container"></div>
|
||||
<noscript>
|
||||
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
||||
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
||||
|
||||
@ -16,6 +16,7 @@ define([
|
||||
'/customize/application_config.js',
|
||||
'/lib/calendar/tui-calendar.min.js',
|
||||
|
||||
'/common/jscolor.js',
|
||||
'css!/lib/calendar/tui-calendar.min.css',
|
||||
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/calendar/app-calendar.less',
|
||||
@ -47,6 +48,13 @@ define([
|
||||
|
||||
Messages.calendar = "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";
|
||||
|
||||
var onCalendarsUpdate = Util.mkEvent();
|
||||
|
||||
var newCalendar = function (data, cb) {
|
||||
APP.module.execCommand('CREATE', data, function (obj) {
|
||||
@ -54,6 +62,18 @@ Messages.calendar_default = "My calendar"; // XXX
|
||||
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 newEvent = function (data, cb) {
|
||||
var start = data.start;
|
||||
var end = data.end;
|
||||
@ -85,9 +105,23 @@ Messages.calendar_default = "My calendar"; // XXX
|
||||
(parseInt(rgb[2]) * 114)) / 1000);
|
||||
return (brightness > 125) ? 'black' : 'white';
|
||||
};
|
||||
|
||||
var getWeekDays = function (locale) {
|
||||
var baseDate = new Date(Date.UTC(2017, 0, 1)); // just a Sunday
|
||||
var weekDays = [];
|
||||
for(i = 0; i < 7; i++) {
|
||||
weekDays.push(baseDate.toLocaleDateString(undefined, { weekday: 'long' }));
|
||||
baseDate.setDate(baseDate.getDate() + 1);
|
||||
}
|
||||
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) { return; }
|
||||
var md = Util.find(c, ['content', 'metadata']);
|
||||
if (!md) { return void console.error('Ignore calendar without metadata'); }
|
||||
return {
|
||||
@ -98,12 +132,13 @@ Messages.calendar_default = "My calendar"; // XXX
|
||||
dragBgColor: md.color,
|
||||
borderColor: md.color,
|
||||
};
|
||||
});
|
||||
}).filter(Boolean);
|
||||
};
|
||||
var getSchedules = function () {
|
||||
var s = [];
|
||||
Object.keys(APP.calendars).forEach(function (id) {
|
||||
var c = APP.calendars[id];
|
||||
if (c.hidden) { return; }
|
||||
var data = c.content || {};
|
||||
Object.keys(data.content || {}).forEach(function (uid) {
|
||||
var obj = data.content[uid];
|
||||
@ -114,7 +149,15 @@ Messages.calendar_default = "My calendar"; // XXX
|
||||
});
|
||||
return s;
|
||||
};
|
||||
var updateCalendar = function (data) {
|
||||
var renderCalendar = function () {
|
||||
var cal = APP.calendar;
|
||||
if (!cal) { return; }
|
||||
|
||||
cal.clear();
|
||||
cal.createSchedules(getSchedules(), true);
|
||||
cal.render();
|
||||
};
|
||||
var onCalendarUpdate = function (data) {
|
||||
var cal = APP.calendar;
|
||||
|
||||
// Is it a new calendar?
|
||||
@ -123,38 +166,244 @@ Messages.calendar_default = "My calendar"; // XXX
|
||||
// Update local data
|
||||
APP.calendars[data.id] = data;
|
||||
|
||||
// If this calendar is new, add it
|
||||
if (cal && isNew) { cal.setCalendars(getCalendars()); }
|
||||
|
||||
// If calendar if initialized, update it
|
||||
if (!cal) { return; }
|
||||
cal.clear();
|
||||
cal.createSchedules(getSchedules(), true);
|
||||
cal.render();
|
||||
cal.setCalendars(getCalendars());
|
||||
onCalendarsUpdate.fire();
|
||||
renderCalendar();
|
||||
};
|
||||
|
||||
var getPadStart = function (value) {
|
||||
value = value.toString();
|
||||
return padStart.call(value, 2, '0');
|
||||
};
|
||||
|
||||
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 () {
|
||||
return Messages.settings_save;
|
||||
}
|
||||
},
|
||||
timegridDisplayTime: getTime,
|
||||
timegridDisplayPrimaryTime: getTime,
|
||||
};
|
||||
|
||||
var makeCalendar = function (ctx) {
|
||||
var $container = $('#cp-container');
|
||||
$container.append([
|
||||
h('div#menu', [
|
||||
h('span#renderRange.render-range')
|
||||
]),
|
||||
h('div#cp-calendar')
|
||||
var makeTeamSelector = function () {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
var keys = Object.keys(privateData.teams);
|
||||
if (!keys.length) { return; }
|
||||
var options = [];
|
||||
keys.forEach(function (id) {
|
||||
var t = privateData.teams[id];
|
||||
if (t.viewer) { return; }
|
||||
});
|
||||
};
|
||||
|
||||
// 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 });
|
||||
var to;
|
||||
$colorPicker.click(function() {
|
||||
jscolorL.show();
|
||||
});
|
||||
if (md.color) { jscolorL.fromString(md.color); }
|
||||
|
||||
var form = h('div', [
|
||||
labelTitle,
|
||||
title,
|
||||
labelColor,
|
||||
$colorPicker[0]
|
||||
]);
|
||||
|
||||
var cal = APP.calendar = new Calendar('#cp-calendar', {
|
||||
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 makeLeftside = function (calendar, $container) {
|
||||
var $topContainer = $(h('div.cp-calendar-new')).appendTo($container);
|
||||
// Add new button
|
||||
var newButton = h('button.btn.btn-primary', [
|
||||
h('i.fa.fa-plus'),
|
||||
h('span', Messages.newButton)
|
||||
]);
|
||||
$(newButton).click(function () {
|
||||
editCalendar();
|
||||
});
|
||||
$topContainer.append(newButton);
|
||||
|
||||
// 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,
|
||||
buttonCls: 'btn btn-secondary'
|
||||
};
|
||||
var $block = UIElements.createDropdown(dropdownConfig);
|
||||
$block.setValue('week');
|
||||
var $views = $block.find('a');
|
||||
$views.click(function () {
|
||||
var mode = $(this).attr('data-value');
|
||||
calendar.changeView(mode);
|
||||
});
|
||||
$topContainer.append($block);
|
||||
|
||||
// Change page
|
||||
var goLeft = h('button.btn.btn-secondary.fa.fa-chevron-left');
|
||||
var goRight = h('button.btn.btn-secondary.fa.fa-chevron-right');
|
||||
var goToday = h('button.btn.btn-secondary', Messages.calendar_today);
|
||||
$(goLeft).click(function () { calendar.prev(); });
|
||||
$(goRight).click(function () { calendar.next(); });
|
||||
$(goToday).click(function () { calendar.today(); });
|
||||
$container.append(h('div.cp-calendar-browse', [
|
||||
goLeft, goToday, goRight
|
||||
]));
|
||||
|
||||
// Show calendars
|
||||
var calendars = h('div.cp-calendar-list');
|
||||
var $calendars = $(calendars).appendTo($container);
|
||||
onCalendarsUpdate.reg(function () {
|
||||
$calendars.empty();
|
||||
Object.keys(APP.calendars || {}).forEach(function (id) {
|
||||
var data = APP.calendars[id];
|
||||
var edit;
|
||||
if (!data.readOnly) {
|
||||
edit = h('button.btn.btn-cancel.fa.fa-pencil.small');
|
||||
$(edit).click(function (e) {
|
||||
e.stopPropagation();
|
||||
editCalendar(id);
|
||||
});
|
||||
}
|
||||
var md = Util.find(data, ['content', 'metadata']);
|
||||
if (!md) { return; }
|
||||
var active = data.hidden ? '' : '.cp-active';
|
||||
var calendar = h('div.cp-calendar-entry'+active, [
|
||||
h('span.cp-calendar-color', {
|
||||
style: 'background-color: '+md.color+';'
|
||||
}),
|
||||
h('span.cp-calendar-title', md.title),
|
||||
edit
|
||||
]);
|
||||
$(calendar).click(function () {
|
||||
data.hidden = !data.hidden;
|
||||
$(calendar).toggleClass('cp-active', !data.hidden);
|
||||
renderCalendar();
|
||||
});
|
||||
$calendars.append(calendar);
|
||||
});
|
||||
});
|
||||
onCalendarsUpdate.fire();
|
||||
};
|
||||
var makeCalendar = function (ctx) {
|
||||
var $container = $('#cp-sidebarlayout-container');
|
||||
var leftside;
|
||||
$container.append([
|
||||
leftside = h('div#cp-sidebarlayout-leftside'),
|
||||
h('div#cp-sidebarlayout-rightside')
|
||||
]);
|
||||
|
||||
var cal = APP.calendar = new Calendar('#cp-sidebarlayout-rightside', {
|
||||
defaultView: 'week', // weekly view option
|
||||
useCreationPopup: true,
|
||||
useDetailPopup: true,
|
||||
usageStatistics: false,
|
||||
calendars: getCalendars(),
|
||||
template: templates,
|
||||
month: {
|
||||
daynames: getWeekDays(),
|
||||
startDayOfWeek: 0,
|
||||
},
|
||||
week: {
|
||||
daynames: getWeekDays(),
|
||||
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)
|
||||
@ -211,8 +460,7 @@ Messages.calendar_default = "My calendar"; // XXX
|
||||
});
|
||||
});
|
||||
|
||||
cal.createSchedules(getSchedules(), true);
|
||||
cal.render();
|
||||
renderCalendar();
|
||||
};
|
||||
|
||||
var createToolbar = function () {
|
||||
@ -232,8 +480,7 @@ Messages.calendar_default = "My calendar"; // XXX
|
||||
var ev = obj.ev;
|
||||
var data = obj.data;
|
||||
if (ev === 'UPDATE') {
|
||||
console.log('Update');
|
||||
updateCalendar(data);
|
||||
onCalendarUpdate(data);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@ -98,6 +98,19 @@ ctx.calendars[channel] = {
|
||||
}, ctx.clients);
|
||||
};
|
||||
|
||||
var updateLocalCalendars = function (ctx, c, data) {
|
||||
// Also update local data
|
||||
c.stores.forEach(function (id) {
|
||||
var s = getStore(ctx, id);
|
||||
if (!s || !s.proxy) { return; }
|
||||
if (!s.rpc) { return; } // team viewer
|
||||
if (!s.proxy.calendars) { return; }
|
||||
var cal = s.proxy.calendars[c.channel];
|
||||
if (!cal) { return; }
|
||||
if (cal.color !== data.color) { cal.color = data.color; }
|
||||
if (cal.title !== data.title) { cal.title = data.title; }
|
||||
});
|
||||
};
|
||||
var openChannel = function (ctx, cfg, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb || function () {}));
|
||||
var teamId = cfg.storeId;
|
||||
@ -175,7 +188,13 @@ ctx.calendars[channel] = {
|
||||
setTimeout(update);
|
||||
if (cb) { cb(null, lm.proxy); }
|
||||
}).on('change', [], function () {
|
||||
if (!c.ready) { return; }
|
||||
setTimeout(update);
|
||||
}).on('change', ['metadata'], function () {
|
||||
// if title or color have changed, update our local values
|
||||
var md = proxy.metadata;
|
||||
if (!md || !md.title || !md.color) { return; }
|
||||
updateLocalCalendars(ctx, c, md);
|
||||
}).on('error', function (info) {
|
||||
if (info && info.error) { cb(info); }
|
||||
});
|
||||
@ -225,7 +244,6 @@ ctx.calendars[channel] = {
|
||||
};
|
||||
|
||||
var createCalendar = function (ctx, data, cId, cb) {
|
||||
console.error(data);
|
||||
var store = getStore(ctx, data.teamId);
|
||||
if (!store) { return void cb({error: "NO_STORE"}); }
|
||||
// Check team edit rights: viewers in teams don't have rpc
|
||||
@ -246,9 +264,38 @@ ctx.calendars[channel] = {
|
||||
}
|
||||
// Add the calendar and call back
|
||||
c[cal.channel] = cal;
|
||||
// XXX PIN (also make sure it's included in the reset)
|
||||
ctx.Store.onSync(store.id, cb);
|
||||
});
|
||||
};
|
||||
var updateCalendar = function (ctx, data, cId, cb) {
|
||||
console.error(data);
|
||||
var id = data.id;
|
||||
var c = ctx.calendars[id];
|
||||
if (!c) { return void cb({error: "ENOENT"}); }
|
||||
var md = Util.find(c, ['proxy', 'metadata']);
|
||||
if (!md) { return void cb({error: 'EINVAL'}); }
|
||||
md.title = data.title;
|
||||
md.color = data.color;
|
||||
Realtime.whenRealtimeSyncs(c.lm.realtime, cb);
|
||||
sendUpdate(ctx, c);
|
||||
|
||||
updateLocalCalendars(ctx, c, data);
|
||||
};
|
||||
var deleteCalendar = function (ctx, data, cId, cb) {
|
||||
var store = getStore(ctx, data.teamId);
|
||||
if (!store) { return void cb({error: "NO_STORE"}); }
|
||||
if (!store.rpc) { return void cb({error: "EFORBIDDEN"}); }
|
||||
if (!store.proxy.calendars) { return; }
|
||||
var id = data.id;
|
||||
var cal = store.proxy.calendars[id];
|
||||
if (!cal) { return void cb(); } // Already deleted
|
||||
|
||||
delete store.proxy.calendars[id];
|
||||
ctx.Store.onSync(store.id, cb);
|
||||
// XXX broadcast to inner
|
||||
};
|
||||
// XXX when we destroy a calendar, make sure we also delete it
|
||||
|
||||
var createEvent = function (ctx, data, cId, cb) {
|
||||
var id = data.calendarId;
|
||||
@ -322,6 +369,9 @@ ctx.calendars[channel] = {
|
||||
if (cmd === 'CREATE') {
|
||||
return void createCalendar(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'UPDATE') {
|
||||
return void updateCalendar(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'CREATE_EVENT') {
|
||||
return void createEvent(ctx, data, clientId, cb);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user