2012-11-23 23:53:38 +01:00
|
|
|
var notifications = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2012-11-26 23:57:31 +01:00
|
|
|
var notice_memory = {};
|
2012-11-23 23:53:38 +01:00
|
|
|
var window_has_focus = true;
|
2013-01-08 16:54:44 +01:00
|
|
|
var asked_permission_already = false;
|
2013-01-09 23:49:54 +01:00
|
|
|
var names;
|
2013-05-03 21:36:38 +02:00
|
|
|
var supports_sound;
|
2012-11-23 23:53:38 +01:00
|
|
|
|
2012-11-26 23:57:31 +01:00
|
|
|
function browser_desktop_notifications_on () {
|
|
|
|
return (window.webkitNotifications &&
|
2013-01-21 21:50:33 +01:00
|
|
|
// Firefox on Ubuntu claims to do webkitNotifications but its notifications are terrible
|
|
|
|
$.browser.webkit &&
|
2012-11-26 23:57:31 +01:00
|
|
|
// 0 is PERMISSION_ALLOWED
|
2013-05-31 22:25:39 +02:00
|
|
|
window.webkitNotifications.checkPermission() === 0) ||
|
|
|
|
// window.bridge is the desktop client
|
|
|
|
(window.bridge !== undefined);
|
2012-11-26 23:57:31 +01:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
|
2012-11-26 23:57:31 +01:00
|
|
|
exports.initialize = function () {
|
2012-11-23 23:53:38 +01:00
|
|
|
$(window).focus(function () {
|
|
|
|
window_has_focus = true;
|
2012-11-27 18:26:36 +01:00
|
|
|
|
|
|
|
$.each(notice_memory, function (index, notice_mem_entry) {
|
|
|
|
notice_mem_entry.obj.cancel();
|
|
|
|
});
|
2013-03-04 23:44:07 +01:00
|
|
|
|
Clean up code for unread counts and notifications.
The core simplification here is that zephyr.js no longer has:
* the global home_unread_messages
* the function unread_in_current_view() [which used the global]
The logic that used to be in zephyr is now in its proper home
of unread.js, which has these changes:
* the structure returned from unread.get_counts() includes
a new member called unread_in_current_view
* there's a helper function unread.num_unread_current_messages()
Deprecating zephyr.unread_in_current_view() affected two callers:
* notifications.update_title_count()
* notifications_bar.update()
The above functions used to call back to zephyr to get counts, but
there was no nice way to enforce that they were getting counts
at the right time in the code flow, because they depended on
functions like process_visible_unread_messages() to orchestrate
updating internal unread counts before pushing out counts to the DOM.
Now both of those function take a parameter with the unread count,
and we then had to change all of their callers appropriately. This
went hand in hand with another goal, which is that we want all the
unread-counts logic to funnel though basically one place, which
is zephyr.update_unread_counts(). So now that function always
calls notifications_bar.update() [NEW] as well as calling into
the modules unread.js, stream_list.js, and notifications.js [OLD].
Adding the call to notifications_bar.update() in update_unread_counts()
made it so that some other places in the code no longer needed to call
notifications_bar.update(), so you'll see some lines of code
removed. There are also cases where notifications.update_title_count()
was called redundantly, since the callers were already reaching
update_unread_counts() via other calls.
Finally, in ui.resizehandler, you'll see a simple case where the call
to notifications_bar.update() is preceded by an explicit call
to unread.get_counts().
(imported from commit ce84b9c8076c1f9bb20a61209913f0cb0dae098c)
2013-06-05 21:04:06 +02:00
|
|
|
// Update many places on the DOM to reflect unread
|
|
|
|
// counts.
|
2013-03-04 23:44:07 +01:00
|
|
|
process_visible_unread_messages();
|
2012-11-23 23:53:38 +01:00
|
|
|
}).blur(function () {
|
|
|
|
window_has_focus = false;
|
|
|
|
});
|
|
|
|
|
2012-11-26 23:57:31 +01:00
|
|
|
if (!window.webkitNotifications) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-11-23 23:53:38 +01:00
|
|
|
$(document).click(function () {
|
2013-03-25 23:26:14 +01:00
|
|
|
if (!page_params.desktop_notifications_enabled || asked_permission_already) {
|
2012-11-23 23:53:38 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (window.webkitNotifications.checkPermission() !== 0) { // 0 is PERMISSION_ALLOWED
|
2013-02-26 23:46:53 +01:00
|
|
|
window.webkitNotifications.requestPermission(function () {});
|
2013-01-08 16:54:44 +01:00
|
|
|
asked_permission_already = true;
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
|
|
|
});
|
2013-05-03 21:36:38 +02:00
|
|
|
var audio = $("<audio>");
|
2013-06-06 19:28:31 +02:00
|
|
|
if (window.bridge !== undefined) {
|
|
|
|
supports_sound = true;
|
|
|
|
} else if (audio[0].canPlayType === undefined) {
|
2013-05-03 21:36:38 +02:00
|
|
|
supports_sound = false;
|
|
|
|
} else {
|
|
|
|
supports_sound = true;
|
|
|
|
$("#notifications-area").append(audio);
|
|
|
|
if (audio[0].canPlayType('audio/ogg; codecs="vorbis"')) {
|
|
|
|
audio.append($("<source>").attr("type", "audio/ogg")
|
|
|
|
.attr("loop", "yes")
|
|
|
|
.attr("src", "/static/audio/humbug.ogg"));
|
|
|
|
} else {
|
|
|
|
audio.append($("<source>").attr("type", "audio/mpeg")
|
|
|
|
.attr("loop", "yes")
|
|
|
|
.attr("src", "/static/audio/humbug.mp3"));
|
|
|
|
}
|
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
};
|
|
|
|
|
Clean up code for unread counts and notifications.
The core simplification here is that zephyr.js no longer has:
* the global home_unread_messages
* the function unread_in_current_view() [which used the global]
The logic that used to be in zephyr is now in its proper home
of unread.js, which has these changes:
* the structure returned from unread.get_counts() includes
a new member called unread_in_current_view
* there's a helper function unread.num_unread_current_messages()
Deprecating zephyr.unread_in_current_view() affected two callers:
* notifications.update_title_count()
* notifications_bar.update()
The above functions used to call back to zephyr to get counts, but
there was no nice way to enforce that they were getting counts
at the right time in the code flow, because they depended on
functions like process_visible_unread_messages() to orchestrate
updating internal unread counts before pushing out counts to the DOM.
Now both of those function take a parameter with the unread count,
and we then had to change all of their callers appropriately. This
went hand in hand with another goal, which is that we want all the
unread-counts logic to funnel though basically one place, which
is zephyr.update_unread_counts(). So now that function always
calls notifications_bar.update() [NEW] as well as calling into
the modules unread.js, stream_list.js, and notifications.js [OLD].
Adding the call to notifications_bar.update() in update_unread_counts()
made it so that some other places in the code no longer needed to call
notifications_bar.update(), so you'll see some lines of code
removed. There are also cases where notifications.update_title_count()
was called redundantly, since the callers were already reaching
update_unread_counts() via other calls.
Finally, in ui.resizehandler, you'll see a simple case where the call
to notifications_bar.update() is preceded by an explicit call
to unread.get_counts().
(imported from commit ce84b9c8076c1f9bb20a61209913f0cb0dae098c)
2013-06-05 21:04:06 +02:00
|
|
|
exports.update_title_count = function (new_message_count) {
|
2013-03-19 21:53:49 +01:00
|
|
|
// Update window title and favicon to reflect unread messages in current view
|
|
|
|
var n;
|
|
|
|
|
2013-05-06 22:35:10 +02:00
|
|
|
var new_title = (new_message_count ? ("(" + new_message_count + ") ") : "")
|
2013-03-25 23:26:14 +01:00
|
|
|
+ page_params.domain + " - Humbug";
|
2013-03-19 21:53:49 +01:00
|
|
|
|
2013-05-06 22:35:10 +02:00
|
|
|
if (document.title === new_title) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
document.title = new_title;
|
|
|
|
|
2013-03-19 21:53:49 +01:00
|
|
|
// IE doesn't support PNG favicons, *shrug*
|
|
|
|
if (! $.browser.msie) {
|
|
|
|
// Indicate the message count in the favicon
|
|
|
|
if (new_message_count) {
|
|
|
|
// Make sure we're working with a number, as a defensive programming
|
|
|
|
// measure. And we don't have images above 99, so display those as
|
|
|
|
// 'infinite'.
|
|
|
|
n = (+new_message_count);
|
|
|
|
if (n > 99)
|
|
|
|
n = 'infinite';
|
|
|
|
|
|
|
|
util.set_favicon('/static/images/favicon/favicon-'+n+'.png');
|
|
|
|
} else {
|
|
|
|
util.set_favicon('/static/favicon.ico?v=2');
|
|
|
|
}
|
|
|
|
}
|
2013-06-02 20:42:03 +02:00
|
|
|
|
|
|
|
if (window.bridge !== undefined) {
|
|
|
|
// We don't use 'n' because we want the exact count. The bridge handles
|
|
|
|
// which icon to show.
|
|
|
|
window.bridge.updateCount(new_message_count);
|
|
|
|
}
|
2013-03-19 21:53:49 +01:00
|
|
|
};
|
|
|
|
|
2013-06-19 00:00:40 +02:00
|
|
|
exports.update_pm_count = function (new_pm_count) {
|
|
|
|
if (window.bridge !== undefined && window.bridge.updatePMCount !== undefined) {
|
|
|
|
window.bridge.updatePMCount(new_pm_count);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-04 23:44:07 +01:00
|
|
|
exports.window_has_focus = function () {
|
|
|
|
return window_has_focus;
|
|
|
|
};
|
|
|
|
|
2013-06-19 01:41:27 +02:00
|
|
|
function process_notification(notification) {
|
2013-01-09 23:49:54 +01:00
|
|
|
var i, notification_object, key;
|
2013-06-19 01:41:27 +02:00
|
|
|
var message = notification.message;
|
2012-11-23 23:53:38 +01:00
|
|
|
var title = message.sender_full_name;
|
|
|
|
var content = $('<div/>').html(message.content).text();
|
2013-01-09 23:49:54 +01:00
|
|
|
var other_recipients;
|
2012-11-23 23:53:38 +01:00
|
|
|
var msg_count = 1;
|
|
|
|
|
2013-01-09 23:49:54 +01:00
|
|
|
if (message.type === "private") {
|
|
|
|
key = message.display_reply_to;
|
|
|
|
other_recipients = message.display_reply_to;
|
|
|
|
// Remove the sender from the list of other recipients
|
|
|
|
other_recipients = other_recipients.replace(", " + message.sender_full_name, "");
|
|
|
|
other_recipients = other_recipients.replace(message.sender_full_name + ", ", "");
|
|
|
|
} else {
|
|
|
|
key = message.sender_full_name + " to " +
|
2013-04-18 17:13:43 +02:00
|
|
|
message.stream + " > " + message.subject;
|
2013-01-09 23:49:54 +01:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
|
|
|
|
if (content.length > 150) {
|
|
|
|
// Truncate content at a word boundary
|
|
|
|
for (i = 150; i > 0; i--) {
|
|
|
|
if (content[i] === ' ') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
content = content.substring(0, i);
|
|
|
|
content += " [...]";
|
|
|
|
}
|
|
|
|
|
2013-05-31 22:25:39 +02:00
|
|
|
if (window.bridge === undefined && notice_memory[key] !== undefined) {
|
2012-11-23 23:53:38 +01:00
|
|
|
msg_count = notice_memory[key].msg_count + 1;
|
|
|
|
title = msg_count + " messages from " + title;
|
|
|
|
notification_object = notice_memory[key].obj;
|
|
|
|
// We must remove the .onclose so that it does not trigger on .cancel
|
|
|
|
notification_object.onclose = function () {};
|
|
|
|
notification_object.onclick = function () {};
|
|
|
|
notification_object.cancel();
|
|
|
|
}
|
|
|
|
|
2012-12-03 19:49:12 +01:00
|
|
|
if (message.type === "private" && message.display_recipient.length > 2) {
|
2012-11-23 23:53:38 +01:00
|
|
|
// If the message has too many recipients to list them all...
|
|
|
|
if (content.length + title.length + other_recipients.length > 230) {
|
|
|
|
// Then count how many people are in the conversation and summarize
|
|
|
|
// by saying the conversation is with "you and [number] other people"
|
|
|
|
other_recipients = other_recipients.replace(/[^,]/g, "").length +
|
|
|
|
" other people";
|
|
|
|
}
|
|
|
|
title += " (to you and " + other_recipients + ")";
|
|
|
|
}
|
2013-01-09 23:49:54 +01:00
|
|
|
if (message.type === "stream") {
|
2013-04-18 17:13:43 +02:00
|
|
|
title += " (to " + message.stream + " > " + message.subject + ")";
|
2013-01-09 23:49:54 +01:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
|
2013-06-19 01:41:27 +02:00
|
|
|
if (window.bridge === undefined && notification.in_browser === undefined) {
|
2013-06-13 23:48:23 +02:00
|
|
|
var icon_url = ui.small_avatar_url(message);
|
2013-05-31 22:25:39 +02:00
|
|
|
notice_memory[key] = {
|
|
|
|
obj: window.webkitNotifications.createNotification(
|
2013-06-13 23:48:23 +02:00
|
|
|
icon_url, title, content),
|
2013-05-31 22:25:39 +02:00
|
|
|
msg_count: msg_count
|
|
|
|
};
|
|
|
|
notification_object = notice_memory[key].obj;
|
|
|
|
notification_object.onclick = function () {
|
|
|
|
notification_object.cancel();
|
|
|
|
window.focus();
|
|
|
|
};
|
|
|
|
notification_object.onclose = function () {
|
|
|
|
delete notice_memory[key];
|
|
|
|
};
|
|
|
|
notification_object.show();
|
2013-06-19 01:41:27 +02:00
|
|
|
} else if (notification.in_browser === true) {
|
|
|
|
var notification_html = $(templates.render('notification', {gravatar_url: ui.small_avatar_url(message),
|
|
|
|
sender_fullname: message.sender_full_name,
|
|
|
|
content: content}));
|
|
|
|
$('.top-right').notify({
|
|
|
|
message: { html: notification_html },
|
|
|
|
fadeOut: {enabled:true, delay: 4000}
|
|
|
|
}).show();
|
2013-05-31 22:25:39 +02:00
|
|
|
} else {
|
|
|
|
// Shunt the message along to the desktop client
|
2013-06-14 00:25:02 +02:00
|
|
|
window.bridge.desktopNotification(title, content);
|
2013-05-31 22:25:39 +02:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
|
|
|
|
2013-03-12 22:36:13 +01:00
|
|
|
exports.speaking_at_me = function (message) {
|
2013-05-31 16:15:27 +02:00
|
|
|
if (message === undefined) {
|
2013-01-10 04:47:11 +01:00
|
|
|
return false;
|
2013-01-10 17:24:39 +01:00
|
|
|
}
|
2013-05-31 16:15:27 +02:00
|
|
|
|
|
|
|
return message.mentioned;
|
2013-03-12 22:36:13 +01:00
|
|
|
};
|
2013-01-09 23:49:54 +01:00
|
|
|
|
2013-05-03 21:49:01 +02:00
|
|
|
function message_is_notifiable(message) {
|
|
|
|
// based purely on message contents, can we notify the user about the message?
|
2013-06-25 19:26:42 +02:00
|
|
|
return (!message.sent_by_me &&
|
|
|
|
(message.type === "private" ||
|
|
|
|
exports.speaking_at_me(message) ||
|
|
|
|
(message.type === "stream" &&
|
|
|
|
subs.receives_notifications(message.stream))));
|
2013-05-03 21:49:01 +02:00
|
|
|
}
|
|
|
|
|
2012-11-23 23:53:38 +01:00
|
|
|
exports.received_messages = function (messages) {
|
Fix unread count in favicon when focusing window.
This fixes a pretty subtle bug where the window-focus handler
wasn't updating the unread counts in the title, but it was
hard to notice, because as soon as you moved the mouse, the
problem fixed itself.
Apart from fixing the bug, this patch eliminates the expensive
mouseover handler, which is a big win.
The fix to the window-focus involved some unrelated cleanup. I
decoupled update_title_count() from received_messages(), as the
former method will probably live somewhere else soon.
Also, in order to get window-focus to update the title count,
I went pretty deep in the stack and added a call to
update_title_count() inside of update_unread_counts(). This
fixes window-focus as well as restoring that behavior to
code paths that were calling received_messages().
You'll see that call to update_title_count() is now adjacent
to the call to update_dom_with_unread_counts(), which is
fairly sensible, but then are calls to similar methods like
notifications.received_messages() that happen higher up in
the call chain, which seems kind of inconsistent to me. I
also don't like the fact that you have to go through a
mostly model-based function to get to view-based stuff, so
there are some refactorings coming.
(imported from commit 2261450f205f1aa81d30194b371a1c5ac6a7bdec)
2013-05-17 18:31:04 +02:00
|
|
|
var i;
|
2012-11-23 23:53:38 +01:00
|
|
|
|
|
|
|
$.each(messages, function (index, message) {
|
2013-06-14 16:46:37 +02:00
|
|
|
// We send notifications for messages which the user has
|
|
|
|
// configured as notifiable, as long as they haven't been
|
|
|
|
// marked as read by process_visible_unread_messages
|
|
|
|
// (which occurs if the message arrived onscreen while the
|
|
|
|
// window had focus).
|
|
|
|
if (!(message_is_notifiable(message) && unread.message_unread(message))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (page_params.desktop_notifications_enabled &&
|
|
|
|
browser_desktop_notifications_on()) {
|
2013-06-19 01:41:27 +02:00
|
|
|
process_notification({message: message});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
process_notification({message: message, in_browser: true});
|
2013-06-14 16:46:37 +02:00
|
|
|
}
|
|
|
|
if (page_params.sounds_enabled && supports_sound) {
|
|
|
|
if (window.bridge !== undefined) {
|
|
|
|
window.bridge.bell();
|
|
|
|
} else {
|
|
|
|
$("#notifications-area").find("audio")[0].play();
|
2012-11-26 23:57:31 +01:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2013-06-14 00:25:02 +02:00
|
|
|
$(function () {
|
|
|
|
// Shim for Cocoa WebScript exporting top-level JS
|
|
|
|
// objects instead of window.foo objects
|
|
|
|
if (typeof(bridge) !== 'undefined' && window.bridge === undefined) {
|
|
|
|
window.bridge = bridge;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-11-23 23:53:38 +01:00
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|