mirror of https://github.com/zulip/zulip.git
185 lines
6.5 KiB
JavaScript
185 lines
6.5 KiB
JavaScript
var typing = (function () {
|
|
var exports = {};
|
|
// How long before we assume a client has gone away
|
|
// and expire its typing status
|
|
var TYPING_STARTED_EXPIRY_PERIOD = 15000; // 15s
|
|
// How frequently 'still typing' notifications are sent
|
|
// to extend the expiry
|
|
var TYPING_STARTED_SEND_FREQUENCY = 10000; // 10s
|
|
// How long after someone stops editing in the compose box
|
|
// do we send a 'stopped typing' notification
|
|
var TYPING_STOPPED_WAIT_PERIOD = 5000; // 5s
|
|
|
|
var current_recipient;
|
|
var users_currently_typing = new Dict();
|
|
var stop_typing_timers = new Dict();
|
|
|
|
// Our logic is a bit too complex to encapsulate in
|
|
// _.throttle/_.debounce (since we need to cancel things), so we do it
|
|
// manually.
|
|
var stop_timer;
|
|
var last_start_time;
|
|
|
|
function send_typing_notification_ajax(recipients, operation) {
|
|
channel.post({
|
|
url: '/json/typing',
|
|
data: {
|
|
to: recipients,
|
|
op: operation,
|
|
},
|
|
success: function () {},
|
|
error: function (xhr) {
|
|
blueslip.warn("Failed to send typing event: " + xhr.responseText);
|
|
},
|
|
});
|
|
}
|
|
|
|
function check_and_send(operation) {
|
|
var compose_recipient = compose_state.recipient();
|
|
var compose_nonempty = compose_state.has_message_content();
|
|
|
|
// If we currently have an active typing notification out, and we
|
|
// want to send a stop notice, or the compose recipient changed
|
|
// (and implicitly we're sending a start notice), send a stop
|
|
// notice to the old recipient.
|
|
if (current_recipient !== undefined &&
|
|
(operation === 'stop' ||
|
|
current_recipient !== compose_recipient)) {
|
|
send_typing_notification_ajax(current_recipient, 'stop');
|
|
// clear the automatic stop notification timer and recipient.
|
|
clearTimeout(stop_timer);
|
|
stop_timer = undefined;
|
|
current_recipient = undefined;
|
|
}
|
|
if (operation === 'start') {
|
|
if (compose_recipient !== undefined && compose_recipient !== "" && compose_nonempty) {
|
|
current_recipient = compose_recipient;
|
|
send_typing_notification_ajax(compose_recipient, operation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: Because we don't make sure we send a final start notification
|
|
// at the last time a user typed something, we require that
|
|
// TYPING_STARTED_SEND_FREQUENCY + TYPING_STOPPED_WAIT_PERIOD <= TYPING_STARTED_EXPIRY_PERIOD
|
|
$(document).on('input', '#new_message_content', function () {
|
|
// If our previous state was no typing notification, send a
|
|
// start-typing notice immediately.
|
|
var current_time = new Date();
|
|
if (current_recipient === undefined ||
|
|
current_time - last_start_time > TYPING_STARTED_SEND_FREQUENCY) {
|
|
last_start_time = current_time;
|
|
check_and_send("start");
|
|
}
|
|
|
|
// Then, regardless of whether we changed state, reset the
|
|
// stop-notification timeout to TYPING_STOPPED_WAIT_PERIOD from
|
|
// now, so that we'll send a stop notice exactly that long after
|
|
// stopping typing.
|
|
if (stop_timer !== undefined) {
|
|
// Clear an existing stop_timer, if any.
|
|
clearTimeout(stop_timer);
|
|
}
|
|
stop_timer = setTimeout(function () {
|
|
check_and_send('stop');
|
|
}, TYPING_STOPPED_WAIT_PERIOD);
|
|
});
|
|
|
|
// We send a stop-typing notification immediately when compose is
|
|
// closed/cancelled
|
|
$(document).on('compose_canceled.zulip compose_finished.zulip', function () {
|
|
check_and_send('stop');
|
|
});
|
|
|
|
function get_users_typing_for_narrow() {
|
|
if (!narrow.narrowed_to_pms()) {
|
|
// Narrow is neither pm-with nor is: private
|
|
return [];
|
|
}
|
|
if (narrow.operators()[0].operator === 'pm-with') {
|
|
// Get list of users typing in this conversation
|
|
var narrow_emails_string = narrow.operators()[0].operand;
|
|
var narrow_user_ids_string = people.emails_strings_to_user_ids_string(narrow_emails_string);
|
|
var narrow_user_ids = narrow_user_ids_string.split(',').map(function (user_id_string) {
|
|
return parseInt(user_id_string, 10);
|
|
});
|
|
var group = narrow_user_ids.concat([page_params.user_id]);
|
|
group.sort();
|
|
return users_currently_typing.setdefault(group, []);
|
|
}
|
|
// Get all users typing (in all private conversations with current user)
|
|
var all_typing_users = [];
|
|
users_currently_typing.each(function (users_typing) {
|
|
all_typing_users = all_typing_users.concat(users_typing);
|
|
});
|
|
return all_typing_users;
|
|
}
|
|
|
|
function render_notifications_for_narrow() {
|
|
var user_ids = get_users_typing_for_narrow();
|
|
var users_typing = user_ids.map(people.get_person_from_user_id);
|
|
if (users_typing.length === 0) {
|
|
$('#typing_notifications').hide();
|
|
} else {
|
|
$('#typing_notifications').html(templates.render('typing_notifications', {users: users_typing}));
|
|
$('#typing_notifications').show();
|
|
}
|
|
}
|
|
|
|
$(document).on('narrow_activated.zulip', render_notifications_for_narrow);
|
|
$(document).on('narrow_deactivated.zulip', render_notifications_for_narrow);
|
|
|
|
exports.hide_notification = function (event) {
|
|
var recipients = event.recipients.map(function (user) {
|
|
return user.user_id;
|
|
});
|
|
recipients.sort();
|
|
|
|
// If there's an existing timer for this typing notifications
|
|
// thread, clear it.
|
|
if (stop_typing_timers[recipients] !== undefined) {
|
|
clearTimeout(stop_typing_timers[recipients]);
|
|
stop_typing_timers[recipients] = undefined;
|
|
}
|
|
|
|
var users_typing = users_currently_typing.get(recipients);
|
|
var i = users_typing.indexOf(event.sender.user_id);
|
|
if (i !== -1) {
|
|
users_typing.splice(i);
|
|
}
|
|
render_notifications_for_narrow();
|
|
};
|
|
|
|
exports.display_notification = function (event) {
|
|
var recipients = event.recipients.map(function (user) {
|
|
return user.user_id;
|
|
});
|
|
recipients.sort();
|
|
|
|
event.sender.name = people.get_person_from_user_id(event.sender.user_id).full_name;
|
|
|
|
var users_typing = users_currently_typing.setdefault(recipients, []);
|
|
var i = users_typing.indexOf(event.sender.user_id);
|
|
if (i === -1) {
|
|
users_typing.push(event.sender.user_id);
|
|
}
|
|
|
|
render_notifications_for_narrow();
|
|
// If there's an existing timeout for this typing notifications
|
|
// thread, clear it.
|
|
if (stop_typing_timers[recipients] !== undefined) {
|
|
clearTimeout(stop_typing_timers[recipients]);
|
|
}
|
|
// Set a time to expire the data if the sender stops transmitting
|
|
stop_typing_timers[recipients] = setTimeout(function () {
|
|
exports.hide_notification(event);
|
|
}, TYPING_STARTED_EXPIRY_PERIOD);
|
|
};
|
|
|
|
return exports;
|
|
}());
|
|
|
|
if (typeof module !== 'undefined') {
|
|
module.exports = typing;
|
|
}
|