diff --git a/www/common/notifications.js b/www/common/notifications.js
index d364bad6c..018093c2a 100644
--- a/www/common/notifications.js
+++ b/www/common/notifications.js
@@ -459,6 +459,25 @@ define([
}
};
+ Messages.reminder_minutes = "{0} will start in {1} minutes!"; // XXX
+ Messages.reminder_hour = "{0} will start in 1 hour!"; // XXX
+ handlers['REMINDER'] = function (common, data) {
+ var content = data.content;
+ var msg = content.msg.content;
+ var now = +new Date();
+ var start = msg.start;
+ content.getFormatText = function () {
+ if ((start - now) > 600000) {
+ return Messages._getKey('reminder_hour', [Util.fixHTML(msg.title)]);
+ }
+ var minutes = Math.round((start - now) / 60000);
+ return Messages._getKey('reminder_minutes', [Util.fixHTML(msg.title), minutes]);
+ };
+ if (!content.archived) {
+ content.dismissHandler = defaultDismiss(common, data);
+ }
+ };
+
// NOTE: don't forget to fixHTML everything returned by "getFormatText"
return {
diff --git a/www/common/outer/calendar.js b/www/common/outer/calendar.js
index e02ad1412..b3d4c707a 100644
--- a/www/common/outer/calendar.js
+++ b/www/common/outer/calendar.js
@@ -12,60 +12,6 @@ define([
], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) {
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) {
if (!id || id === 1) {
return ctx.store;
@@ -109,6 +55,15 @@ ctx.calendars[channel] = {
}, 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 ctxCal = ctx.calendars[id];
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 (!ctxCal.stores.length) {
ctxCal.lm.stop();
+ clearReminders(ctx, id);
delete ctx.calendars[id];
}
};
@@ -133,12 +89,83 @@ ctx.calendars[channel] = {
if (cal.title !== data.title) { cal.title = data.title; }
});
};
+
+ var updateEventReminders = function (ctx, reminders, ev) {
+ var now = +new Date();
+ var time10 = now + (600 * 1000); // 10 minutes from now
+ var time60 = now + (3600 * 1000); // 1 hour from now
+ var uid = ev.id;
+
+ // Clear reminders for this event
+ if (Array.isArray(reminders[uid])) {
+ reminders[uid].forEach(function (to) { clearTimeout(to); });
+ }
+ reminders[uid] = [];
+
+ // No reminder for past events
+ if (ev.start <= now) {
+ delete reminders[uid];
+ return;
+ }
+
+ var send = function () {
+ console.error(ev);
+ ctx.store.mailbox.showMessage('reminders', {
+ msg: {
+ ctime: +new Date(),
+ type: "REMINDER",
+ content: ev
+ },
+ hash: 'REMINDER|'+uid
+ }, null, function () {
+ });
+ };
+ var sendNotif = function () { ctx.Store.onReadyEvt.reg(send); };
+
+ if (ev.start <= time10) {
+ sendNotif();
+ return;
+ }
+
+ // It starts in more than 10 minutes: prepare the 10 minutes notification
+ reminders[uid].push(setTimeout(function () {
+ sendNotif();
+ }, (ev.start - time10)));
+
+ if (ev.start <= time60) { return; }
+
+ // It starts in more than 1 hour: prepare the 1 hour notification
+ reminders[uid].push(setTimeout(function () {
+ sendNotif();
+ }, (ev.start - time60)));
+ };
+ 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) {
+ 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]);
+ });
+ };
var openChannel = function (ctx, cfg, _cb) {
var cb = Util.once(Util.mkAsync(_cb || function () {}));
var teamId = cfg.storeId;
var data = cfg.data;
var channel = data.channel;
- console.error(cfg);
if (!channel) { return; }
var c = ctx.calendars[channel];
@@ -193,6 +220,7 @@ ctx.calendars[channel] = {
readOnly: !data.href,
stores: [teamId],
roStores: data.href ? [] : [teamId],
+ reminders: {},
hashes: {}
};
@@ -232,6 +260,7 @@ ctx.calendars[channel] = {
if (c.lm) { c.lm.stop(); }
c.stores = [];
sendUpdate(ctx, c);
+ clearReminders(ctx, channel);
delete ctx.calendars[channel];
};
@@ -290,6 +319,7 @@ ctx.calendars[channel] = {
c.cacheready = true;
setTimeout(update);
if (cb) { cb(null, lm.proxy); }
+ addInitialReminders(ctx, channel);
}).on('ready', function (info) {
var md = info.metadata;
c.owners = md.owners || [];
@@ -306,9 +336,25 @@ ctx.calendars[channel] = {
}
setTimeout(update);
if (cb) { cb(null, lm.proxy); }
+ addInitialReminders(ctx, channel);
}).on('change', [], function () {
if (!c.ready) { return; }
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 () {
// if title or color have changed, update our local values
var md = proxy.metadata;
@@ -627,6 +673,7 @@ ctx.calendars[channel] = {
c.proxy.content = c.proxy.content || {};
c.proxy.content[data.id] = data;
Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
+ addReminders(ctx, id, data);
sendUpdate(ctx, c);
cb();
});
@@ -655,6 +702,7 @@ ctx.calendars[channel] = {
ev[key] = changes[key];
});
+
// Move to a different calendar?
if (changes.calendarId && newC) {
newC.proxy.content[data.ev.id] = Util.clone(ev);
@@ -664,7 +712,10 @@ ctx.calendars[channel] = {
nThen(function (waitFor) {
Realtime.whenRealtimeSyncs(c.lm.realtime, waitFor());
if (newC) { Realtime.whenRealtimeSyncs(newC.lm.realtime, waitFor()); }
- }).nThen(cb);
+ }).nThen(function () {
+ if (changes.start) { addReminders(ctx, id, ev); }
+ cb();
+ });
};
var deleteEvent = function (ctx, data, cId, cb) {
var id = data.calendarId;
@@ -672,7 +723,12 @@ ctx.calendars[channel] = {
if (!c) { return void cb({error: "ENOENT"}); }
c.proxy.content = c.proxy.content || {};
delete c.proxy.content[data.id];
- Realtime.whenRealtimeSyncs(c.lm.realtime, cb);
+ Realtime.whenRealtimeSyncs(c.lm.realtime, function () {
+ addReminders(ctx, id, {
+ id: data.id,
+ start: 0
+ });
+ });
};
var removeClient = function (ctx, cId) {
@@ -693,7 +749,7 @@ ctx.calendars[channel] = {
emit: emit,
onReady: Util.mkEvent(true),
calendars: {},
- clients: [],
+ clients: []
};
initializeCalendars(ctx, waitFor(function (err) {
diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js
index 6605a55ee..eea275093 100644
--- a/www/common/outer/mailbox.js
+++ b/www/common/outer/mailbox.js
@@ -174,6 +174,18 @@ proxy.mailboxes = {
var dismiss = function (ctx, data, cId, cb) {
var type = data.type;
var hash = data.hash;
+
+ // Reminder messages don't persist
+ if (/^REMINDER\|/.test(hash)) {
+ cb();
+ delete ctx.boxes.reminders.content[hash];
+ hideMessage(ctx, type, hash, ctx.clients.filter(function (clientId) {
+ return clientId !== cId;
+ }));
+ return;
+ };
+
+
var box = ctx.boxes[type];
if (!box) { return void cb({error: 'NOT_LOADED'}); }
var m = box.data || {};
@@ -548,6 +560,10 @@ proxy.mailboxes = {
initializeHistory(ctx);
}
+ ctx.boxes.reminders = {
+ content: {}
+ };
+
Object.keys(mailboxes).forEach(function (key) {
if (TYPES.indexOf(key) === -1) { return; }
var m = mailboxes[key];
@@ -588,6 +604,15 @@ proxy.mailboxes = {
});
};
+ mailbox.showMessage = function (type, msg, cId, cb) {
+ if (type === "reminders" && msg) {
+ ctx.boxes.reminders.content[msg.hash] = msg.msg;
+ // Hide existing messages for this event
+ hideMessage(ctx, type, msg.hash, ctx.clients);
+ }
+ showMessage(ctx, type, msg, cId, cb);
+ };
+
mailbox.open = function (key, m, cb, team, opts) {
if (TYPES.indexOf(key) === -1 && !team) { return; }
openChannel(ctx, key, m, cb, opts);
diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js
index 861ff8d53..215a4866d 100644
--- a/www/common/sframe-common-mailbox.js
+++ b/www/common/sframe-common-mailbox.js
@@ -65,6 +65,8 @@ define([
if (/^LOCAL\|/.test(data.content.hash)) {
$(avatar).addClass('preview');
}
+ } else if (data.type === 'reminders') {
+ avatar = h('i.fa.fa-calendar.cp-broadcast.preview');
} else if (userData && typeof(userData) === "object" && userData.profile) {
avatar = h('span.cp-avatar');
Common.displayAvatar($(avatar), userData.avatar, userData.displayName || userData.name);
@@ -117,7 +119,7 @@ define([
// Call the onMessage handlers
var isNotification = function (type) {
- return type === "notifications" || /^team-/.test(type) || type === "broadcast";
+ return type === "notifications" || /^team-/.test(type) || type === "broadcast" || type === "reminders";
};
var pushMessage = function (data, handler) {
var todo = function (f) {
diff --git a/www/common/toolbar.js b/www/common/toolbar.js
index e2ad32ea6..583a9168a 100644
--- a/www/common/toolbar.js
+++ b/www/common/toolbar.js
@@ -1148,7 +1148,7 @@ MessengerUI, Messages) {
$button.addClass('fa-bell');
};
- Common.mailbox.subscribe(['notifications', 'team', 'broadcast'], {
+ Common.mailbox.subscribe(['notifications', 'team', 'broadcast', 'reminders'], {
onMessage: function (data, el) {
if (el) {
$(div).prepend(el);