|
|
@ -12,60 +12,6 @@ define([
|
|
|
|
], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) {
|
|
|
|
], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) {
|
|
|
|
var Calendar = {};
|
|
|
|
var Calendar = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* TODO
|
|
|
|
|
|
|
|
* Calendar
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
href,
|
|
|
|
|
|
|
|
roHref,
|
|
|
|
|
|
|
|
channel, (pinning)
|
|
|
|
|
|
|
|
title, (when created from the UI, own calendar has no title)
|
|
|
|
|
|
|
|
color
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Own drive
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
calendars: {
|
|
|
|
|
|
|
|
uid: calendar,
|
|
|
|
|
|
|
|
uid: calendar
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Team drive
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
calendars: {
|
|
|
|
|
|
|
|
uid: calendar,
|
|
|
|
|
|
|
|
uid: calendar
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Calendars are listmap
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
content: {},
|
|
|
|
|
|
|
|
metadata: {
|
|
|
|
|
|
|
|
title: "pewpewpew"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.calendars[channel] = {
|
|
|
|
|
|
|
|
lm: lm,
|
|
|
|
|
|
|
|
proxy: lm.proxy?
|
|
|
|
|
|
|
|
stores: [teamId, teamId, 1]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* calendar app can subscribe to this module
|
|
|
|
|
|
|
|
* when a listmap changes, push an update for this calendar to subscribed tabs
|
|
|
|
|
|
|
|
* Ability to open a calendar not stored in the stores but from its URL directly
|
|
|
|
|
|
|
|
* No "userlist" visible in the UI
|
|
|
|
|
|
|
|
* No framework
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var getStore = function (ctx, id) {
|
|
|
|
var getStore = function (ctx, id) {
|
|
|
|
if (!id || id === 1) {
|
|
|
|
if (!id || id === 1) {
|
|
|
|
return ctx.store;
|
|
|
|
return ctx.store;
|
|
|
@ -109,6 +55,15 @@ ctx.calendars[channel] = {
|
|
|
|
}, ctx.clients);
|
|
|
|
}, ctx.clients);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var clearReminders = function (ctx, id) {
|
|
|
|
|
|
|
|
var calendar = ctx.calendars[id];
|
|
|
|
|
|
|
|
if (!calendar || !calendar.reminders) { return; }
|
|
|
|
|
|
|
|
// Clear existing reminders
|
|
|
|
|
|
|
|
Object.keys(calendar.reminders).forEach(function (uid) {
|
|
|
|
|
|
|
|
if (!Array.isArray(calendar.reminders[uid])) { return; }
|
|
|
|
|
|
|
|
calendar.reminders[uid].forEach(function (to) { clearTimeout(to); });
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
var closeCalendar = function (ctx, id) {
|
|
|
|
var closeCalendar = function (ctx, id) {
|
|
|
|
var ctxCal = ctx.calendars[id];
|
|
|
|
var ctxCal = ctx.calendars[id];
|
|
|
|
if (!ctxCal) { return; }
|
|
|
|
if (!ctxCal) { return; }
|
|
|
@ -116,6 +71,7 @@ ctx.calendars[channel] = {
|
|
|
|
// If the calendar doesn't exist in any other team, stop it and delete it from ctx
|
|
|
|
// If the calendar doesn't exist in any other team, stop it and delete it from ctx
|
|
|
|
if (!ctxCal.stores.length) {
|
|
|
|
if (!ctxCal.stores.length) {
|
|
|
|
ctxCal.lm.stop();
|
|
|
|
ctxCal.lm.stop();
|
|
|
|
|
|
|
|
clearReminders(ctx, id);
|
|
|
|
delete ctx.calendars[id];
|
|
|
|
delete ctx.calendars[id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
@ -133,6 +89,105 @@ ctx.calendars[channel] = {
|
|
|
|
if (cal.title !== data.title) { cal.title = data.title; }
|
|
|
|
if (cal.title !== data.title) { cal.title = data.title; }
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var updateEventReminders = function (ctx, reminders, _ev, useLastVisit) {
|
|
|
|
|
|
|
|
var now = +new Date();
|
|
|
|
|
|
|
|
var ev = Util.clone(_ev);
|
|
|
|
|
|
|
|
var uid = ev.id;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Clear reminders for this event
|
|
|
|
|
|
|
|
if (Array.isArray(reminders[uid])) {
|
|
|
|
|
|
|
|
reminders[uid].forEach(function (to) { clearTimeout(to); });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
reminders[uid] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var last = ctx.store.data.lastVisit;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (ev.isAllDay) {
|
|
|
|
|
|
|
|
if (ev.startDay) { ev.start = +new Date(ev.startDay); }
|
|
|
|
|
|
|
|
if (ev.endDay) {
|
|
|
|
|
|
|
|
var endDate = new Date(ev.endDay);
|
|
|
|
|
|
|
|
endDate.setHours(23);
|
|
|
|
|
|
|
|
endDate.setMinutes(59);
|
|
|
|
|
|
|
|
endDate.setSeconds(59);
|
|
|
|
|
|
|
|
ev.end = +endDate;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// XXX add a limit to make sure we don't go too far in the past?
|
|
|
|
|
|
|
|
var missed = useLastVisit && ev.start > last && ev.end <= now;
|
|
|
|
|
|
|
|
if (ev.end <= now && !missed) {
|
|
|
|
|
|
|
|
// No reminder for past events
|
|
|
|
|
|
|
|
delete reminders[uid];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var send = function () {
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
ctx.store.mailbox.showMessage('reminders', {
|
|
|
|
|
|
|
|
msg: {
|
|
|
|
|
|
|
|
ctime: ctime,
|
|
|
|
|
|
|
|
type: "REMINDER",
|
|
|
|
|
|
|
|
missed: Boolean(missed),
|
|
|
|
|
|
|
|
content: ev
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
hash: 'REMINDER|'+uid
|
|
|
|
|
|
|
|
}, null, function () {
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
var sendNotif = function () { ctx.Store.onReadyEvt.reg(send); };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var notifs = ev.reminders || [];
|
|
|
|
|
|
|
|
notifs.sort(function (a, b) {
|
|
|
|
|
|
|
|
return a - b;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
notifs.some(function (delayMinutes) {
|
|
|
|
|
|
|
|
var delay = delayMinutes * 60000;
|
|
|
|
|
|
|
|
var time = now + delay;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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?
|
|
|
|
|
|
|
|
if (ev.start - time >= 2147483647) { return true; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// It starts in more than "delay": prepare the notification
|
|
|
|
|
|
|
|
reminders[uid].push(setTimeout(function () {
|
|
|
|
|
|
|
|
sendNotif();
|
|
|
|
|
|
|
|
}, (ev.start - time)));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
var addReminders = function (ctx, id, ev) {
|
|
|
|
|
|
|
|
var calendar = ctx.calendars[id];
|
|
|
|
|
|
|
|
if (!calendar || !calendar.reminders) { return; }
|
|
|
|
|
|
|
|
if (calendar.stores.length === 1 && calendar.stores[0] === 0) { return; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateEventReminders(ctx, calendar.reminders, ev);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
var addInitialReminders = function (ctx, id, useLastVisit) {
|
|
|
|
|
|
|
|
var calendar = ctx.calendars[id];
|
|
|
|
|
|
|
|
if (!calendar || !calendar.reminders) { return; }
|
|
|
|
|
|
|
|
if (Object.keys(calendar.reminders).length) { return; } // Already initialized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// No reminders for calendars not stored
|
|
|
|
|
|
|
|
if (calendar.stores.length === 1 && calendar.stores[0] === 0) { return; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Re-add all reminders
|
|
|
|
|
|
|
|
var content = Util.find(calendar, ['proxy', 'content']);
|
|
|
|
|
|
|
|
if (!content) { return; }
|
|
|
|
|
|
|
|
Object.keys(content).forEach(function (uid) {
|
|
|
|
|
|
|
|
updateEventReminders(ctx, calendar.reminders, content[uid], useLastVisit);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
var openChannel = function (ctx, cfg, _cb) {
|
|
|
|
var openChannel = function (ctx, cfg, _cb) {
|
|
|
|
var cb = Util.once(Util.mkAsync(_cb || function () {}));
|
|
|
|
var cb = Util.once(Util.mkAsync(_cb || function () {}));
|
|
|
|
var teamId = cfg.storeId;
|
|
|
|
var teamId = cfg.storeId;
|
|
|
@ -192,6 +247,7 @@ ctx.calendars[channel] = {
|
|
|
|
readOnly: !data.href,
|
|
|
|
readOnly: !data.href,
|
|
|
|
stores: [teamId],
|
|
|
|
stores: [teamId],
|
|
|
|
roStores: data.href ? [] : [teamId],
|
|
|
|
roStores: data.href ? [] : [teamId],
|
|
|
|
|
|
|
|
reminders: {},
|
|
|
|
hashes: {}
|
|
|
|
hashes: {}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
@ -231,6 +287,7 @@ ctx.calendars[channel] = {
|
|
|
|
if (c.lm) { c.lm.stop(); }
|
|
|
|
if (c.lm) { c.lm.stop(); }
|
|
|
|
c.stores = [];
|
|
|
|
c.stores = [];
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
|
|
|
|
clearReminders(ctx, channel);
|
|
|
|
delete ctx.calendars[channel];
|
|
|
|
delete ctx.calendars[channel];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
@ -289,6 +346,7 @@ ctx.calendars[channel] = {
|
|
|
|
c.cacheready = true;
|
|
|
|
c.cacheready = true;
|
|
|
|
setTimeout(update);
|
|
|
|
setTimeout(update);
|
|
|
|
if (cb) { cb(null, lm.proxy); }
|
|
|
|
if (cb) { cb(null, lm.proxy); }
|
|
|
|
|
|
|
|
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
|
|
|
}).on('ready', function (info) {
|
|
|
|
}).on('ready', function (info) {
|
|
|
|
var md = info.metadata;
|
|
|
|
var md = info.metadata;
|
|
|
|
c.owners = md.owners || [];
|
|
|
|
c.owners = md.owners || [];
|
|
|
@ -305,9 +363,25 @@ ctx.calendars[channel] = {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setTimeout(update);
|
|
|
|
setTimeout(update);
|
|
|
|
if (cb) { cb(null, lm.proxy); }
|
|
|
|
if (cb) { cb(null, lm.proxy); }
|
|
|
|
|
|
|
|
addInitialReminders(ctx, channel, cfg.lastVisitNotif);
|
|
|
|
}).on('change', [], function () {
|
|
|
|
}).on('change', [], function () {
|
|
|
|
if (!c.ready) { return; }
|
|
|
|
if (!c.ready) { return; }
|
|
|
|
setTimeout(update);
|
|
|
|
setTimeout(update);
|
|
|
|
|
|
|
|
}).on('change', ['content'], function (o, n, p) {
|
|
|
|
|
|
|
|
if (p.length === 2 && n && !o) { // New event
|
|
|
|
|
|
|
|
addReminders(ctx, channel, n);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p.length === 2 && !n && o) { // Deleted event
|
|
|
|
|
|
|
|
addReminders(ctx, channel, {
|
|
|
|
|
|
|
|
id: p[1],
|
|
|
|
|
|
|
|
start: 0
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p.length === 3 && n && o && p[2] === 'start') { // Update event start
|
|
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
|
|
|
addReminders(ctx, channel, proxy.content[p[1]]);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}).on('change', ['metadata'], function () {
|
|
|
|
}).on('change', ['metadata'], function () {
|
|
|
|
// if title or color have changed, update our local values
|
|
|
|
// if title or color have changed, update our local values
|
|
|
|
var md = proxy.metadata;
|
|
|
|
var md = proxy.metadata;
|
|
|
@ -402,6 +476,7 @@ ctx.calendars[channel] = {
|
|
|
|
decryptTeamCalendarHref(store, cal);
|
|
|
|
decryptTeamCalendarHref(store, cal);
|
|
|
|
openChannel(ctx, {
|
|
|
|
openChannel(ctx, {
|
|
|
|
storeId: storeId,
|
|
|
|
storeId: storeId,
|
|
|
|
|
|
|
|
lastVisitNotif: true,
|
|
|
|
data: cal
|
|
|
|
data: cal
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -434,6 +509,23 @@ ctx.calendars[channel] = {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var importICSCalendar = function (ctx, data, cId, cb) {
|
|
|
|
|
|
|
|
var id = data.id;
|
|
|
|
|
|
|
|
var c = ctx.calendars[id];
|
|
|
|
|
|
|
|
if (!c || !c.proxy) { return void cb({error: "ENOENT"}); }
|
|
|
|
|
|
|
|
var json = data.json;
|
|
|
|
|
|
|
|
c.proxy.content = c.proxy.content || {};
|
|
|
|
|
|
|
|
Object.keys(json).forEach(function (uid) {
|
|
|
|
|
|
|
|
c.proxy.content[uid] = json[uid];
|
|
|
|
|
|
|
|
addReminders(ctx, id, json[uid]);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
|
|
|
|
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
|
|
|
|
cb();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var openCalendar = function (ctx, data, cId, cb) {
|
|
|
|
var openCalendar = function (ctx, data, cId, cb) {
|
|
|
|
var secret = Hash.getSecrets('calendar', data.hash, data.password);
|
|
|
|
var secret = Hash.getSecrets('calendar', data.hash, data.password);
|
|
|
|
var hash = Hash.getEditHashFromKeys(secret);
|
|
|
|
var hash = Hash.getEditHashFromKeys(secret);
|
|
|
@ -638,6 +730,7 @@ ctx.calendars[channel] = {
|
|
|
|
c.proxy.content[data.id] = data;
|
|
|
|
c.proxy.content[data.id] = data;
|
|
|
|
|
|
|
|
|
|
|
|
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
|
|
|
|
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
|
|
|
|
|
|
|
|
addReminders(ctx, id, data);
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
cb();
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -686,6 +779,18 @@ ctx.calendars[channel] = {
|
|
|
|
Realtime.whenRealtimeSyncs(c.lm.realtime, waitFor());
|
|
|
|
Realtime.whenRealtimeSyncs(c.lm.realtime, waitFor());
|
|
|
|
if (newC) { Realtime.whenRealtimeSyncs(newC.lm.realtime, waitFor()); }
|
|
|
|
if (newC) { Realtime.whenRealtimeSyncs(newC.lm.realtime, waitFor()); }
|
|
|
|
}).nThen(function () {
|
|
|
|
}).nThen(function () {
|
|
|
|
|
|
|
|
if (newC) {
|
|
|
|
|
|
|
|
// Move reminders to the new calendar
|
|
|
|
|
|
|
|
addReminders(ctx, id, {
|
|
|
|
|
|
|
|
id: ev.id,
|
|
|
|
|
|
|
|
start: 0
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
addReminders(ctx, ev.calendarId, ev);
|
|
|
|
|
|
|
|
} else if (changes.start || changes.reminders || changes.isAllDay) {
|
|
|
|
|
|
|
|
// Update reminders
|
|
|
|
|
|
|
|
addReminders(ctx, id, ev);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
if (newC) { sendUpdate(ctx, newC); }
|
|
|
|
if (newC) { sendUpdate(ctx, newC); }
|
|
|
|
cb();
|
|
|
|
cb();
|
|
|
@ -698,6 +803,10 @@ ctx.calendars[channel] = {
|
|
|
|
c.proxy.content = c.proxy.content || {};
|
|
|
|
c.proxy.content = c.proxy.content || {};
|
|
|
|
delete c.proxy.content[data.id];
|
|
|
|
delete c.proxy.content[data.id];
|
|
|
|
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
|
|
|
|
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
|
|
|
|
|
|
|
|
addReminders(ctx, id, {
|
|
|
|
|
|
|
|
id: data.id,
|
|
|
|
|
|
|
|
start: 0
|
|
|
|
|
|
|
|
});
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
sendUpdate(ctx, c);
|
|
|
|
cb();
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -721,7 +830,7 @@ ctx.calendars[channel] = {
|
|
|
|
emit: emit,
|
|
|
|
emit: emit,
|
|
|
|
onReady: Util.mkEvent(true),
|
|
|
|
onReady: Util.mkEvent(true),
|
|
|
|
calendars: {},
|
|
|
|
calendars: {},
|
|
|
|
clients: [],
|
|
|
|
clients: []
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
initializeCalendars(ctx, waitFor(function (err) {
|
|
|
|
initializeCalendars(ctx, waitFor(function (err) {
|
|
|
@ -785,6 +894,10 @@ ctx.calendars[channel] = {
|
|
|
|
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
|
|
|
|
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
|
|
|
|
return void importCalendar(ctx, data, clientId, cb);
|
|
|
|
return void importCalendar(ctx, data, clientId, cb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd === 'IMPORT_ICS') {
|
|
|
|
|
|
|
|
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
|
|
|
|
|
|
|
|
return void importICSCalendar(ctx, data, clientId, cb);
|
|
|
|
|
|
|
|
}
|
|
|
|
if (cmd === 'ADD') {
|
|
|
|
if (cmd === 'ADD') {
|
|
|
|
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
|
|
|
|
if (ctx.store.offline) { return void cb({error: 'OFFLINE'}); }
|
|
|
|
return void addCalendar(ctx, data, clientId, cb);
|
|
|
|
return void addCalendar(ctx, data, clientId, cb);
|
|
|
|