Merge branch 'rrule' into staging
This commit is contained in:
commit
b2022f5e73
|
@ -183,6 +183,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.cp-dropdown-content {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-alertify-type-container {
|
||||
overflow: visible !important;
|
||||
|
|
|
@ -120,6 +120,7 @@
|
|||
border-width: 0 @checkmark-width @checkmark-width 0;
|
||||
border-width: 0 var(--checkmark-width) var(--checkmark-width) 0;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 5px @cp_checkmark-back1;
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
|
||||
button {
|
||||
.fa-caret-down {
|
||||
margin-right: 1em !important;
|
||||
margin-right: 0.5em !important;
|
||||
}
|
||||
* {
|
||||
.tools_unselectable();
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
height: 100%;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
background-color: @cp_buttons-primary;
|
||||
background-color: @cp_buttons-default-color;
|
||||
&.danger, &.btn-danger, &.danger-alt, &.btn-danger-alt {
|
||||
background-color: @cp_buttons-red;
|
||||
}
|
||||
|
@ -327,6 +327,9 @@
|
|||
fill: @cryptpad_text_col;
|
||||
}
|
||||
}
|
||||
.flatpickr-monthDropdown-month {
|
||||
background: @cp_flatpickr-bg;
|
||||
}
|
||||
}
|
||||
.flatpickr-current-month {
|
||||
span.cur-month:hover {
|
||||
|
|
|
@ -27,14 +27,16 @@
|
|||
color: @cryptpad_color_red;
|
||||
}
|
||||
}
|
||||
.cp-avatar {
|
||||
.avatar_main(30px);
|
||||
padding: 0 5px;
|
||||
.cp-reminder, .cp-avatar {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: @cp_dropdown-bg-hover;
|
||||
}
|
||||
}
|
||||
.cp-avatar {
|
||||
.avatar_main(30px);
|
||||
padding: 0 5px;
|
||||
}
|
||||
.cp-notification-content {
|
||||
flex: 1;
|
||||
align-items: stretch;
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
.cp-small { display: none; }
|
||||
}
|
||||
|
||||
.flatpickr-calendar {
|
||||
z-index: 100001 !important; // Alertify is 100000
|
||||
}
|
||||
#cp-sidebarlayout-container #cp-sidebarlayout-rightside {
|
||||
padding: 0;
|
||||
& > div {
|
||||
|
@ -101,6 +104,21 @@
|
|||
color: @cryptpad_text_col !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-full-calendar-floating-layer.cp-calendar-popup-flex {
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
.tui-full-calendar-popup {
|
||||
width: 540px !important;
|
||||
}
|
||||
}
|
||||
#tui-full-calendar-popup-arrow {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.tui-full-calendar-timegrid-timezone {
|
||||
background-color: @cp_sidebar-right-bg !important;
|
||||
|
@ -116,13 +134,30 @@
|
|||
border-color: @cp_calendar-border !important;
|
||||
|
||||
}
|
||||
.tui-full-calendar-popup {
|
||||
border-radius: @variables_radius_L;
|
||||
}
|
||||
.tui-full-calendar-popup-container {
|
||||
background: @cp_flatpickr-bg;
|
||||
color: @cryptpad_text_col;
|
||||
border-radius: @variables_radius;
|
||||
border-radius: @variables_radius_L;
|
||||
font-weight: normal;
|
||||
.tui-full-calendar-icon:not(.tui-full-calendar-calendar-dot):not(.tui-full-calendar-dropdown-arrow):not(.tui-full-calendar-ic-checkbox) {
|
||||
display: none;
|
||||
}
|
||||
.tui-full-calendar-popup-detail-item {
|
||||
a {
|
||||
color: @cryptpad_color_link;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.tui-full-calendar-section-button-save {
|
||||
height: 40px;
|
||||
.btn-primary { // Update button
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
li.tui-full-calendar-popup-section-item {
|
||||
padding: 0 6px;
|
||||
|
@ -186,6 +221,9 @@
|
|||
width: 100%;
|
||||
height: 32px;
|
||||
border-radius: @variables_radius;
|
||||
input[type="checkbox"].tui-full-calendar-checkbox-square:checked + span {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABbmlDQ1BpY2MAACiRdZHNK0RRGMZ/Zoh8NIqFNItZDFmMEiVLxsJmkgZlsLn3zpe6c93uvZNkq2wspizExtfCf8BW2VJKkZKs/AG+Npqu97hqJM7t3PfXc87zds5zIJQyjZJbPwAly3PSE8nYXGY+1vhEMy20EyaqGa49NjWV4t/xfkOdqtf9qtf/+/4cLdmca0Bdk/CwYTue8KhwasWzFW8KdxpFLSu8L5xw5IDCF0rXA35UXAj4VbEzkx6HkOoZK/xg/QcbRack3CccL5ll4/s86iatOWt2Wmq3zCguaSZIEkOnzBImHv1SLcnsb9/Al2+SZfEY8rdZxRFHgaJ4E6KWpWtOal70nHwmqyr333m6+aHBoHtrEhoefP+lBxq3oFrx/Y8D368eQvgezqyaf1lyGnkTvVLT4nsQWYeT85qmb8PpBnTd2ZqjfUlhmaF8Hp6PoS0DHVfQvBBk9b3O0S3MrMkTXcLOLvTK/sjiJ6CLZ94KREMsAAAACXBIWXMAAAsSAAALEgHS3X78AAABHUlEQVQoFWNkaP//n4EMwESGHrAW6mtkZvz3G2g03BsBagz/nRUQ7sNp49//TJ+Byv6BlMbrMjCsC2Jg3BbKwOAgB9GMolGAg4GBESIOIrmA+A9I03xvFHGwCrhGkDOe5TAw9LvAFbEn6jEwwTT9BtodvJ7h/4FHYH0MLBCKgaHTgYGBE8jLN2FgYAJae+4FA8NcLwZWkAtAmoLWMTBsuYNwECMsHrVEGBj2RzEwiIEciATANgE1bb6DJAgMNLhTr71hYHBcxsDw+htCAQ5NIAU/4RpBPKjmf2++MfwHaQpZD7cJyEMB3+BORRL+rybE8FOEk4Hj2FO4KMgdrFAMitun2DTCVSMxYAkBFFYg9j94qCIpwsZEil5wyDIDAAXIUsnSKmq7AAAAAElFTkSuQmCC);
|
||||
}
|
||||
.tui-full-calendar-ic-checkbox {
|
||||
margin-left: 5px;
|
||||
border-radius: 2px;
|
||||
|
@ -196,6 +234,7 @@
|
|||
.tui-full-calendar-popup-detail {
|
||||
font: @colortheme_app-font;
|
||||
color: @cryptpad_text_col;
|
||||
box-shadow: @cryptpad_ui_shadow;
|
||||
.tui-full-calendar-popup-container {
|
||||
padding-bottom: 17px;
|
||||
}
|
||||
|
@ -203,28 +242,44 @@
|
|||
font-size: 14px;
|
||||
}
|
||||
.tui-full-calendar-section-button {
|
||||
margin-top: 10px;
|
||||
border: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: start;
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.tui-full-calendar-popup-top-line {
|
||||
border-radius: 10px 10px 0px 0px;
|
||||
height: 10px;
|
||||
}
|
||||
.tui-full-calendar-popup-vertical-line {
|
||||
visibility: hidden;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-recurrence-label, .cp-notif-label {
|
||||
color: @cryptpad_text_col;
|
||||
margin-right: 1rem;
|
||||
i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-calendar-recurrence-container {
|
||||
margin-top: 1rem;
|
||||
.cp-calendar-rec-translated-str {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-calendar-add-notif {
|
||||
flex-flow: column;
|
||||
align-items: baseline !important;
|
||||
margin: 10px 0;
|
||||
.cp-notif-label {
|
||||
color: @cp_sidebar-hint;
|
||||
margin-right: 20px;
|
||||
}
|
||||
margin: 1rem 0;
|
||||
* {
|
||||
font-size: @colortheme_app-font-size;
|
||||
font-weight: normal;
|
||||
|
@ -234,34 +289,58 @@
|
|||
}
|
||||
.cp-calendar-notif-list-container {
|
||||
margin-bottom: 10px;
|
||||
.cp-notif-label {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
.cp-calendar-notif-list {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.cp-notif-entry {
|
||||
margin-bottom: 2px;
|
||||
border-radius: @variables_radius;
|
||||
background-color: fade(@cryptpad_text_col, 10%);
|
||||
padding: 0.25rem;
|
||||
.cp-notif-value {
|
||||
width: 170px;
|
||||
display: inline-flex;
|
||||
line-height: 30px;
|
||||
vertical-align: middle;
|
||||
.cp-before {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
span:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
margin: 0px 5px;
|
||||
}
|
||||
.btn-danger-outline {
|
||||
margin-right: 0px !important;
|
||||
background-color: transparent;
|
||||
color: @cryptpad_text_col;
|
||||
border-color: @cryptpad_text_col;
|
||||
&:hover {
|
||||
color: @cp_buttons-red-color;
|
||||
background-color: @cp_buttons-red;
|
||||
border-color: @cp_buttons-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-notif-empty {
|
||||
display: none;
|
||||
margin-bottom: 2px;
|
||||
border-radius: @variables_radius;
|
||||
background-color: fade(@cryptpad_text_col, 10%);
|
||||
padding: 0.25rem 0.5rem;
|
||||
line-height: 30px;
|
||||
}
|
||||
.cp-calendar-notif-list:empty ~ .cp-notif-empty {
|
||||
display: block;
|
||||
}
|
||||
.cp-calendar-notif-form {
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
// margin-bottom: 20px;
|
||||
input {
|
||||
width: 80px;
|
||||
margin-right: 5px;
|
||||
|
@ -270,15 +349,120 @@
|
|||
}
|
||||
|
||||
.cp-calendar-close {
|
||||
top: 17px;
|
||||
right: 17px;
|
||||
height: auto;
|
||||
margin-right: 0px;
|
||||
line-height: initial;
|
||||
border: 1px solid;
|
||||
&:not(:hover) {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.cp-calendar-rec-inline, .cp-calendar-rec-block {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.cp-calendar-rec-inline {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
& > *:not(:first-child) { margin-left: 5px; }
|
||||
.cp-dropdown-container {
|
||||
position: unset;
|
||||
}
|
||||
input[type="number"] {
|
||||
width: 80px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.cp-checkmark {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
.cp-calendar-rec-block {
|
||||
.cp-calendar-rec-block-title {
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
.cp-radio {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
input[type="radio"]:not(:checked) ~ .cp-checkmark-label {
|
||||
input {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
.cp-checkmark-label {
|
||||
& > *:not(:first-child) { margin-left: 5px; }
|
||||
width: 100%;
|
||||
//height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > input {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
input {
|
||||
display: inline;
|
||||
height: 24px !important;
|
||||
padding: 0 5px !important;
|
||||
}
|
||||
input[type="text"] {
|
||||
width: 200px !important;
|
||||
}
|
||||
input[type="number"] {
|
||||
width: 80px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
#cp-calendar-rec-monthly-pick ~ .cp-checkmark-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& > span {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
button.cp-calendar-pick-el {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
div.cp-calendar-weekly-pick {
|
||||
button {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
div.cp-calendar-monthly-pick {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
& > div {
|
||||
display: flex;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
button {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
&.lastday {
|
||||
width: 115px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tui-full-calendar-ic-repeat-b {
|
||||
display: none;
|
||||
& ~ * {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#cp-toolbar .cp-calendar-browse {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -395,6 +579,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: @variables_radius;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
&.cp-active {
|
||||
background-color: @cp_sidebar-left-item-bg;
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
// Calendars will be exported using this format instead of plain text.
|
||||
define([
|
||||
'/customize/pages.js',
|
||||
], function (Pages) {
|
||||
'/common/common-util.js',
|
||||
'/calendar/recurrence.js'
|
||||
], function (Pages, Util, Rec) {
|
||||
var module = {};
|
||||
|
||||
var getICSDate = function (str) {
|
||||
|
@ -57,60 +59,197 @@ define([
|
|||
var data = content[uid];
|
||||
// DTSTAMP: now...
|
||||
// UID: uid
|
||||
var start, end;
|
||||
if (data.isAllDay && data.startDay && data.endDay) {
|
||||
start = "DTSTART;VALUE=DATE:" + getDate(data.startDay);
|
||||
end = "DTEND;VALUE=DATE:" + getDate(data.endDay, true);
|
||||
} else {
|
||||
start = "DTSTART:"+getICSDate(data.start);
|
||||
end = "DTEND:"+getICSDate(data.end);
|
||||
}
|
||||
var getDT = function (data) {
|
||||
var start, end;
|
||||
if (data.isAllDay) {
|
||||
var startDate = new Date(data.start);
|
||||
var endDate = new Date(data.end);
|
||||
data.startDay = data.startDay || (startDate.getFullYear() + '-' + (startDate.getMonth()+1) + '-' + startDate.getDate());
|
||||
data.endDay = data.endDay || (endDate.getFullYear() + '-' + (endDate.getMonth()+1) + '-' + endDate.getDate());
|
||||
start = "DTSTART;VALUE=DATE:" + getDate(data.startDay);
|
||||
end = "DTEND;VALUE=DATE:" + getDate(data.endDay, true);
|
||||
} else {
|
||||
start = "DTSTART:"+getICSDate(data.start);
|
||||
end = "DTEND:"+getICSDate(data.end);
|
||||
}
|
||||
return {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
};
|
||||
|
||||
Array.prototype.push.apply(ICS, [
|
||||
'BEGIN:VEVENT',
|
||||
'DTSTAMP:'+getICSDate(+new Date()),
|
||||
'UID:'+uid,
|
||||
start,
|
||||
end,
|
||||
'SUMMARY:'+ data.title,
|
||||
'LOCATION:'+ data.location,
|
||||
]);
|
||||
|
||||
if (Array.isArray(data.reminders)) {
|
||||
data.reminders.forEach(function (valueMin) {
|
||||
var time = valueMin * 60;
|
||||
var days = Math.floor(time / DAY);
|
||||
time -= days * DAY;
|
||||
var hours = Math.floor(time / HOUR);
|
||||
time -= hours * HOUR;
|
||||
var minutes = Math.floor(time / MINUTE);
|
||||
time -= minutes * MINUTE;
|
||||
var seconds = time;
|
||||
|
||||
var str = "-P" + days + "D";
|
||||
if (hours || minutes || seconds) {
|
||||
str += "T" + hours + "H" + minutes + "M" + seconds + "S";
|
||||
var getRRule = function (data) {
|
||||
if (!data.recurrenceRule || !data.recurrenceRule.freq) { return; }
|
||||
var r = data.recurrenceRule;
|
||||
var rrule = "RRULE:";
|
||||
rrule += "FREQ="+r.freq.toUpperCase();
|
||||
Object.keys(r).forEach(function (k) {
|
||||
if (k === "freq") { return; }
|
||||
if (k === "by") {
|
||||
Object.keys(r.by).forEach(function (_k) {
|
||||
rrule += ";BY"+_k.toUpperCase()+"="+r.by[_k];
|
||||
});
|
||||
return;
|
||||
}
|
||||
rrule += ";"+k.toUpperCase()+"="+r[k];
|
||||
});
|
||||
return rrule;
|
||||
};
|
||||
|
||||
|
||||
|
||||
var addEvent = function (arr, data, recId) {
|
||||
var uid = data.id;
|
||||
var dt = getDT(data);
|
||||
var start = dt.start;
|
||||
var end = dt.end;
|
||||
var rrule = getRRule(data);
|
||||
|
||||
Array.prototype.push.apply(arr, [
|
||||
'BEGIN:VEVENT',
|
||||
'DTSTAMP:'+getICSDate(+new Date()),
|
||||
'UID:'+uid,
|
||||
start,
|
||||
end,
|
||||
recId,
|
||||
rrule,
|
||||
'SUMMARY:'+ data.title,
|
||||
'LOCATION:'+ data.location,
|
||||
].filter(Boolean));
|
||||
|
||||
if (Array.isArray(data.reminders)) {
|
||||
data.reminders.forEach(function (valueMin) {
|
||||
var time = valueMin * 60;
|
||||
var days = Math.floor(time / DAY);
|
||||
time -= days * DAY;
|
||||
var hours = Math.floor(time / HOUR);
|
||||
time -= hours * HOUR;
|
||||
var minutes = Math.floor(time / MINUTE);
|
||||
time -= minutes * MINUTE;
|
||||
var seconds = time;
|
||||
|
||||
var str = "-P" + days + "D";
|
||||
if (hours || minutes || seconds) {
|
||||
str += "T" + hours + "H" + minutes + "M" + seconds + "S";
|
||||
}
|
||||
Array.prototype.push.apply(arr, [
|
||||
'BEGIN:VALARM',
|
||||
'ACTION:DISPLAY',
|
||||
'DESCRIPTION:This is an event reminder',
|
||||
'TRIGGER:'+str,
|
||||
'END:VALARM'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(data.cp_hidden)) {
|
||||
Array.prototype.push.apply(arr, data.cp_hidden);
|
||||
}
|
||||
|
||||
arr.push('END:VEVENT');
|
||||
};
|
||||
|
||||
|
||||
var applyChanges = function (base, changes) {
|
||||
var applyDiff = function (obj, k) {
|
||||
var diff = obj[k]; // Diff is always compared to origin start/end
|
||||
var d = new Date(base[k]);
|
||||
d.setDate(d.getDate() + diff.d);
|
||||
d.setHours(d.getHours() + diff.h);
|
||||
d.setMinutes(d.getMinutes() + diff.m);
|
||||
base[k] = +d;
|
||||
};
|
||||
Object.keys(changes || {}).forEach(function (k) {
|
||||
if (k === "start" || k === "end") {
|
||||
return applyDiff(changes, k);
|
||||
}
|
||||
base[k] = changes[k];
|
||||
});
|
||||
};
|
||||
|
||||
var prev = data;
|
||||
|
||||
// Check if we have "one-time" or "from date" updates.
|
||||
// "One-time" updates will be added accordingly to the ICS specs
|
||||
// "From date" updates will be added as new events and will add
|
||||
// an "until" value to the initial event's RRULE
|
||||
var toAdd = [];
|
||||
if (data.recurrenceRule && data.recurrenceRule.freq && data.recUpdate) {
|
||||
var ru = data.recUpdate;
|
||||
var _all = {};
|
||||
var duration = data.end - data.start;
|
||||
|
||||
var all = Rec.getAllOccurrences(data); // "false" if infinite
|
||||
|
||||
Object.keys(ru.from || {}).forEach(function (d) {
|
||||
if (!Object.keys(ru.from[d] || {}).length) { return; }
|
||||
_all[d] = _all[d] || {};
|
||||
_all[d].from = ru.from[d];
|
||||
});
|
||||
Object.keys(ru.one || {}).forEach(function (d) {
|
||||
if (!Object.keys(ru.one[d] || {}).length) { return; }
|
||||
_all[d] = _all[d] || {};
|
||||
_all[d].one = ru.one[d];
|
||||
});
|
||||
Object.keys(_all).sort(function (a, b) {
|
||||
return Number(a) - Number(b);
|
||||
}).forEach(function (d) {
|
||||
d = Number(d);
|
||||
var r = prev.recurrenceRule;
|
||||
|
||||
// This rule won't apply if we've reached "until" or "count"
|
||||
var idx = all && all.indexOf(d);
|
||||
if (all && idx === -1) {
|
||||
// Make sure we don't have both count and until
|
||||
if (all.length === r.count) { delete r.until; }
|
||||
else { delete r.count; }
|
||||
return;
|
||||
}
|
||||
|
||||
var ud = _all[d];
|
||||
|
||||
if (ud.from) { // "From" updates are not supported by ICS: make a new event
|
||||
var _new = Util.clone(prev);
|
||||
r.until = getICSDate(d - 1); // Stop previous recursion
|
||||
delete r.count;
|
||||
addEvent(ICS, prev, null); // Add previous event
|
||||
Array.prototype.push.apply(ICS, toAdd); // Add individual updates
|
||||
toAdd = [];
|
||||
prev = _new;
|
||||
if (all) { all = all.slice(idx); }
|
||||
|
||||
// if we updated the recurrence rule, count is reset, nothing to do
|
||||
// if we didn't update the recurrence, we need to fix the count
|
||||
var _r = _new.recurrenceRule;
|
||||
if (all && !ud.from.recurrenceRule && _r && _r.count) {
|
||||
_r.count -= idx;
|
||||
}
|
||||
|
||||
prev.start = d;
|
||||
prev.end = d + duration;
|
||||
prev.id = Util.uid();
|
||||
applyChanges(prev, ud.from);
|
||||
duration = prev.end - prev.start;
|
||||
}
|
||||
if (ud.one) { // Add update
|
||||
var _one = Util.clone(prev);
|
||||
_one.start = d;
|
||||
_one.end = d + duration;
|
||||
applyChanges(_one, ud.one);
|
||||
var recId = "RECURRENCE-ID:"+getICSDate(+d);
|
||||
delete _one.recurrenceRule;
|
||||
addEvent(toAdd, _one, recId); // Add updated event
|
||||
}
|
||||
Array.prototype.push.apply(ICS, [
|
||||
'BEGIN:VALARM',
|
||||
'ACTION:DISPLAY',
|
||||
'DESCRIPTION:This is an event reminder',
|
||||
'TRIGGER:'+str,
|
||||
'END:VALARM'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(data.cp_hidden)) {
|
||||
Array.prototype.push.apply(ICS, data.cp_hidden);
|
||||
}
|
||||
|
||||
ICS.push('END:VEVENT');
|
||||
addEvent(ICS, prev);
|
||||
Array.prototype.push.apply(ICS, toAdd); // Add individual updates
|
||||
});
|
||||
|
||||
ICS.push('END:VCALENDAR');
|
||||
|
||||
return new Blob([ ICS.join('\n') ], { type: 'text/calendar;charset=utf-8' });
|
||||
return new Blob([ ICS.join('\r\n') ], { type: 'text/calendar;charset=utf-8' });
|
||||
};
|
||||
|
||||
module.import = function (content, id, cb) {
|
||||
|
@ -171,7 +310,7 @@ define([
|
|||
}
|
||||
|
||||
// Store other properties
|
||||
var used = ['dtstart', 'dtend', 'uid', 'summary', 'location', 'dtstamp'];
|
||||
var used = ['dtstart', 'dtend', 'uid', 'summary', 'location', 'dtstamp', 'rrule', 'recurrence-id'];
|
||||
var hidden = [];
|
||||
ev.getAllProperties().forEach(function (p) {
|
||||
if (used.indexOf(p.name) !== -1) { return; }
|
||||
|
@ -192,8 +331,25 @@ define([
|
|||
if (reminders.indexOf(minutes) === -1) { reminders.push(minutes); }
|
||||
});
|
||||
|
||||
// Get recurrence rule
|
||||
var rrule = ev.getFirstPropertyValue('rrule');
|
||||
var rec;
|
||||
if (rrule && rrule.freq) {
|
||||
rec = {};
|
||||
rec.freq = rrule.freq.toLowerCase();
|
||||
if (rrule.interval) { rec.interval = rrule.interval; }
|
||||
if (rrule.count) { rec.count = rrule.count; }
|
||||
if (Object.keys(rrule).includes('wkst')) { rec.wkst = (rrule.wkst + 6) % 7; }
|
||||
if (rrule.until) { rec.until = +new Date(rrule.until); }
|
||||
Object.keys(rrule.parts || {}).forEach(function (k) {
|
||||
rec.by = rec.by || {};
|
||||
var _k = k.toLowerCase().slice(2); // "BYDAY" ==> "day"
|
||||
rec.by[_k] = rrule.parts[k];
|
||||
});
|
||||
}
|
||||
|
||||
// Create event
|
||||
res[uid] = {
|
||||
var obj = {
|
||||
calendarId: id,
|
||||
id: uid,
|
||||
category: 'time',
|
||||
|
@ -203,15 +359,48 @@ define([
|
|||
start: start,
|
||||
end: end,
|
||||
reminders: reminders,
|
||||
cp_hidden: hidden
|
||||
cp_hidden: hidden,
|
||||
};
|
||||
if (rec) { obj.recurrenceRule = rec; }
|
||||
|
||||
if (!hidden.length) { delete res[uid].cp_hidden; }
|
||||
if (!reminders.length) { delete res[uid].reminders; }
|
||||
if (!hidden.length) { delete obj.cp_hidden; }
|
||||
if (!reminders.length) { delete obj.reminders; }
|
||||
|
||||
var recId = ev.getFirstPropertyValue('recurrence-id');
|
||||
if (recId) {
|
||||
setTimeout(function () {
|
||||
if (!res[uid]) { return; }
|
||||
var old = res[uid];
|
||||
var time = +new Date(recId);
|
||||
var diff = {};
|
||||
var from = {};
|
||||
Object.keys(obj).forEach(function (k) {
|
||||
if (JSON.stringify(old[k]) === JSON.stringify(obj[k])) { return; }
|
||||
if (['start','end'].includes(k)) {
|
||||
diff[k] = Rec.diffDate(old[k], obj[k]);
|
||||
return;
|
||||
}
|
||||
if (k === "recurrenceRule") {
|
||||
from[k] = obj[k];
|
||||
return;
|
||||
}
|
||||
diff[k] = obj[k];
|
||||
});
|
||||
old.recUpdate = old.recUpdate || {one:{},from:{}};
|
||||
if (Object.keys(from).length) { old.recUpdate.from[time] = from; }
|
||||
if (Object.keys(diff).length) { old.recUpdate.one[time] = diff; }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res[uid] = obj;
|
||||
});
|
||||
|
||||
cb(null, res);
|
||||
// setTimeout to make sure we call back after the "recurrence-id" setTimeout
|
||||
// are called
|
||||
setTimeout(function () {
|
||||
cb(null, res);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,13 +6,21 @@ define([
|
|||
'/common/sframe-common-outer.js',
|
||||
], function (nThen, ApiConfig, DomReady, SFCommonO) {
|
||||
|
||||
// Loaded in load #2
|
||||
var hash, href;
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
SFCommonO.initIframe(waitFor);
|
||||
var obj = SFCommonO.initIframe(waitFor, true);
|
||||
href = obj.href;
|
||||
hash = obj.hash;
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
var addData = function (meta) {
|
||||
var addData = function (meta, Cryptpad, user, Utils) {
|
||||
if (hash) {
|
||||
var parsed = Utils.Hash.parsePadUrl(href);
|
||||
if (parsed.hashData && parsed.hashData.newPadOpts) {
|
||||
meta.calendarOpts = Utils.Hash.decodeDataOptions(parsed.hashData.newPadOpts);
|
||||
}
|
||||
}
|
||||
meta.calendarHash = Boolean(window.location.hash);
|
||||
};
|
||||
SFCommonO.start({
|
||||
|
|
|
@ -0,0 +1,869 @@
|
|||
define([
|
||||
'/common/common-util.js',
|
||||
], function (Util) {
|
||||
var Rec = {};
|
||||
|
||||
var debug = function () {};
|
||||
|
||||
// Get week number with any "WKST" (firts day of the week)
|
||||
// Week 1 is the first week of the year containing at least 4 days in this year
|
||||
// It depends on which day is considered the first day of the week (default Monday)
|
||||
// In our case, wkst is a number matching the JS rule: 0 == Sunday
|
||||
var getWeekNo = Rec.getWeekNo = function (date, wkst) {
|
||||
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
|
||||
|
||||
var newYear = new Date(date.getFullYear(),0,1);
|
||||
var day = newYear.getDay() - wkst; //the day of week the year begins on
|
||||
day = (day >= 0 ? day : day + 7);
|
||||
var daynum = Math.floor((date.getTime() - newYear.getTime())/86400000) + 1;
|
||||
var weeknum;
|
||||
// Week 1 / week 53
|
||||
if (day < 4) {
|
||||
weeknum = Math.floor((daynum+day-1)/7) + 1;
|
||||
if (weeknum > 52) {
|
||||
var nYear = new Date(date.getFullYear() + 1,0,1);
|
||||
var nday = nYear.getDay() - wkst;
|
||||
nday = nday >= 0 ? nday : nday + 7;
|
||||
weeknum = nday < 4 ? 1 : 53;
|
||||
}
|
||||
}
|
||||
else {
|
||||
weeknum = Math.floor((daynum+day-1)/7);
|
||||
}
|
||||
return weeknum;
|
||||
};
|
||||
|
||||
var getYearDay = function (date) {
|
||||
var start = new Date(date.getFullYear(), 0, 0);
|
||||
var diff = (date - start) +
|
||||
((start.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000);
|
||||
var oneDay = 1000 * 60 * 60 * 24;
|
||||
return Math.floor(diff / oneDay);
|
||||
};
|
||||
var setYearDay = function (date, day) {
|
||||
if (typeof(day) !== "number" || Math.abs(day) < 1 || Math.abs(day) > 366) { return; }
|
||||
if (day < 0) {
|
||||
var max = getYearDay(new Date(date.getFullYear(), 11, 31));
|
||||
day = max + day + 1;
|
||||
}
|
||||
date.setMonth(0);
|
||||
date.setDate(day);
|
||||
return true;
|
||||
};
|
||||
|
||||
var getEndData = function (s, e) {
|
||||
if (s > e) { return void console.error("Wrong data"); }
|
||||
var days;
|
||||
if (e.getFullYear() === s.getFullYear()) {
|
||||
days = getYearDay(e) - getYearDay(s);
|
||||
} else { // eYear < sYear
|
||||
var tmp = new Date(s.getFullYear(), 11, 31);
|
||||
var d1 = getYearDay(tmp) - getYearDay(s); // Number of days before December 31st
|
||||
var de = getYearDay(e);
|
||||
days = d1 + de;
|
||||
while ((tmp.getFullYear()+1) < e.getFullYear()) {
|
||||
tmp.setFullYear(tmp.getFullYear()+1);
|
||||
days += getYearDay(tmp);
|
||||
}
|
||||
}
|
||||
return {
|
||||
h: e.getHours(),
|
||||
m: e.getMinutes(),
|
||||
days: days
|
||||
};
|
||||
};
|
||||
var setEndData = function (s, e, data) {
|
||||
e.setTime(+s);
|
||||
if (!data) { return; }
|
||||
e.setHours(data.h);
|
||||
e.setMinutes(data.m);
|
||||
e.setSeconds(0);
|
||||
e.setDate(s.getDate() + data.days);
|
||||
};
|
||||
|
||||
var DAYORDER = Rec.DAYORDER = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
|
||||
var getDayData = function (str) {
|
||||
var pos = Number(str.slice(0,-2));
|
||||
var day = DAYORDER.indexOf(str.slice(-2));
|
||||
return pos ? [pos, day] : day;
|
||||
};
|
||||
|
||||
var goToFirstWeekDay = function (date, wkst) {
|
||||
var d = date.getDay();
|
||||
wkst = typeof(wkst) === "number" ? wkst : 1;
|
||||
if (d >= wkst) {
|
||||
date.setDate(date.getDate() - (d-wkst));
|
||||
} else {
|
||||
date.setDate(date.getDate() - (7+d-wkst));
|
||||
}
|
||||
};
|
||||
|
||||
var getDateStr = function (date) {
|
||||
return date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate();
|
||||
};
|
||||
var FREQ = {};
|
||||
FREQ['daily'] = function (s, i) {
|
||||
s.setDate(s.getDate()+i);
|
||||
};
|
||||
FREQ['weekly'] = function (s,i) {
|
||||
s.setDate(s.getDate()+(i*7));
|
||||
};
|
||||
FREQ['monthly'] = function (s,i) {
|
||||
s.setMonth(s.getMonth()+i);
|
||||
};
|
||||
FREQ['yearly'] = function (s,i) {
|
||||
s.setFullYear(s.getFullYear()+i);
|
||||
};
|
||||
|
||||
// EXPAND is used to create iterations added from a BYxxx rule
|
||||
// dateA is the start date and b is the number or id of the BYxxx rule item
|
||||
var EXPAND = {};
|
||||
EXPAND['month'] = function (dateS, origin, b) {
|
||||
var oS = new Date(origin.start);
|
||||
var a = dateS.getMonth() + 1;
|
||||
var toAdd = (b-a+12)%12;
|
||||
var m = dateS.getMonth() + toAdd;
|
||||
dateS.setMonth(m);
|
||||
dateS.setDate(oS.getDate());
|
||||
if (dateS.getMonth() !== m) { return; } // Day 31 may move us to the next month
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
EXPAND['weekno'] = function (dateS, origin, week, rule) {
|
||||
var wkst = rule && rule.wkst;
|
||||
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
|
||||
var oS = new Date(origin.start);
|
||||
|
||||
var lastD = new Date(dateS.getFullYear(), 11, 31); // December 31st
|
||||
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
|
||||
|
||||
var doubleOne = lastW === 1;
|
||||
if (lastW === 1) { lastW = 52; }
|
||||
|
||||
var a = getWeekNo(dateS, wkst);
|
||||
if (!week || week > lastW) { return false; } // Week 53 may not exist this year
|
||||
|
||||
if (week < 0) { week = lastW + week + 1; } // Turn negative week number into positive
|
||||
|
||||
var toAdd = week - a;
|
||||
var weekS = new Date(+dateS);
|
||||
// Go to the selected week
|
||||
weekS.setDate(weekS.getDate() + (toAdd * 7));
|
||||
goToFirstWeekDay(weekS, wkst);
|
||||
|
||||
// Then make sure we are in the correct start day
|
||||
var all = 'aaaaaaa'.split('').map(function (o, i) {
|
||||
var date = new Date(+weekS);
|
||||
date.setDate(date.getDate() + i);
|
||||
if (date.getFullYear() !== dateS.getFullYear()) { return; }
|
||||
return date.toLocaleDateString() !== oS.toLocaleDateString() && date;
|
||||
}).filter(Boolean);
|
||||
|
||||
// If we're looking for week 1 and the last week is a week 1, add the days
|
||||
if (week === 1 && doubleOne) {
|
||||
goToFirstWeekDay(lastD, wkst);
|
||||
'aaaaaaa'.split('').some(function (o, i) {
|
||||
var date = new Date(+lastD);
|
||||
date.setDate(date.getDate() + i);
|
||||
if (date.toLocaleDateString() === oS.toLocaleDateString()) { return; }
|
||||
if (date.getFullYear() > dateS.getFullYear()) { return true; }
|
||||
all.push(date);
|
||||
});
|
||||
}
|
||||
|
||||
return all.length ? all : undefined;
|
||||
};
|
||||
EXPAND['yearday'] = function (dateS, origin, b) {
|
||||
var y = dateS.getFullYear();
|
||||
var state = setYearDay(dateS, b);
|
||||
if (!state) { return; } // Invalid day "b"
|
||||
if (dateS.getFullYear() !== y) { return; } // Day 366 make move us to the next year
|
||||
return true;
|
||||
};
|
||||
EXPAND['monthday'] = function (dateS, origin, b, rule) {
|
||||
if (typeof(b) !== "number" || Math.abs(b) < 1 || Math.abs(b) > 31) { return false; }
|
||||
|
||||
var setMonthDay = function (date, day) {
|
||||
var m = date.getMonth();
|
||||
if (day < 0) {
|
||||
var tmp = new Date(date.getFullYear(), date.getMonth()+1, 0); // Last day
|
||||
day = tmp.getDate() + day + 1;
|
||||
}
|
||||
date.setDate(day);
|
||||
return date.getMonth() === m; // Don't push if day 31 moved us to the next month
|
||||
|
||||
};
|
||||
|
||||
// Monthly events
|
||||
if (rule.freq === 'monthly') {
|
||||
return setMonthDay(dateS, b);
|
||||
}
|
||||
|
||||
var all = 'aaaaaaaaaaaa'.split('').map(function (o, i) {
|
||||
var date = new Date(dateS.getFullYear(), i, 1);
|
||||
var ok = setMonthDay(date, b);
|
||||
return ok ? date : undefined;
|
||||
}).filter(Boolean);
|
||||
return all.length ? all : undefined;
|
||||
};
|
||||
EXPAND['day'] = function (dateS, origin, b, rule) {
|
||||
// Here "b" can be a single day ("TU") or a position and a day ("1MO")
|
||||
var day = getDayData(b);
|
||||
var pos;
|
||||
if (Array.isArray(day)) {
|
||||
pos = day[0];
|
||||
day = day[1];
|
||||
}
|
||||
|
||||
var all = [];
|
||||
if (![0,1,2,3,4,5,6].includes(day)) { return false; }
|
||||
|
||||
var filterPos = function (m) {
|
||||
if (!pos) { return; }
|
||||
|
||||
var _all = [];
|
||||
'aaaaaaaaaaaa'.split('').some(function (a, i) {
|
||||
if (typeof(m) !== "undefined" && i !== m) { return; }
|
||||
|
||||
var _pos;
|
||||
var tmp = all.filter(function (d) {
|
||||
return d.getMonth() === i;
|
||||
});
|
||||
if (pos < 0) {
|
||||
_pos = tmp.length + pos;
|
||||
} else {
|
||||
_pos = pos - 1; // An array starts at 0 but the recurrence rule starts at 1
|
||||
}
|
||||
_all.push(tmp[_pos]);
|
||||
|
||||
return typeof(m) !== "undefined" && i === m;
|
||||
});
|
||||
all = _all.filter(Boolean); // The "5th" {day} won't always exist
|
||||
};
|
||||
|
||||
var tmp;
|
||||
if (rule.freq === 'yearly') {
|
||||
tmp = new Date(+dateS);
|
||||
var y = dateS.getFullYear();
|
||||
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
|
||||
while (tmp.getFullYear() === y) {
|
||||
all.push(new Date(+tmp));
|
||||
tmp.setDate(tmp.getDate()+7);
|
||||
}
|
||||
filterPos();
|
||||
return all;
|
||||
}
|
||||
|
||||
if (rule.freq === 'monthly') {
|
||||
tmp = new Date(+dateS);
|
||||
var m = dateS.getMonth();
|
||||
while (tmp.getDay() !== day) { tmp.setDate(tmp.getDate()+1); }
|
||||
while (tmp.getMonth() === m) {
|
||||
all.push(new Date(+tmp));
|
||||
tmp.setDate(tmp.getDate()+7);
|
||||
}
|
||||
filterPos(m);
|
||||
return all;
|
||||
}
|
||||
|
||||
if (rule.freq === 'weekly') {
|
||||
while (dateS.getDay() !== day) { dateS.setDate(dateS.getDate()+1); }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var LIMIT = {};
|
||||
LIMIT['month'] = function (events, rule) {
|
||||
return events.filter(function (s) {
|
||||
return rule.includes(s.getMonth()+1);
|
||||
});
|
||||
};
|
||||
LIMIT['weekno'] = function (events, weeks, rules) {
|
||||
return events.filter(function (s) {
|
||||
var wkst = rules && rules.wkst;
|
||||
if (typeof(wkst) !== "number") { wkst = 1; } // Default monday
|
||||
|
||||
var lastD = new Date(s.getFullYear(), 11, 31); // December 31st
|
||||
var lastW = getWeekNo(lastD, wkst); // Last week of the year is either 52 or 53
|
||||
if (lastW === 1) { lastW = 52; }
|
||||
|
||||
var w = getWeekNo(s, wkst);
|
||||
|
||||
return weeks.some(function (week) {
|
||||
if (week > 0) { return week === w; }
|
||||
return w === (lastW + week + 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
LIMIT['yearday'] = function (events, days) {
|
||||
return events.filter(function (s) {
|
||||
var d = getYearDay(s);
|
||||
var max = getYearDay(new Date(s.getFullYear(), 11, 31));
|
||||
|
||||
return days.some(function (day) {
|
||||
if (day > 0) { return day === d; }
|
||||
return d === (max + day + 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
LIMIT['monthday'] = function (events, rule) {
|
||||
return events.filter(function (s) {
|
||||
var r = Util.clone(rule);
|
||||
// Transform the negative monthdays into positive for this specific month
|
||||
r = r.map(function (b) {
|
||||
if (b < 0) {
|
||||
var tmp = new Date(s.getFullYear(), s.getMonth()+1, 0); // Last day
|
||||
b = tmp.getDate() + b + 1;
|
||||
}
|
||||
return b;
|
||||
});
|
||||
return r.includes(s.getDate());
|
||||
});
|
||||
};
|
||||
LIMIT['day'] = function (events, days, rules) {
|
||||
return events.filter(function (s) {
|
||||
var dayStr = s.toLocaleDateString();
|
||||
|
||||
// Check how to handle position in BYDAY rules (last day of the month or the year?)
|
||||
var type = 'yearly';
|
||||
if (rules.freq === 'monthly' ||
|
||||
(rules.freq === 'yearly' && rules.by && rules.by.month)) {
|
||||
type = 'monthly';
|
||||
}
|
||||
|
||||
// Check if this event matches one of the allowed days
|
||||
return days.some(function (r) {
|
||||
// rule elements are strings with pos and day
|
||||
var day = getDayData(r);
|
||||
var pos;
|
||||
if (Array.isArray(day)) {
|
||||
pos = day[0];
|
||||
day = day[1];
|
||||
}
|
||||
if (!pos) {
|
||||
return s.getDay() === day;
|
||||
}
|
||||
|
||||
// If we have a position, we can use EXPAND.day to get the nth {day} of the
|
||||
// year/month and compare if it matches with
|
||||
var d = new Date(s.getFullYear(), s.getMonth(), 1);
|
||||
if (type === 'yearly') { d.setMonth(0); }
|
||||
var res = EXPAND["day"](d, {}, r, {freq: type});
|
||||
return res.some(function (date) {
|
||||
return date.toLocaleDateString() === dayStr;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
LIMIT['setpos'] = function (events, rule) {
|
||||
var init = events.slice();
|
||||
var rules = Util.deduplicateString(rule.slice().map(function (n) {
|
||||
if (n > 0) { return (n-1); }
|
||||
if (n === 0) { return; }
|
||||
return init.length + n;
|
||||
}));
|
||||
return events.filter(function (ev) {
|
||||
var idx = init.indexOf(ev);
|
||||
return rules.includes(idx);
|
||||
});
|
||||
};
|
||||
|
||||
var BYORDER = ['month','weekno','yearday','monthday','day'];
|
||||
var BYDAYORDER = ['month','monthday','day'];
|
||||
|
||||
Rec.getMonthId = function (d) {
|
||||
return d.getFullYear() + '-' + d.getMonth();
|
||||
};
|
||||
var cache = window.CP_calendar_cache = {};
|
||||
var recurringAcross = {};
|
||||
Rec.resetCache = function () {
|
||||
cache = window.CP_calendar_cache = {};
|
||||
recurringAcross = {};
|
||||
};
|
||||
|
||||
var iterate = function (rule, _origin, s) {
|
||||
// "origin" is the original event to detect the start of BYxxx
|
||||
var origin = Util.clone(_origin);
|
||||
var oS = new Date(origin.start);
|
||||
|
||||
var id = origin.id.split('|')[0]; // Use same cache when updating recurrence rule
|
||||
|
||||
// "uid" is used for the cache
|
||||
var uid = s.toLocaleDateString();
|
||||
cache[id] = cache[id] || {};
|
||||
|
||||
var inter = rule.interval || 1;
|
||||
var freq = rule.freq;
|
||||
|
||||
var all = [];
|
||||
var limit = function (byrule, n) {
|
||||
all = LIMIT[byrule](all, n, rule);
|
||||
};
|
||||
var expand = function (byrule) {
|
||||
return function (n) {
|
||||
// Set the start date at the beginning of the current FREQ
|
||||
var _s = new Date(+s);
|
||||
if (rule.freq === 'yearly') {
|
||||
// January 1st
|
||||
_s.setMonth(0);
|
||||
_s.setDate(1);
|
||||
} else if (rule.freq === 'monthly') {
|
||||
_s.setDate(1);
|
||||
} else if (rule.freq === 'weekly') {
|
||||
goToFirstWeekDay(_s, rule.wkst);
|
||||
} else if (rule.freq === 'daily') {
|
||||
// We don't have < byday rules so we can't expand daily rules
|
||||
}
|
||||
|
||||
var add = EXPAND[byrule](_s, origin, n, rule);
|
||||
|
||||
if (!add) { return; }
|
||||
|
||||
if (Array.isArray(add)) {
|
||||
add = add.filter(function (dateS) {
|
||||
return dateS.toLocaleDateString() !== oS.toLocaleDateString();
|
||||
});
|
||||
Array.prototype.push.apply(all, add);
|
||||
} else {
|
||||
if (_s.toLocaleDateString() === oS.toLocaleDateString()) { return; }
|
||||
all.push(_s);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Manage interval for the next iteration
|
||||
var it = Util.once(function () {
|
||||
FREQ[freq](s, inter);
|
||||
});
|
||||
var addDefault = function () {
|
||||
if (freq === "monthly") {
|
||||
s.setDate(15);
|
||||
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
|
||||
s.setDate(28);
|
||||
}
|
||||
|
||||
it();
|
||||
|
||||
var _s = new Date(+s);
|
||||
if (freq === "monthly" || freq === "yearly") {
|
||||
_s.setDate(oS.getDate());
|
||||
if (_s.getDate() !== oS.getDate()) { return; } // If 31st or Feb 29th doesn't exist
|
||||
if (freq === "yearly" && _s.getMonth() !== oS.getMonth()) { return; }
|
||||
|
||||
// FIXME if there is a recUpdate that moves the 31st to the 30th, the event
|
||||
// will still only be displayed on months with 31 days
|
||||
}
|
||||
all.push(_s);
|
||||
};
|
||||
|
||||
if (Array.isArray(cache[id][uid])) {
|
||||
debug('Get cache', id, uid);
|
||||
if (freq === "monthly") {
|
||||
s.setDate(15);
|
||||
} else if (freq === "yearly" && oS.getMonth() === 1 && oS.getDate() === 29) {
|
||||
s.setDate(28);
|
||||
}
|
||||
it();
|
||||
return cache[id][uid];
|
||||
}
|
||||
|
||||
if (rule.by && freq === 'yearly') {
|
||||
var order = BYORDER.slice();
|
||||
var monthLimit = false;
|
||||
if (rule.by.weekno || rule.by.yearday || rule.by.monthday || rule.by.day) {
|
||||
order.shift();
|
||||
monthLimit = true;
|
||||
}
|
||||
var first = true;
|
||||
order.forEach(function (_order) {
|
||||
var r = rule.by[_order];
|
||||
if (!r) { return; }
|
||||
if (first) {
|
||||
r.forEach(expand(_order));
|
||||
first = false;
|
||||
} else if (_order === "day") {
|
||||
if (rule.by.yearday || rule.by.monthday || rule.by.weekno) {
|
||||
limit('day', rule.by.day);
|
||||
} else {
|
||||
rule.by.day.forEach(expand('day'));
|
||||
}
|
||||
} else {
|
||||
limit(_order, r);
|
||||
}
|
||||
});
|
||||
if (rule.by.month && monthLimit) {
|
||||
limit('month', rule.by.month);
|
||||
}
|
||||
}
|
||||
if (rule.by && freq === 'monthly') {
|
||||
// We're going to compute all the entries for the coming month
|
||||
if (!rule.by.monthday && !rule.by.day) {
|
||||
addDefault();
|
||||
} else if (rule.by.monthday) {
|
||||
rule.by.monthday.forEach(expand('monthday'));
|
||||
} else if (rule.by.day) {
|
||||
rule.by.day.forEach(expand('day'));
|
||||
}
|
||||
if (rule.by.month) {
|
||||
limit('month', rule.by.month);
|
||||
}
|
||||
if (rule.by.day && rule.by.monthday) {
|
||||
limit('day', rule.by.day);
|
||||
}
|
||||
}
|
||||
if (rule.by && freq === 'weekly') {
|
||||
// We're going to compute all the entries for the coming week
|
||||
if (!rule.by.day) {
|
||||
addDefault();
|
||||
} else {
|
||||
rule.by.day.forEach(expand('day'));
|
||||
}
|
||||
if (rule.by.month) {
|
||||
limit('month', rule.by.month);
|
||||
}
|
||||
}
|
||||
if (rule.by && freq === 'daily') {
|
||||
addDefault();
|
||||
BYDAYORDER.forEach(function (_order) {
|
||||
var r = rule.by[_order];
|
||||
if (!r) { return; }
|
||||
limit(_order, r);
|
||||
});
|
||||
}
|
||||
|
||||
all.sort(function (a, b) {
|
||||
return a-b;
|
||||
});
|
||||
|
||||
if (rule.by && rule.by.setpos) {
|
||||
limit('setpos', rule.by.setpos);
|
||||
}
|
||||
|
||||
if (!rule.by || !Object.keys(rule.by).length) {
|
||||
addDefault();
|
||||
} else {
|
||||
it();
|
||||
}
|
||||
|
||||
|
||||
var done = [];
|
||||
all = all.filter(function (newS) {
|
||||
var start = new Date(+newS).toLocaleDateString();
|
||||
if (done.includes(start)) { return false; }
|
||||
done.push(start);
|
||||
return true;
|
||||
});
|
||||
|
||||
debug('Set cache', id, uid);
|
||||
cache[id][uid] = all;
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
var getNextRules = function (obj) {
|
||||
if (!obj.recUpdate) { return []; }
|
||||
var _allRules = {};
|
||||
var _obj = obj.recUpdate.from;
|
||||
Object.keys(_obj || {}).forEach(function (d) {
|
||||
var u = _obj[d];
|
||||
if (u.recurrenceRule) { _allRules[d] = u.recurrenceRule; }
|
||||
});
|
||||
return Object.keys(_allRules).sort(function (a, b) { return Number(a)-Number(b); })
|
||||
.map(function (k) {
|
||||
var r = Util.clone(_allRules[k]);
|
||||
if (!FREQ[r.freq]) { return; }
|
||||
if (r.interval && r.interval < 1) { return; }
|
||||
r._start = Number(k);
|
||||
return r;
|
||||
}).filter(Boolean);
|
||||
};
|
||||
Rec.getRecurring = function (months, events) {
|
||||
if (window.CP_DEV_MODE) { debug = console.warn; }
|
||||
|
||||
var toAdd = [];
|
||||
months.forEach(function (monthId) {
|
||||
// from 1st day of the month at 00:00 to last day at 23:59:59:999
|
||||
var ms = monthId.split('-');
|
||||
var _startMonth = new Date(ms[0], ms[1]);
|
||||
var _endMonth = new Date(+_startMonth);
|
||||
_endMonth.setMonth(_endMonth.getMonth() + 1);
|
||||
_endMonth.setMilliseconds(-1);
|
||||
|
||||
debug('Compute month', _startMonth.toLocaleDateString());
|
||||
|
||||
var rec = events || [];
|
||||
rec.forEach(function (obj) {
|
||||
var _start = new Date(obj.start);
|
||||
var _end = new Date(obj.end);
|
||||
var _origin = obj;
|
||||
var rule = obj.recurrenceRule;
|
||||
if (!rule) { return; }
|
||||
|
||||
var nextRules = getNextRules(obj);
|
||||
var nextRule = nextRules.shift();
|
||||
|
||||
if (_start >= _endMonth) { return; }
|
||||
|
||||
// Check the "until" date of the latest rule we can use and stop now
|
||||
// if the recurrence ends before the current month
|
||||
var until = rule.until;
|
||||
var _nextRules = nextRules.slice();
|
||||
var _nextRule = nextRule;
|
||||
while (_nextRule && _nextRule._start && _nextRule._start < _startMonth) {
|
||||
until = nextRule.until;
|
||||
_nextRule = _nextRules.shift();
|
||||
}
|
||||
if (until < _startMonth) { return; }
|
||||
|
||||
var endData = getEndData(_start, _end);
|
||||
|
||||
if (rule.interval && rule.interval < 1) { return; }
|
||||
if (!FREQ[rule.freq]) { return; }
|
||||
|
||||
/*
|
||||
// Rule examples
|
||||
rule.by = {
|
||||
//month: [1, 4, 5, 8, 12],
|
||||
//weekno: [1, 2, 4, 5, 32, 34, 35, 50],
|
||||
//yearday: [1, 2, 29, 30, -2, -1, 250],
|
||||
//monthday: [1, 2, 3, -3, -2, -1],
|
||||
//day: ["MO", "WE", "FR"],
|
||||
//setpos: [1, 2, -1, -2]
|
||||
};
|
||||
rule.wkst = 0;
|
||||
rule.interval = 2;
|
||||
rule.freq = 'yearly';
|
||||
rule.count = 10;
|
||||
*/
|
||||
debug('Iterate over', obj.title, obj);
|
||||
debug('Use rule', rule);
|
||||
|
||||
var count = rule.count;
|
||||
var c = 1;
|
||||
|
||||
var next = function (start) {
|
||||
var evS = new Date(+start);
|
||||
|
||||
if (count && c >= count) { return; }
|
||||
|
||||
debug('Start iteration', evS.toLocaleDateString());
|
||||
|
||||
var _toAdd = iterate(rule, obj, evS);
|
||||
|
||||
debug('Iteration results', JSON.stringify(_toAdd.map(function (o) { return new Date(o).toLocaleDateString();})));
|
||||
|
||||
// Make sure to continue if the current year doesn't provide any result
|
||||
if (!_toAdd.length) {
|
||||
if (evS.getFullYear() < _startMonth.getFullYear() ||
|
||||
evS < _endMonth) {
|
||||
return void next(evS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var stop = false;
|
||||
var newrule = false;
|
||||
_toAdd.some(function (_newS) {
|
||||
// Make event with correct start and end time
|
||||
var _ev = Util.clone(obj);
|
||||
_ev.id = _origin.id + '|' + (+_newS);
|
||||
var _evS = new Date(+_newS);
|
||||
var _evE = new Date(+_newS);
|
||||
setEndData(_evS, _evE, endData);
|
||||
_ev.start = +_evS;
|
||||
_ev.end = +_evE;
|
||||
_ev._count = c;
|
||||
if (_ev.isAllDay && _ev.startDay) { _ev.startDay = getDateStr(_evS); }
|
||||
if (_ev.isAllDay && _ev.endDay) { _ev.endDay = getDateStr(_evE); }
|
||||
|
||||
if (nextRule && _ev.start === nextRule._start) {
|
||||
newrule = true;
|
||||
}
|
||||
|
||||
var useNewRule = function () {
|
||||
if (!newrule) { return; }
|
||||
debug('Use new rule', nextRule);
|
||||
_ev._count = c;
|
||||
count = nextRule.count;
|
||||
c = 1;
|
||||
evS = +_evS;
|
||||
obj = _ev;
|
||||
rule = nextRule;
|
||||
nextRule = nextRules.shift();
|
||||
};
|
||||
|
||||
|
||||
if (c >= count) { // Limit reached
|
||||
debug(_evS.toLocaleDateString(), 'count');
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (_evS >= _endMonth) { // Won't affect us anymore
|
||||
debug(_evS.toLocaleDateString(), 'endMonth');
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (rule.until && _evS > rule.until) {
|
||||
debug(_evS.toLocaleDateString(), 'until');
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (_evS < _start) { // "Expand" rules may create events before the _start
|
||||
debug(_evS.toLocaleDateString(), 'start');
|
||||
return;
|
||||
}
|
||||
c++;
|
||||
if (_evE < _startMonth) { // Ended before the current month
|
||||
// Nothing to display but continue the recurrence
|
||||
debug(_evS.toLocaleDateString(), 'startMonth');
|
||||
if (newrule) { useNewRule(); }
|
||||
return;
|
||||
}
|
||||
// If a recurring event start and end in different months, make sure
|
||||
// it is only added once
|
||||
if ((_evS < _endMonth && _evE >= _endMonth) ||
|
||||
(_evS < _startMonth && _evE >= _startMonth)) {
|
||||
if (recurringAcross[_ev.id] && recurringAcross[_ev.id].includes(_ev.start)) {
|
||||
return;
|
||||
} else {
|
||||
recurringAcross[_ev.id] = recurringAcross[_ev.id] || [];
|
||||
recurringAcross[_ev.id].push(_ev.start);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add this event
|
||||
toAdd.push(_ev);
|
||||
if (newrule) {
|
||||
useNewRule();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (!stop) { next(evS); }
|
||||
};
|
||||
next(_start);
|
||||
debug('Added this month (all events)', toAdd.map(function (ev) {
|
||||
return new Date(ev.start).toLocaleDateString();
|
||||
}));
|
||||
});
|
||||
});
|
||||
return toAdd;
|
||||
};
|
||||
Rec.getAllOccurrences = function (ev) {
|
||||
if (!ev.recurrenceRule) { return [ev.start]; }
|
||||
var r = ev.recurrenceRule;
|
||||
// In case of infinite recursion, we can't get all
|
||||
if (!r.until && !r.count) { return false; }
|
||||
var all = [ev.start];
|
||||
var d = new Date(ev.start);
|
||||
d.setDate(15); // Make sure we won't skip a month if the event starts on day > 28
|
||||
var toAdd = [];
|
||||
|
||||
var i = 0;
|
||||
var check = function () {
|
||||
return r.count ? (all.length < r.count) : (+d <= r.until);
|
||||
};
|
||||
while ((toAdd = Rec.getRecurring([Rec.getMonthId(d)], [ev])) && check() && i < (r.count*12)) {
|
||||
Array.prototype.push.apply(all, toAdd.map(function (_ev) { return _ev.start; }));
|
||||
d.setMonth(d.getMonth() + 1);
|
||||
i++;
|
||||
}
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
Rec.diffDate = function (oldTime, newTime) {
|
||||
var n = new Date(newTime);
|
||||
var o = new Date(oldTime);
|
||||
|
||||
// Diff Days
|
||||
var d = 0;
|
||||
var mult = n < o ? -1 : 1;
|
||||
while (n.toLocaleDateString() !== o.toLocaleDateString() || mult >= 10000) {
|
||||
n.setDate(n.getDate() - mult);
|
||||
d++;
|
||||
}
|
||||
d = mult * d;
|
||||
|
||||
// Diff hours
|
||||
n = new Date(newTime);
|
||||
var h = n.getHours() - o.getHours();
|
||||
|
||||
// Diff minutes
|
||||
var m = n.getMinutes() - o.getMinutes();
|
||||
|
||||
return {
|
||||
d: d,
|
||||
h: h,
|
||||
m: m
|
||||
};
|
||||
};
|
||||
|
||||
var sortUpdate = function (obj) {
|
||||
return Object.keys(obj).sort(function (d1, d2) {
|
||||
return Number(d1) - Number(d2);
|
||||
});
|
||||
};
|
||||
Rec.applyUpdates = function (events) {
|
||||
events.forEach(function (ev) {
|
||||
ev.raw = {
|
||||
start: ev.start,
|
||||
end: ev.end,
|
||||
};
|
||||
|
||||
if (!ev.recUpdate) { return; }
|
||||
|
||||
var from = ev.recUpdate.from || {};
|
||||
var one = ev.recUpdate.one || {};
|
||||
var s = ev.start;
|
||||
|
||||
// Add "until" date to our recurrenceRule if it has been modified in future occurences
|
||||
var nextRules = getNextRules(ev).filter(function (r) {
|
||||
return r._start > s;
|
||||
});
|
||||
var nextRule = nextRules.shift();
|
||||
|
||||
var applyDiff = function (obj, k) {
|
||||
var diff = obj[k]; // Diff is always compared to origin start/end
|
||||
var d = new Date(ev.raw[k]);
|
||||
d.setDate(d.getDate() + diff.d);
|
||||
d.setHours(d.getHours() + diff.h);
|
||||
d.setMinutes(d.getMinutes() + diff.m);
|
||||
ev[k] = +d;
|
||||
};
|
||||
|
||||
sortUpdate(from).forEach(function (d) {
|
||||
if (s < Number(d)) { return; }
|
||||
Object.keys(from[d]).forEach(function (k) {
|
||||
if (k === 'start' || k === 'end') { return void applyDiff(from[d], k); }
|
||||
if (k === "recurrenceRule" && !from[d][k]) { return; }
|
||||
ev[k] = from[d][k];
|
||||
});
|
||||
});
|
||||
Object.keys(one[s] || {}).forEach(function (k) {
|
||||
if (k === 'start' || k === 'end') { return void applyDiff(one[s], k); }
|
||||
if (k === "recurrenceRule" && !one[s][k]) { return; }
|
||||
ev[k] = one[s][k];
|
||||
});
|
||||
if (ev.deleted) {
|
||||
Object.keys(ev).forEach(function (k) {
|
||||
delete ev[k];
|
||||
});
|
||||
}
|
||||
|
||||
if (nextRule && ev.recurrenceRule) {
|
||||
ev.recurrenceRule._next = nextRule._start - 1;
|
||||
}
|
||||
|
||||
if (ev.reminders) {
|
||||
ev.raw.reminders = ev.reminders;
|
||||
}
|
||||
});
|
||||
return events;
|
||||
};
|
||||
|
||||
|
||||
return Rec;
|
||||
});
|
|
@ -1417,7 +1417,7 @@ define([
|
|||
return /HTML/.test(Object.prototype.toString.call(o)) &&
|
||||
typeof(o.tagName) === 'string';
|
||||
};
|
||||
var allowedTags = ['a', 'p', 'hr', 'div'];
|
||||
var allowedTags = ['a', 'li', 'p', 'hr', 'div'];
|
||||
var isValidOption = function (o) {
|
||||
if (typeof o !== "object") { return false; }
|
||||
if (isElement(o)) { return true; }
|
||||
|
@ -1541,6 +1541,7 @@ define([
|
|||
$innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
|
||||
if (config.isSelect && value) {
|
||||
// We use JSON.stringify here to escape quotes
|
||||
if (typeof(value) === "object") { value = JSON.stringify(value); }
|
||||
var $val = $innerblock.find('[data-value='+JSON.stringify(value)+']');
|
||||
setActive($val);
|
||||
try {
|
||||
|
|
|
@ -481,6 +481,18 @@ define([
|
|||
var missed = content.msg.missed;
|
||||
var start = msg.start;
|
||||
var title = Util.fixHTML(msg.title);
|
||||
content.handler = function () {
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var time = Util.find(data, ['content', 'msg', 'content', 'start']);
|
||||
if (priv.app === "calendar" && window.APP && window.APP.moveToDate) {
|
||||
return void window.APP.moveToDate(time);
|
||||
}
|
||||
var url = Hash.hashToHref('', 'calendar');
|
||||
var optsUrl = Hash.getNewPadURL(url, {
|
||||
time: time
|
||||
});
|
||||
common.openURL(optsUrl);
|
||||
};
|
||||
content.getFormatText = function () {
|
||||
var now = +new Date();
|
||||
|
||||
|
|
|
@ -4,12 +4,13 @@ define([
|
|||
'/common/common-constants.js',
|
||||
'/common/common-realtime.js',
|
||||
'/common/outer/cache-store.js',
|
||||
'/calendar/recurrence.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'chainpad-listmap',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) {
|
||||
], function (Util, Hash, Constants, Realtime, Cache, Rec, Messages, nThen, Listmap, Crypto, ChainPad) {
|
||||
var Calendar = {};
|
||||
|
||||
var getStore = function (ctx, id) {
|
||||
|
@ -90,7 +91,29 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var updateEventReminders = function (ctx, reminders, _ev, useLastVisit) {
|
||||
var getRecurring = function (ev) {
|
||||
var mid = new Date();
|
||||
var start = new Date(mid.getFullYear(), mid.getMonth()-1, 15);
|
||||
var end = new Date(mid.getFullYear(), mid.getMonth()+1, 15);
|
||||
var startId = Rec.getMonthId(start);
|
||||
var midId = Rec.getMonthId(mid);
|
||||
var endId = Rec.getMonthId(end);
|
||||
|
||||
var toAdd = Rec.getRecurring([startId, midId, endId], [ev]);
|
||||
|
||||
var all = [ev];
|
||||
Array.prototype.push.apply(all, toAdd);
|
||||
return Rec.applyUpdates(all);
|
||||
};
|
||||
var clearDismissed = function (ctx, uid) {
|
||||
var h = Util.find(ctx, ['store', 'proxy', 'hideReminders']) || {};
|
||||
Object.keys(h).filter(function (id) {
|
||||
return id.indexOf(uid) === 0;
|
||||
}).forEach(function (id) {
|
||||
delete h[id];
|
||||
});
|
||||
};
|
||||
var _updateEventReminders = function (ctx, reminders, _ev, useLastVisit) {
|
||||
var now = +new Date();
|
||||
var ev = Util.clone(_ev);
|
||||
var uid = ev.id;
|
||||
|
@ -101,6 +124,10 @@ define([
|
|||
}
|
||||
reminders[uid] = [];
|
||||
|
||||
if (_ev.deleted) { return; }
|
||||
|
||||
var d = Util.find(ctx, ['store', 'proxy', 'hideReminders', uid]) || []; // dismissed
|
||||
|
||||
var last = ctx.store.data.lastVisit;
|
||||
|
||||
if (ev.isAllDay) {
|
||||
|
@ -119,10 +146,11 @@ define([
|
|||
if (ev.end <= now && !missed) {
|
||||
// No reminder for past events
|
||||
delete reminders[uid];
|
||||
clearDismissed(ctx, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
var send = function () {
|
||||
var send = function (d) {
|
||||
var hide = Util.find(ctx, ['store', 'proxy', 'settings', 'general', 'calendar', 'hideNotif']);
|
||||
if (hide) { return; }
|
||||
var ctime = ev.start <= now ? ev.start : +new Date(); // Correct order for past events
|
||||
|
@ -133,11 +161,18 @@ define([
|
|||
missed: Boolean(missed),
|
||||
content: ev
|
||||
},
|
||||
hash: 'REMINDER|'+uid
|
||||
hash: 'REMINDER|'+uid+'-'+d
|
||||
}, null, function () {
|
||||
});
|
||||
};
|
||||
var sendNotif = function () { ctx.Store.onReadyEvt.reg(send); };
|
||||
var sent = false;
|
||||
var sendNotif = function (delay) {
|
||||
sent = true;
|
||||
|
||||
ctx.Store.onReadyEvt.reg(function () {
|
||||
send(delay);
|
||||
});
|
||||
};
|
||||
|
||||
var notifs = ev.reminders || [];
|
||||
notifs.sort(function (a, b) {
|
||||
|
@ -148,6 +183,10 @@ define([
|
|||
var delay = delayMinutes * 60000;
|
||||
var time = now + delay;
|
||||
|
||||
if (d.some(function (minutes) {
|
||||
return delayMinutes >= minutes;
|
||||
})) { return; }
|
||||
|
||||
// setTimeout only work with 32bit timeout values. If the event is too far away,
|
||||
// ignore this event for now
|
||||
// FIXME: call this function again in xxx days to reload these missing timeout?
|
||||
|
@ -156,18 +195,35 @@ define([
|
|||
// If we're too late to send a notification, send it instantly and ignore
|
||||
// all notifications that were supposed to be sent even earlier
|
||||
if (ev.start <= time) {
|
||||
sendNotif();
|
||||
sendNotif(delayMinutes);
|
||||
return true;
|
||||
}
|
||||
|
||||
// It starts in more than "delay": prepare the notification
|
||||
reminders[uid].push(setTimeout(function () {
|
||||
sendNotif();
|
||||
sendNotif(delayMinutes);
|
||||
}, (ev.start - time)));
|
||||
});
|
||||
|
||||
if (!sent) {
|
||||
// Remone any existing notification from the UI
|
||||
ctx.Store.onReadyEvt.reg(function () {
|
||||
ctx.store.mailbox.hideMessage('reminders', {
|
||||
hash: 'REMINDER|'+uid
|
||||
}, null, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
var updateEventReminders = function (ctx, reminders, ev, useLastVisit) {
|
||||
var all = getRecurring(Util.clone(ev));
|
||||
all.forEach(function (_ev) {
|
||||
_updateEventReminders(ctx, reminders, _ev, useLastVisit);
|
||||
});
|
||||
};
|
||||
var addReminders = function (ctx, id, ev) {
|
||||
var calendar = ctx.calendars[id];
|
||||
if (!ev) { return; } // XXX deleted event remote: delete reminders
|
||||
if (!calendar || !calendar.reminders) { return; }
|
||||
if (calendar.stores.length === 1 && calendar.stores[0] === 0) { return; }
|
||||
|
||||
|
@ -352,10 +408,20 @@ define([
|
|||
c.lm = lm;
|
||||
var proxy = c.proxy = lm.proxy;
|
||||
|
||||
var _updateCalled = false;
|
||||
var _update = function () {
|
||||
if (_updateCalled) { return; }
|
||||
_updateCalled = true;
|
||||
setTimeout(function () {
|
||||
_updateCalled = false;
|
||||
update();
|
||||
});
|
||||
};
|
||||
|
||||
lm.proxy.on('cacheready', function () {
|
||||
if (!proxy.metadata) { return; }
|
||||
c.cacheready = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
if (cb) { cb(null, lm.proxy); }
|
||||
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
||||
}).on('ready', function (info) {
|
||||
|
@ -372,24 +438,39 @@ define([
|
|||
title: data.title
|
||||
};
|
||||
}
|
||||
setTimeout(update);
|
||||
_update();
|
||||
if (cb) { cb(null, lm.proxy); }
|
||||
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
||||
}).on('change', [], function () {
|
||||
if (!c.ready) { return; }
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('change', ['content'], function (o, n, p) {
|
||||
if (p.length === 2 && n && !o) { // New event
|
||||
addReminders(ctx, channel, n);
|
||||
return void addReminders(ctx, channel, n);
|
||||
}
|
||||
if (p.length === 2 && !n && o) { // Deleted event
|
||||
addReminders(ctx, channel, {
|
||||
return void addReminders(ctx, channel, {
|
||||
id: p[1],
|
||||
start: 0
|
||||
});
|
||||
}
|
||||
if (p.length === 3 && n && o && p[2] === 'start') { // Update event start
|
||||
setTimeout(function () {
|
||||
if (p.length >= 3 && ['start','reminders','isAllDay'].includes(p[2])) {
|
||||
// Updated event
|
||||
return void setTimeout(function () {
|
||||
addReminders(ctx, channel, proxy.content[p[1]]);
|
||||
});
|
||||
}
|
||||
if (p.length >= 6 && ['start','reminders','isAllDay'].includes(p[5])) {
|
||||
// Updated recurring event
|
||||
return void setTimeout(function () {
|
||||
addReminders(ctx, channel, proxy.content[p[1]]);
|
||||
});
|
||||
}
|
||||
}).on('remove', ['content'], function (x, p) {
|
||||
_update();
|
||||
if ((p.length >= 3 && p[2] === 'reminders') ||
|
||||
(p.length >= 6 && p[5] === 'reminders')) {
|
||||
return void setTimeout(function () {
|
||||
addReminders(ctx, channel, proxy.content[p[1]]);
|
||||
});
|
||||
}
|
||||
|
@ -400,10 +481,10 @@ define([
|
|||
updateLocalCalendars(ctx, c, md);
|
||||
}).on('disconnect', function () {
|
||||
c.offline = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('reconnect', function () {
|
||||
c.offline = false;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}).on('error', function (info) {
|
||||
if (!info || !info.error) { return; }
|
||||
if (info.error === "EDELETED" ) {
|
||||
|
@ -411,7 +492,7 @@ define([
|
|||
}
|
||||
if (info.error === "ERESTRICTED" ) {
|
||||
c.restricted = true;
|
||||
setTimeout(update);
|
||||
_update();
|
||||
}
|
||||
cb(info);
|
||||
});
|
||||
|
@ -760,8 +841,11 @@ define([
|
|||
var ev = c.proxy.content[data.ev.id];
|
||||
if (!ev) { return void cb({error: "EINVAL"}); }
|
||||
|
||||
data.rawData = data.rawData || {};
|
||||
|
||||
// update the event
|
||||
var changes = data.changes || {};
|
||||
var type = data.type || {};
|
||||
|
||||
var newC;
|
||||
if (changes.calendarId) {
|
||||
|
@ -770,7 +854,122 @@ define([
|
|||
newC.proxy.content = newC.proxy.content || {};
|
||||
}
|
||||
|
||||
var RECUPDATE = {
|
||||
one: {},
|
||||
from: {}
|
||||
};
|
||||
if (['one','from','all'].includes(type.which)) {
|
||||
ev.recUpdate = ev.recUpdate || RECUPDATE;
|
||||
if (!ev.recUpdate.one) { ev.recUpdate.one = {}; }
|
||||
if (!ev.recUpdate.from) { ev.recUpdate.from = {}; }
|
||||
}
|
||||
var update = ev.recUpdate;
|
||||
var alwaysAll = ['calendarId'];
|
||||
var keys = Object.keys(changes).filter(function (s) {
|
||||
// we can only change the calendar or recurrence rule on the origin
|
||||
return !alwaysAll.includes(s);
|
||||
});
|
||||
|
||||
// Delete (future) affected keys
|
||||
var cleanAfter = function (time) {
|
||||
[update.from, update.one].forEach(function (obj) {
|
||||
Object.keys(obj).forEach(function (d) {
|
||||
if (Number(d) < time) { return; }
|
||||
delete obj[d];
|
||||
});
|
||||
});
|
||||
};
|
||||
var cleanKeys = function (obj, when) {
|
||||
Object.keys(obj).forEach(function (d) {
|
||||
if (when && Number(d) < when) { return; }
|
||||
keys.forEach(function (k) {
|
||||
delete obj[d][k];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Update recurrence rule. We may create a new event here
|
||||
var dontSendUpdate = false;
|
||||
if (typeof(changes.recurrenceRule) !== "undefined") {
|
||||
if (['one','from'].includes(type.which) && !data.rawData.isOrigin) {
|
||||
cleanAfter(type.when);
|
||||
} else {
|
||||
update = ev.recUpdate = RECUPDATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.which === "one") {
|
||||
update.one[type.when] = update.one[type.when] || {};
|
||||
// Nothing to delete
|
||||
} else if (type.which === "from") {
|
||||
update.from[type.when] = update.from[type.when] || {};
|
||||
// Delete all "single/from" updates (affected keys only) after this "from" date
|
||||
cleanKeys(update.from, type.when);
|
||||
cleanKeys(update.one, type.when);
|
||||
} else if (type.which === "all") {
|
||||
// Delete all "single/from" updates (affected keys only) after
|
||||
cleanKeys(update.from);
|
||||
cleanKeys(update.one);
|
||||
}
|
||||
|
||||
if (changes.start && (!type.which || type.which === "all")) {
|
||||
var diff = changes.start - ev.start;
|
||||
var newOne = {};
|
||||
var newFrom = {};
|
||||
Object.keys(update.one).forEach(function (time) {
|
||||
newOne[Number(time)+diff] = update.one[time];
|
||||
});
|
||||
Object.keys(update.from).forEach(function (time) {
|
||||
newFrom[Number(time)+diff] = update.from[time];
|
||||
});
|
||||
update.one = newOne;
|
||||
update.from = newFrom;
|
||||
}
|
||||
|
||||
|
||||
// Clear the "dismissed" reminders when the user is updating reminders
|
||||
var h = Util.find(ctx, ['store', 'proxy', 'hideReminders']) || {};
|
||||
if (changes.reminders) {
|
||||
if (type.which === 'one') {
|
||||
if (!type.when || type.when === ev.start) { delete h[data.ev.id]; }
|
||||
else { delete h[data.ev.id +'|'+ type.when]; }
|
||||
} else if (type.which === "from") {
|
||||
Object.keys(h).filter(function (id) {
|
||||
return id.indexOf(data.ev.id) === 0;
|
||||
}).forEach(function (id) {
|
||||
var time = Number(id.split('|')[1]);
|
||||
if (!time) { return; }
|
||||
if (time < type.when) { return; }
|
||||
delete h[id];
|
||||
});
|
||||
} else {
|
||||
Object.keys(h).filter(function (id) {
|
||||
return id.indexOf(data.ev.id) === 0;
|
||||
}).forEach(function (id) {
|
||||
delete h[id];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the changes
|
||||
Object.keys(changes).forEach(function (key) {
|
||||
if (!alwaysAll.includes(key) && type.which === "one") {
|
||||
if (key === "recurrenceRule") {
|
||||
if (data.rawData && data.rawData.isOrigin) {
|
||||
return (ev[key] = changes[key]);
|
||||
}
|
||||
// Always "from", never "one" for recurrence rules
|
||||
update.from[type.when] = update.from[type.when] || {};
|
||||
return (update.from[type.when][key] = changes[key]);
|
||||
}
|
||||
update.one[type.when][key] = changes[key];
|
||||
return;
|
||||
}
|
||||
if (!alwaysAll.includes(key) && type.which === "from") {
|
||||
update.from[type.when][key] = changes[key];
|
||||
return;
|
||||
}
|
||||
ev[key] = changes[key];
|
||||
});
|
||||
|
||||
|
@ -790,6 +989,7 @@ define([
|
|||
delete c.proxy.content[data.ev.id];
|
||||
}
|
||||
|
||||
|
||||
nThen(function (waitFor) {
|
||||
Realtime.whenRealtimeSyncs(c.lm.realtime, waitFor());
|
||||
if (newC) { Realtime.whenRealtimeSyncs(newC.lm.realtime, waitFor()); }
|
||||
|
@ -806,8 +1006,8 @@ define([
|
|||
addReminders(ctx, id, ev);
|
||||
}
|
||||
|
||||
sendUpdate(ctx, c);
|
||||
if (newC) { sendUpdate(ctx, newC); }
|
||||
if (!dontSendUpdate || newC) { sendUpdate(ctx, c); }
|
||||
if (newC && !dontSendUpdate) { sendUpdate(ctx, newC); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
@ -816,7 +1016,22 @@ define([
|
|||
var c = ctx.calendars[id];
|
||||
if (!c) { return void cb({error: "ENOENT"}); }
|
||||
c.proxy.content = c.proxy.content || {};
|
||||
delete c.proxy.content[data.id];
|
||||
var evId = data.id.split('|')[0];
|
||||
if (data.id === evId) {
|
||||
delete c.proxy.content[data.id];
|
||||
} else {
|
||||
var ev = c.proxy.content[evId];
|
||||
var s = data.raw && data.raw.start;
|
||||
if (s) {
|
||||
ev.recUpdate = ev.recUpdate || {
|
||||
one: {},
|
||||
from: {}
|
||||
};
|
||||
ev.recUpdate.one[s] = {
|
||||
deleted: true
|
||||
};
|
||||
}
|
||||
}
|
||||
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
|
||||
addReminders(ctx, id, {
|
||||
id: data.id,
|
||||
|
@ -867,6 +1082,20 @@ define([
|
|||
openChannels(ctx);
|
||||
}));
|
||||
|
||||
ctx.store.proxy.on('change', ['hideReminders'], function (o,n,p) {
|
||||
var uid = p[1].split('|')[0];
|
||||
Object.keys(ctx.calendars).some(function (calId) {
|
||||
var c = ctx.calendars[calId];
|
||||
if (!c || !c.proxy || !c.proxy.content) { return; }
|
||||
if (c.proxy.content[uid]) {
|
||||
setTimeout(function () {
|
||||
addReminders(ctx, calId, c.proxy.content[uid]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
calendar.closeTeam = function (teamId) {
|
||||
Object.keys(ctx.calendars).forEach(function (id) {
|
||||
var ctxCal = ctx.calendars[id];
|
||||
|
|
|
@ -177,6 +177,15 @@ proxy.mailboxes = {
|
|||
hideMessage(ctx, type, hash, ctx.clients.filter(function (clientId) {
|
||||
return clientId !== cId;
|
||||
}));
|
||||
|
||||
var uid = hash.slice(9).split('-')[0];
|
||||
var d = Util.find(ctx, ['store', 'proxy', 'hideReminders', uid]);
|
||||
if (!d) {
|
||||
var h = ctx.store.proxy.hideReminders = ctx.store.proxy.hideReminders || {};
|
||||
d = h[uid] = h[uid] || [];
|
||||
}
|
||||
var delay = hash.split('-')[1];
|
||||
if (delay && !d.includes(delay)) { d.push(Number(delay)); }
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -590,6 +599,9 @@ proxy.mailboxes = {
|
|||
});
|
||||
};
|
||||
|
||||
mailbox.hideMessage = function (type, msg) {
|
||||
hideMessage(ctx, type, msg.hash, ctx.clients);
|
||||
};
|
||||
mailbox.showMessage = function (type, msg, cId, cb) {
|
||||
if (type === "reminders" && msg) {
|
||||
ctx.boxes.reminders.content[msg.hash] = msg.msg;
|
||||
|
|
|
@ -80,7 +80,7 @@ define([
|
|||
try {
|
||||
var val = JSON.parse(states[idx].getContent().doc);
|
||||
var md = config.extractMetadata(val);
|
||||
var users = Object.keys(md.users).sort();
|
||||
var users = Object.keys(md.users || {}).sort();
|
||||
return users.join();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
|
@ -13,6 +13,7 @@ define([
|
|||
Mailbox.create = function (Common) {
|
||||
var mailbox = Common.mailbox;
|
||||
var sframeChan = Common.getSframeChannel();
|
||||
var priv = Common.getMetadataMgr().getPrivateData();
|
||||
|
||||
var execCommand = function (cmd, data, cb) {
|
||||
sframeChan.query('Q_MAILBOX_COMMAND', {
|
||||
|
@ -67,6 +68,14 @@ define([
|
|||
}
|
||||
} else if (data.type === 'reminders') {
|
||||
avatar = h('i.fa.fa-calendar.cp-broadcast.preview');
|
||||
if (priv.app !== 'calendar') { avatar.classList.add('cp-reminder'); }
|
||||
$(avatar).click(function (e) {
|
||||
e.stopPropagation();
|
||||
if (data.content && data.content.handler) {
|
||||
return void data.content.handler();
|
||||
}
|
||||
Common.openURL(Hash.hashToHref('', 'calendar'));
|
||||
});
|
||||
} else if (userData && typeof(userData) === "object" && userData.profile) {
|
||||
avatar = h('span.cp-avatar');
|
||||
Common.displayAvatar($(avatar), userData.avatar, userData.displayName || userData.name);
|
||||
|
@ -120,7 +129,8 @@ define([
|
|||
|
||||
onViewedHandlers.push(function (data) {
|
||||
var hash = data.hash.replace(/"/g, '\\\"');
|
||||
var $notif = $('.cp-notification[data-hash="'+hash+'"]:not(.cp-app-notification-archived)');
|
||||
if (/^REMINDER\|/.test(hash)) { hash = hash.split('-')[0]; }
|
||||
var $notif = $('.cp-notification[data-hash^="'+hash+'"]:not(.cp-app-notification-archived)');
|
||||
if ($notif.length) {
|
||||
$notif.remove();
|
||||
}
|
||||
|
|
|
@ -43,7 +43,14 @@ define([
|
|||
};
|
||||
var getEndDate = function () {
|
||||
setTimeout(function () { $(endPickr.calendarContainer).remove(); });
|
||||
return endPickr.parseDate(e.value);
|
||||
var d = endPickr.parseDate(e.value);
|
||||
|
||||
if (endPickr.config.dateFormat === "Y-m-d") { // All day event
|
||||
// Tui-calendar will remove 1s (1000ms) to the date for an unknown reason...
|
||||
d.setMilliseconds(1000);
|
||||
}
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in New Issue