2013-02-07 19:57:45 +01:00
|
|
|
var activity = (function () {
|
2013-02-12 20:40:28 +01:00
|
|
|
var exports = {};
|
2013-02-07 19:57:45 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
Helpers for detecting user activity and managing user idle states
|
|
|
|
*/
|
|
|
|
|
2013-08-20 20:57:26 +02:00
|
|
|
/* Broadcast "idle" to server after 5 minutes of local inactivity */
|
|
|
|
var DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
2013-03-05 17:11:55 +01:00
|
|
|
/* Time between keep-alive pings */
|
2013-08-21 23:40:12 +02:00
|
|
|
var ACTIVE_PING_INTERVAL_MS = 50 * 1000;
|
2013-02-12 20:40:28 +01:00
|
|
|
|
2013-08-21 23:40:12 +02:00
|
|
|
/* Mark users as offline after 140 seconds since their last checkin */
|
|
|
|
var OFFLINE_THRESHOLD_SECS = 140;
|
2013-02-07 19:57:45 +01:00
|
|
|
|
2013-10-23 19:02:18 +02:00
|
|
|
|
|
|
|
var presence_descriptions = {
|
|
|
|
active: 'is active',
|
|
|
|
idle: 'is not active'
|
|
|
|
};
|
|
|
|
|
2013-02-07 19:57:45 +01:00
|
|
|
/* Keep in sync with views.py:json_update_active_status() */
|
2013-08-20 21:18:56 +02:00
|
|
|
exports.ACTIVE = "active";
|
|
|
|
exports.IDLE = "idle";
|
2013-02-07 19:57:45 +01:00
|
|
|
|
2013-09-16 18:22:52 +02:00
|
|
|
// When you start Zulip, has_focus should be true, but it might not be the
|
|
|
|
// case after a server-initiated reload.
|
|
|
|
exports.has_focus = document.hasFocus && document.hasFocus();
|
2013-02-07 19:57:45 +01:00
|
|
|
|
2013-09-06 21:52:12 +02:00
|
|
|
// We initialize this to true, to count new page loads, but set it to
|
|
|
|
// false in the onload function in reload.js if this was a
|
|
|
|
// server-initiated-reload to avoid counting a server-initiated reload
|
|
|
|
// as user activity.
|
|
|
|
exports.new_user_input = true;
|
|
|
|
|
|
|
|
$("html").on("mousemove", function () {
|
|
|
|
exports.new_user_input = true;
|
|
|
|
});
|
|
|
|
|
2013-10-23 18:46:47 +02:00
|
|
|
var presence_info = {};
|
2013-04-03 22:00:02 +02:00
|
|
|
|
2013-10-23 20:44:31 +02:00
|
|
|
var huddle_timestamps = new Dict();
|
|
|
|
|
|
|
|
exports.process_loaded_messages = function (messages) {
|
|
|
|
_.each(messages, function (message) {
|
|
|
|
if (message.type === 'private') {
|
|
|
|
if (message.reply_to.indexOf(',') > 0) {
|
|
|
|
var old_timestamp = huddle_timestamps.get(message.reply_to);
|
|
|
|
|
|
|
|
if (!old_timestamp || (old_timestamp < message.timestamp)) {
|
|
|
|
huddle_timestamps.set(message.reply_to, message.timestamp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.get_huddles = function () {
|
|
|
|
var huddles = huddle_timestamps.keys();
|
|
|
|
huddles = _.sortBy(huddles, function (huddle) {
|
|
|
|
return huddle_timestamps.get(huddle);
|
|
|
|
});
|
|
|
|
return huddles.reverse();
|
|
|
|
};
|
|
|
|
|
2013-10-23 22:22:34 +02:00
|
|
|
exports.full_huddle_name = function (huddle) {
|
|
|
|
var emails = huddle.split(',');
|
|
|
|
|
|
|
|
var names = _.map(emails, function (email) {
|
|
|
|
var person = people_dict.get(email);
|
|
|
|
return person ? person.full_name : email;
|
|
|
|
});
|
|
|
|
|
|
|
|
return names.join(', ');
|
|
|
|
};
|
|
|
|
|
2013-11-04 22:59:03 +01:00
|
|
|
exports.short_huddle_name = function (huddle) {
|
|
|
|
var emails = huddle.split(',');
|
|
|
|
|
2013-12-05 22:20:20 +01:00
|
|
|
var num_to_show = 3;
|
|
|
|
var names = _.map(emails.slice(0, num_to_show), function (email) {
|
2013-11-04 22:59:03 +01:00
|
|
|
var person = people_dict.get(email);
|
|
|
|
return person ? person.full_name : email;
|
|
|
|
});
|
2013-12-05 22:20:20 +01:00
|
|
|
var others = emails.length - num_to_show;
|
2013-11-04 22:59:03 +01:00
|
|
|
|
|
|
|
if (others === 1) {
|
|
|
|
names.push("+ 1 other");
|
|
|
|
} else if (others >= 2) {
|
|
|
|
names.push("+ " + others + " others");
|
|
|
|
}
|
|
|
|
|
|
|
|
return names.join(', ');
|
|
|
|
};
|
|
|
|
|
2013-11-04 21:56:56 +01:00
|
|
|
exports.huddle_fraction_present = function (huddle, presence_info) {
|
|
|
|
var emails = huddle.split(',');
|
|
|
|
|
|
|
|
var num_present = 0;
|
|
|
|
_.each(emails, function (email) {
|
|
|
|
var status = presence_info[email];
|
|
|
|
if (status && (status !== 'offline')) {
|
|
|
|
++num_present;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var ratio = num_present / emails.length;
|
|
|
|
|
|
|
|
return ratio.toFixed(2);
|
|
|
|
};
|
|
|
|
|
2013-10-23 18:46:47 +02:00
|
|
|
function sort_users(users, presence_info) {
|
2013-02-07 19:57:45 +01:00
|
|
|
// TODO sort by unread count first, once we support that
|
|
|
|
users.sort(function (a, b) {
|
2013-10-23 18:46:47 +02:00
|
|
|
if (presence_info[a] === 'active' && presence_info[b] !== 'active') {
|
2013-02-12 20:40:28 +01:00
|
|
|
return -1;
|
2013-10-23 18:46:47 +02:00
|
|
|
} else if (presence_info[b] === 'active' && presence_info[a] !== 'active') {
|
2013-02-12 20:40:28 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-10-23 18:46:47 +02:00
|
|
|
if (presence_info[a] === 'idle' && presence_info[b] !== 'idle') {
|
2013-02-07 19:57:45 +01:00
|
|
|
return -1;
|
2013-10-23 18:46:47 +02:00
|
|
|
} else if (presence_info[b] === 'idle' && presence_info[a] !== 'idle') {
|
2013-02-07 19:57:45 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort equivalent PM names alphabetically
|
2013-02-15 21:01:49 +01:00
|
|
|
var full_name_a = a;
|
|
|
|
var full_name_b = b;
|
2013-08-07 23:56:51 +02:00
|
|
|
if (people_dict.has(a)) {
|
|
|
|
full_name_a = people_dict.get(a).full_name;
|
2013-02-15 21:01:49 +01:00
|
|
|
}
|
2013-08-07 23:56:51 +02:00
|
|
|
if (people_dict.has(b)) {
|
|
|
|
full_name_b = people_dict.get(b).full_name;
|
2013-02-15 21:01:49 +01:00
|
|
|
}
|
2013-05-03 19:16:50 +02:00
|
|
|
return util.strcmp(full_name_a, full_name_b);
|
2013-02-07 19:57:45 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return users;
|
|
|
|
}
|
|
|
|
|
2013-08-09 14:42:49 +02:00
|
|
|
// for testing:
|
|
|
|
exports._sort_users = sort_users;
|
|
|
|
|
2013-02-07 19:57:45 +01:00
|
|
|
function focus_lost() {
|
2013-08-20 21:18:56 +02:00
|
|
|
if (!exports.has_focus) {
|
2013-02-07 19:57:45 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-20 21:18:56 +02:00
|
|
|
exports.has_focus = false;
|
2013-02-07 19:57:45 +01:00
|
|
|
}
|
|
|
|
|
2013-12-17 17:34:46 +01:00
|
|
|
function actually_update_users() {
|
2013-10-23 19:02:18 +02:00
|
|
|
if (page_params.domain === 'mit.edu') {
|
|
|
|
return; // MIT realm doesn't have a presence list
|
|
|
|
}
|
|
|
|
|
2013-10-23 19:12:40 +02:00
|
|
|
var users = sort_users(Object.keys(presence_info), presence_info);
|
|
|
|
|
2013-12-16 19:53:21 +01:00
|
|
|
function get_num_unread(email) {
|
|
|
|
if (suppress_unread_counts) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return unread.num_unread_for_person(email);
|
|
|
|
}
|
|
|
|
|
2013-10-23 19:02:18 +02:00
|
|
|
var my_info = {
|
|
|
|
name: page_params.fullname,
|
|
|
|
email: page_params.email,
|
2013-12-16 19:53:21 +01:00
|
|
|
num_unread: get_num_unread(page_params.email),
|
2013-10-23 19:02:18 +02:00
|
|
|
type: (activity.has_focus) ? activity.ACTIVE : activity.IDLE,
|
|
|
|
type_desc: presence_descriptions.active,
|
|
|
|
my_fullname: true
|
|
|
|
};
|
|
|
|
|
|
|
|
function info_for(email) {
|
|
|
|
var presence = presence_info[email];
|
|
|
|
return {
|
|
|
|
name: people_dict.get(email).full_name,
|
|
|
|
email: email,
|
2013-12-16 19:53:21 +01:00
|
|
|
num_unread: get_num_unread(email),
|
2013-10-23 19:02:18 +02:00
|
|
|
type: presence,
|
|
|
|
type_desc: presence_descriptions[presence]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var user_emails = _.filter(users, function (email) {
|
|
|
|
return people_dict.has(email);
|
|
|
|
});
|
|
|
|
|
|
|
|
var user_info = [my_info].concat(_.map(user_emails, info_for));
|
|
|
|
|
|
|
|
$('#user_presences').html(templates.render('user_presence_rows', {users: user_info}));
|
|
|
|
|
|
|
|
// Update user fading, if necessary.
|
|
|
|
compose_fade.update_faded_users();
|
2013-04-03 22:00:02 +02:00
|
|
|
}
|
|
|
|
|
2013-12-17 17:34:46 +01:00
|
|
|
// The function actually_update_users() can be pretty expensive for realms with lots
|
|
|
|
// of users. Not only is there more work to do in terms of rendering the user list, but
|
|
|
|
// we also get more updates. Large realms have reported lags while typing in the compose
|
|
|
|
// box, and there's strong evidence that this is caused by user list updates. This isn't a
|
|
|
|
// perfect solution, but it should remove some pain, and there's no real harm in waiting five
|
|
|
|
// seconds to update user activity.
|
|
|
|
var update_users = _.throttle(actually_update_users, 5000);
|
|
|
|
|
2013-10-18 00:56:49 +02:00
|
|
|
exports.update_huddles = function () {
|
2013-11-28 22:13:01 +01:00
|
|
|
if (page_params.domain === 'mit.edu') {
|
|
|
|
return; // MIT realm doesn't have a presence list
|
|
|
|
}
|
|
|
|
|
2013-10-18 00:56:49 +02:00
|
|
|
var section = $('#group-pm-list').expectOne();
|
|
|
|
|
|
|
|
var huddles = exports.get_huddles().slice(0, 10);
|
|
|
|
|
2013-12-03 16:44:42 +01:00
|
|
|
if (huddles.length === 0) {
|
2013-10-18 00:56:49 +02:00
|
|
|
section.hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var group_pms = _.map(huddles, function (huddle) {
|
|
|
|
return {
|
|
|
|
emails: huddle,
|
|
|
|
name: exports.full_huddle_name(huddle),
|
|
|
|
fraction_present: exports.huddle_fraction_present(huddle, presence_info),
|
|
|
|
short_name: exports.short_huddle_name(huddle)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
var html = templates.render('group_pms', {group_pms: group_pms});
|
|
|
|
$('#group-pms').expectOne().html(html);
|
2013-11-08 00:53:12 +01:00
|
|
|
|
|
|
|
_.each(huddles, function (huddle) {
|
|
|
|
var count = unread.num_unread_for_person(huddle);
|
|
|
|
stream_list.set_presence_list_count(huddle, count);
|
|
|
|
});
|
|
|
|
|
2013-10-18 00:56:49 +02:00
|
|
|
section.show();
|
|
|
|
};
|
|
|
|
|
2013-05-06 17:14:59 +02:00
|
|
|
function status_from_timestamp(baseline_time, presence) {
|
2013-04-05 00:13:03 +02:00
|
|
|
if (presence.website === undefined) {
|
2013-08-20 20:57:26 +02:00
|
|
|
return 'offline';
|
2013-04-05 00:13:03 +02:00
|
|
|
}
|
|
|
|
|
2013-05-06 17:14:59 +02:00
|
|
|
var age = baseline_time - presence.website.timestamp;
|
2013-04-05 00:13:03 +02:00
|
|
|
|
2013-08-20 20:57:26 +02:00
|
|
|
var status = 'offline';
|
|
|
|
if (age < OFFLINE_THRESHOLD_SECS) {
|
|
|
|
status = presence.website.status;
|
2013-04-05 00:13:03 +02:00
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2013-02-07 19:57:45 +01:00
|
|
|
function focus_ping() {
|
2013-08-20 20:57:26 +02:00
|
|
|
$.post('/json/update_active_status',
|
2013-09-06 21:52:12 +02:00
|
|
|
{status: (exports.has_focus) ? exports.ACTIVE : exports.IDLE,
|
|
|
|
new_user_input: exports.new_user_input}, function (data) {
|
2013-06-12 19:58:25 +02:00
|
|
|
if (data === undefined || data.presences === undefined) {
|
|
|
|
// We sometimes receive no data even on successful
|
|
|
|
// requests; we should figure out why but this will
|
|
|
|
// prevent us from throwing errors until then
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-10-23 18:46:47 +02:00
|
|
|
presence_info = {};
|
2013-02-07 19:57:45 +01:00
|
|
|
|
2013-03-13 20:02:57 +01:00
|
|
|
// Update Zephyr mirror activity warning
|
|
|
|
if (data.zephyr_mirror_active === false) {
|
|
|
|
$('#zephyr-mirror-error').show();
|
|
|
|
} else {
|
|
|
|
$('#zephyr-mirror-error').hide();
|
|
|
|
}
|
|
|
|
|
2013-09-06 21:52:12 +02:00
|
|
|
exports.new_user_input = false;
|
|
|
|
|
2013-02-07 19:57:45 +01:00
|
|
|
// Ping returns the active peer list
|
2013-07-30 00:35:44 +02:00
|
|
|
_.each(data.presences, function (presence, this_email) {
|
2013-03-25 23:26:14 +01:00
|
|
|
if (page_params.email !== this_email) {
|
2013-10-23 18:46:47 +02:00
|
|
|
presence_info[this_email] = status_from_timestamp(data.server_timestamp, presence);
|
2013-02-07 19:57:45 +01:00
|
|
|
}
|
|
|
|
});
|
2013-04-03 22:00:02 +02:00
|
|
|
update_users();
|
2013-10-18 00:56:49 +02:00
|
|
|
exports.update_huddles();
|
2013-02-07 19:57:45 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function focus_gained() {
|
2013-08-20 21:18:56 +02:00
|
|
|
if (!exports.has_focus) {
|
|
|
|
exports.has_focus = true;
|
2013-02-07 19:57:45 +01:00
|
|
|
|
|
|
|
focus_ping();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.initialize = function () {
|
|
|
|
$(window).focus(focus_gained);
|
2013-02-12 20:40:28 +01:00
|
|
|
$(window).idle({idle: DEFAULT_IDLE_TIMEOUT_MS,
|
2013-02-07 19:57:45 +01:00
|
|
|
onIdle: focus_lost,
|
|
|
|
onActive: focus_gained,
|
|
|
|
keepTracking: true});
|
|
|
|
|
2013-08-20 20:57:26 +02:00
|
|
|
setInterval(focus_ping, ACTIVE_PING_INTERVAL_MS);
|
2013-04-05 00:13:03 +02:00
|
|
|
|
|
|
|
focus_ping();
|
2013-04-03 22:00:02 +02:00
|
|
|
};
|
|
|
|
|
2013-07-11 21:51:26 +02:00
|
|
|
// Set user statuses. `users` should be an object with user emails as keys
|
|
|
|
// and presence information (see `status_from_timestamp`) as values.
|
|
|
|
//
|
|
|
|
// The object does not need to include every user, only the ones
|
|
|
|
// whose presence you wish to update.
|
|
|
|
//
|
|
|
|
// This rerenders the user sidebar at the end, which can be slow if done too
|
|
|
|
// often, so try to avoid calling this repeatedly.
|
|
|
|
exports.set_user_statuses = function (users, server_time) {
|
2013-07-30 00:35:44 +02:00
|
|
|
_.each(users, function (presence, email) {
|
2013-07-11 21:51:26 +02:00
|
|
|
if (email === page_params.email) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-23 18:46:47 +02:00
|
|
|
presence_info[email] = status_from_timestamp(server_time, presence);
|
2013-07-11 21:51:26 +02:00
|
|
|
});
|
2013-02-07 19:57:45 +01:00
|
|
|
|
2013-04-03 22:00:02 +02:00
|
|
|
update_users();
|
2013-10-18 00:56:49 +02:00
|
|
|
exports.update_huddles();
|
2013-02-07 19:57:45 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|
2013-08-09 14:42:49 +02:00
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = activity;
|
|
|
|
}
|