mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 06:53:25 +00:00
Support locally echoing messages when sending
(imported from commit 00b5c5f9b933d119553c32cadff0f17b9f7c2879)
This commit is contained in:
@@ -290,12 +290,14 @@ function create_message_object() {
|
|||||||
|
|
||||||
var content = make_uploads_relative(compose.message_content());
|
var content = make_uploads_relative(compose.message_content());
|
||||||
|
|
||||||
|
// Changes here must also be kept in sync with echo.try_deliver_locally
|
||||||
var message = {client: client(),
|
var message = {client: client(),
|
||||||
type: compose.composing(),
|
type: compose.composing(),
|
||||||
subject: subject,
|
subject: subject,
|
||||||
stream: compose.stream_name(),
|
stream: compose.stream_name(),
|
||||||
private_message_recipient: compose.recipient(),
|
private_message_recipient: compose.recipient(),
|
||||||
content: content};
|
content: content,
|
||||||
|
queue_id: page_params.event_queue_id};
|
||||||
|
|
||||||
if (message.type === "private") {
|
if (message.type === "private") {
|
||||||
// TODO: this should be collapsed with the code in composebox_typeahead.js
|
// TODO: this should be collapsed with the code in composebox_typeahead.js
|
||||||
@@ -359,7 +361,7 @@ function compose_error(error_text, bad_input) {
|
|||||||
|
|
||||||
var send_options;
|
var send_options;
|
||||||
|
|
||||||
function send_message_ajax(request, success) {
|
function send_message_ajax(request, success, error) {
|
||||||
channel.post({
|
channel.post({
|
||||||
url: '/json/send_message',
|
url: '/json/send_message',
|
||||||
data: request,
|
data: request,
|
||||||
@@ -370,8 +372,9 @@ function send_message_ajax(request, success) {
|
|||||||
reload.initiate({immediate: true, send_after_reload: true});
|
reload.initiate({immediate: true, send_after_reload: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = util.xhr_error_message("Error sending message", xhr);
|
var response = util.xhr_error_message("Error sending message", xhr);
|
||||||
compose_error(response, $('#new_message_content'));
|
error(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -392,13 +395,13 @@ if (feature_flags.use_socket) {
|
|||||||
// For debugging. The socket will eventually move out of this file anyway.
|
// For debugging. The socket will eventually move out of this file anyway.
|
||||||
exports._socket = socket;
|
exports._socket = socket;
|
||||||
|
|
||||||
function send_message_socket(request, success) {
|
function send_message_socket(request, success, error) {
|
||||||
socket.send(request, success, function (type, resp) {
|
socket.send(request, success, function (type, resp) {
|
||||||
var err_msg = "Error sending message";
|
var err_msg = "Error sending message";
|
||||||
if (type === 'response') {
|
if (type === 'response') {
|
||||||
err_msg += ": " + resp.msg;
|
err_msg += ": " + resp.msg;
|
||||||
}
|
}
|
||||||
compose_error(err_msg, $('#new_message_content'));
|
error(err_msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,6 +462,33 @@ function clear_compose_box() {
|
|||||||
ui.resize_bottom_whitespace();
|
ui.resize_bottom_whitespace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.send_message_success = function (local_id, message_id, start_time) {
|
||||||
|
if (! feature_flags.local_echo) {
|
||||||
|
clear_compose_box();
|
||||||
|
}
|
||||||
|
|
||||||
|
process_send_time(message_id, start_time);
|
||||||
|
|
||||||
|
if (feature_flags.local_echo) {
|
||||||
|
echo.reify_message_id(local_id, message_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
if (exports.send_times_data[message_id].received === undefined) {
|
||||||
|
blueslip.error("Restarting get_updates due to delayed receipt of sent message " + message_id);
|
||||||
|
restart_get_updates();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.transmit_message = function (request, success, error) {
|
||||||
|
if (feature_flags.use_socket) {
|
||||||
|
send_message_socket(request, success, error);
|
||||||
|
} else {
|
||||||
|
send_message_ajax(request, success, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function send_message(request) {
|
function send_message(request) {
|
||||||
if (request === undefined) {
|
if (request === undefined) {
|
||||||
request = create_message_object();
|
request = create_message_object();
|
||||||
@@ -472,27 +502,32 @@ function send_message(request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var start_time = new Date();
|
var start_time = new Date();
|
||||||
function success(data) {
|
var local_id;
|
||||||
var message_id = data.id;
|
if (feature_flags.local_echo) {
|
||||||
process_send_time(message_id, start_time);
|
local_id = echo.try_deliver_locally(request);
|
||||||
|
if (local_id !== undefined) {
|
||||||
|
// We delivered this message locally
|
||||||
|
request.local_id = local_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! feature_flags.local_echo) {
|
function success(data) {
|
||||||
clear_compose_box();
|
exports.send_message_success(local_id, data.id, start_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(response) {
|
||||||
|
// If we're not local echo'ing messages, or if this message was not
|
||||||
|
// locally echoed, show error in compose box
|
||||||
|
if (!feature_flags.local_echo || request.local_id === undefined) {
|
||||||
|
compose_error(response, $('#new_message_content'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(function () {
|
echo.message_send_error(local_id);
|
||||||
if (exports.send_times_data[message_id].received === undefined) {
|
|
||||||
blueslip.error("Restarting get_updates due to delayed receipt of sent message " + message_id);
|
|
||||||
restart_get_updates();
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feature_flags.use_socket) {
|
exports.transmit_message(request, success, error);
|
||||||
send_message_socket(request, success);
|
|
||||||
} else {
|
|
||||||
send_message_ajax(request, success);
|
|
||||||
}
|
|
||||||
if (get_updates_xhr === undefined && get_updates_timeout === undefined) {
|
if (get_updates_xhr === undefined && get_updates_timeout === undefined) {
|
||||||
restart_get_updates({dont_block: true});
|
restart_get_updates({dont_block: true});
|
||||||
blueslip.error("Restarting get_updates because it was not running during send");
|
blueslip.error("Restarting get_updates because it was not running during send");
|
||||||
@@ -926,6 +961,14 @@ $(function () {
|
|||||||
compose.start("stream", {});
|
compose.start("stream", {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(document).on('message_id_changed', function (event) {
|
||||||
|
if (exports.send_times_data[event.old_id] !== undefined) {
|
||||||
|
var value = exports.send_times_data[event.old_id];
|
||||||
|
delete exports.send_times_data[event.old_id];
|
||||||
|
exports.send_times_data[event.new_id] = _.extend({}, exports.send_times_data[event.old_id], value);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return exports;
|
return exports;
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ var echo = (function () {
|
|||||||
|
|
||||||
var exports = {};
|
var exports = {};
|
||||||
|
|
||||||
|
var waiting_for_id = {};
|
||||||
|
var waiting_for_ack = {};
|
||||||
|
|
||||||
// Regexes that match some of our common bugdown markup
|
// Regexes that match some of our common bugdown markup
|
||||||
var bugdown_re = [
|
var bugdown_re = [
|
||||||
/(?::[^:\s]+:)(?!\w)/, // Emoji
|
/(?::[^:\s]+:)(?!\w)/, // Emoji
|
||||||
@@ -27,6 +30,222 @@ exports.contains_bugdown = function contains_bugdown(content) {
|
|||||||
return markedup !== undefined;
|
return markedup !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.apply_markdown = function apply_markdown(content) {
|
||||||
|
return marked(content).trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
function truncate_precision(float) {
|
||||||
|
return parseFloat(float.toFixed(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.try_deliver_locally = function try_deliver_locally(message_request) {
|
||||||
|
var local_id_increment = 0.01;
|
||||||
|
var next_local_id = truncate_precision(all_msg_list.last().id + local_id_increment);
|
||||||
|
|
||||||
|
if (next_local_id % 1 === 0) {
|
||||||
|
blueslip.error("Incremented local id to next integer---100 local messages queued");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exports.contains_bugdown(message_request.content)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shallow clone of message request object that is turned into something suitable
|
||||||
|
// for zulip.js:add_message
|
||||||
|
// Keep this in sync with changes to compose.create_message_object
|
||||||
|
var message = $.extend({}, message_request);
|
||||||
|
message.raw_content = message.content;
|
||||||
|
// NOTE: This will parse synchronously. We're not using the async pipeline
|
||||||
|
message.content = exports.apply_markdown(message.content);
|
||||||
|
message.content_type = 'text/html';
|
||||||
|
// Locally delivered messages cannot be unread (since we sent them), nor
|
||||||
|
// can they alert the user
|
||||||
|
message.flags = ["read"];
|
||||||
|
message.sender_email = page_params.email;
|
||||||
|
message.sender_full_name = page_params.fullname;
|
||||||
|
message.avatar_url = page_params.avatar_url;
|
||||||
|
message.timestamp = new XDate().getTime() / 1000;
|
||||||
|
message.local_id = next_local_id;
|
||||||
|
message.id = message.local_id;
|
||||||
|
|
||||||
|
waiting_for_id[message.local_id] = message;
|
||||||
|
waiting_for_ack[message.local_id] = message;
|
||||||
|
|
||||||
|
if (message.type === 'stream') {
|
||||||
|
message.display_recipient = message.stream;
|
||||||
|
} else {
|
||||||
|
// Build a display recipient with the full names of each recipient
|
||||||
|
var emails = message_request.private_message_recipient.split(',');
|
||||||
|
message.display_recipient = _.map(emails, function (email) {
|
||||||
|
email = email.trim();
|
||||||
|
var person = people_dict.get(email);
|
||||||
|
if (person !== undefined) {
|
||||||
|
return person;
|
||||||
|
}
|
||||||
|
return {email: email, full_name: email};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_new_messages([message]);
|
||||||
|
|
||||||
|
blueslip.debug("Generated local id " + message.local_id);
|
||||||
|
return message.local_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.reify_message_id = function reify_message_id(local_id, server_id) {
|
||||||
|
var message = waiting_for_id[local_id];
|
||||||
|
delete waiting_for_id[local_id];
|
||||||
|
|
||||||
|
// reify_message_id is called both on receiving a self-sent message
|
||||||
|
// from the server, and on receiving the response to the send request
|
||||||
|
// Reification is only needed the first time the server id is found
|
||||||
|
if (message === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blueslip.debug("Reifying ID: " + local_id + " TO " + server_id);
|
||||||
|
message.id = server_id;
|
||||||
|
delete message.local_id;
|
||||||
|
|
||||||
|
// We have the real message ID for this message
|
||||||
|
$(document).trigger($.Event('message_id_changed', {old_id: local_id, new_id: server_id}));
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.process_from_server = function process_from_server(messages) {
|
||||||
|
var updated = false;
|
||||||
|
var locally_processed_ids = [];
|
||||||
|
messages = _.filter(messages, function (message) {
|
||||||
|
// In case we get the sent message before we get the send ACK, reify here
|
||||||
|
exports.reify_message_id(message.local_id, message.id);
|
||||||
|
|
||||||
|
var client_message = waiting_for_ack[message.local_id];
|
||||||
|
if (client_message !== undefined) {
|
||||||
|
if (client_message.content !== message.content) {
|
||||||
|
client_message.content = message.content;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
// If a PM was sent to an out-of-realm address,
|
||||||
|
// we didn't have the full person object originally,
|
||||||
|
// so we might have to update the recipient bar and
|
||||||
|
// internal data structures
|
||||||
|
if (client_message.type === 'private') {
|
||||||
|
var reply_to = get_private_message_recipient(message, 'full_name', 'email');
|
||||||
|
if (client_message.display_reply_to !== reply_to) {
|
||||||
|
client_message.display_reply_to = reply_to;
|
||||||
|
_.each(message.display_recipient, function (person) {
|
||||||
|
if (people_dict.get(person.email).full_name !== person.full_name) {
|
||||||
|
reify_person(person);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locally_processed_ids.push(client_message.id);
|
||||||
|
delete waiting_for_ack[client_message.id];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
// TODO just rerender the message, not the whole list
|
||||||
|
home_msg_list.rerender();
|
||||||
|
if (current_msg_list === narrowed_msg_list) {
|
||||||
|
narrowed_msg_list.rerender();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_.each(locally_processed_ids, function (id) {
|
||||||
|
ui.show_local_message_arrived(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.message_send_error = function message_send_error(local_id) {
|
||||||
|
// Error sending message, show inline
|
||||||
|
all_msg_list.get(local_id).failed_request = true;
|
||||||
|
ui.show_message_failed(local_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
function resend_message(message) {
|
||||||
|
message.content = message.raw_content;
|
||||||
|
compose.transmit_message(message, function success(data) {
|
||||||
|
var message_id = data.id;
|
||||||
|
var local_id = data.local_id;
|
||||||
|
|
||||||
|
exports.reify_message_id(local_id, message_id);
|
||||||
|
|
||||||
|
// Resend succeeded, so mark as no longer failed
|
||||||
|
all_msg_list.get(message_id).failed_request = false;
|
||||||
|
ui.show_failed_message_success(message_id);
|
||||||
|
}, function error() {
|
||||||
|
blueslip.log("Manual resend of message failed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function abort_message(message) {
|
||||||
|
// Remove in all lists in which it exists
|
||||||
|
_.each([all_msg_list, home_msg_list, current_msg_list], function (msg_list) {
|
||||||
|
msg_list.remove_and_rerender([message]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
function disable_markdown_regex(rules, name) {
|
||||||
|
rules[name] = {exec: function (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the marked markdown parser for our usage
|
||||||
|
var r = new marked.Renderer();
|
||||||
|
|
||||||
|
// Disable ordered lists
|
||||||
|
// We used GFM + tables, so replace the list start regex for that ruleset
|
||||||
|
// We remove the |[\d+]\. that matches the numbering in a numbered list
|
||||||
|
marked.Lexer.rules.tables.list = /^( *)((?:\*)) [\s\S]+?(?:\n+(?=(?: *[\-*_]){3,} *(?:\n+|$))|\n{2,}(?! )(?!\1(?:\*) )\n*|\s*$)/;
|
||||||
|
// marked.Lexer.rules.tables
|
||||||
|
// Disable headings
|
||||||
|
disable_markdown_regex(marked.Lexer.rules.tables, 'heading');
|
||||||
|
disable_markdown_regex(marked.Lexer.rules.tables, 'lheading');
|
||||||
|
|
||||||
|
// Disable __strong__, all <em>
|
||||||
|
marked.InlineLexer.rules.breaks.strong = /^\*\*([\s\S]+?)\*\*(?!\*)/;
|
||||||
|
disable_markdown_regex(marked.InlineLexer.rules.breaks, 'em');
|
||||||
|
disable_markdown_regex(marked.InlineLexer.rules.breaks, 'del');
|
||||||
|
|
||||||
|
marked.setOptions({
|
||||||
|
gfm: true,
|
||||||
|
tables: true,
|
||||||
|
breaks: true,
|
||||||
|
pedantic: false,
|
||||||
|
sanitize: true,
|
||||||
|
smartLists: true,
|
||||||
|
smartypants: false,
|
||||||
|
renderer: r
|
||||||
|
});
|
||||||
|
|
||||||
|
function on_failed_action(action, callback) {
|
||||||
|
$("#main_div").on("click", "." + action + "-failed-message", function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
popovers.hide_all();
|
||||||
|
var message_id = rows.id($(this).closest(".message_row"));
|
||||||
|
// Message should be waiting for ack and only have a local id,
|
||||||
|
// otherwise send would not have failed
|
||||||
|
var message = waiting_for_ack[message_id];
|
||||||
|
if (message === undefined) {
|
||||||
|
blueslip.warning("Got resend or retry on failure request but did not find message in ack list " + message_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
on_failed_action('remove', abort_message);
|
||||||
|
on_failed_action('refresh', resend_message);
|
||||||
|
});
|
||||||
|
|
||||||
return exports;
|
return exports;
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ exports.left_side_userlist = page_params.staging || _.contains(['customer7.inval
|
|||||||
exports.show_autoscroll_forever_option = page_params.show_autoscroll_forever_option;
|
exports.show_autoscroll_forever_option = page_params.show_autoscroll_forever_option;
|
||||||
|
|
||||||
// Still very beta:
|
// Still very beta:
|
||||||
|
exports.local_echo = page_params.staging;
|
||||||
|
|
||||||
exports.full_width = false; //page_params.staging;
|
exports.full_width = false; //page_params.staging;
|
||||||
exports.local_echo = page_params.staging;
|
exports.local_echo = page_params.staging;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ function MessageList(table_name, filter, opts) {
|
|||||||
|
|
||||||
this.num_appends = 0;
|
this.num_appends = 0;
|
||||||
this.min_id_exempted_from_summaries = -1;
|
this.min_id_exempted_from_summaries = -1;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,6 +515,64 @@ MessageList.prototype = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.view.rerender_the_whole_thing();
|
this.view.rerender_the_whole_thing();
|
||||||
|
},
|
||||||
|
|
||||||
|
change_message_id: function MessageList_change_message_id(old_id, new_id) {
|
||||||
|
// Update our local cache that uses the old id to the new id
|
||||||
|
function message_sort_func(a, b) {return a.id - b.id;}
|
||||||
|
|
||||||
|
function is_local_only(message) {
|
||||||
|
return message.id % 1 !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function next_nonlocal_message(item_list, start_index, op) {
|
||||||
|
var cur_idx = start_index;
|
||||||
|
do {
|
||||||
|
cur_idx = op(cur_idx);
|
||||||
|
} while(item_list[cur_idx] !== undefined && is_local_only(item_list[cur_idx]));
|
||||||
|
return item_list[cur_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._hash.hasOwnProperty(old_id)) {
|
||||||
|
var value = this._hash[old_id];
|
||||||
|
delete this._hash[old_id];
|
||||||
|
this._hash[new_id] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._selected_id === old_id) {
|
||||||
|
this._selected_id = new_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.min_id_exempted_from_summaries === old_id) {
|
||||||
|
this.min_id_exempted_from_summaries = new_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this message is now out of order, re-order and re-render
|
||||||
|
var self = this;
|
||||||
|
setTimeout(function () {
|
||||||
|
var current_message = self._hash[new_id];
|
||||||
|
var index = self._items.indexOf(current_message);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
if ( !self.muting_enabled && current_msg_list === self) {
|
||||||
|
blueslip.error("Trying to re-order message but can't find message with new_id in _items!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = next_nonlocal_message(self._items, index, function (idx) { return idx + 1; });
|
||||||
|
var prev = next_nonlocal_message(self._items, index, function (idx) { return idx - 1; });
|
||||||
|
|
||||||
|
if ((next !== undefined && current_message.id > next.id) ||
|
||||||
|
(prev !== undefined && current_message.id < prev.id)) {
|
||||||
|
blueslip.debug("Changed message ID from server caused out-of-order list, reordering");
|
||||||
|
self._items.sort(message_sort_func);
|
||||||
|
if (self.muting_enabled) {
|
||||||
|
self._all_items.sort(message_sort_func);
|
||||||
|
}
|
||||||
|
self.view.rerender_the_whole_thing();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -695,8 +695,32 @@ MessageListView.prototype = {
|
|||||||
|
|
||||||
get_message: function MessageListView_get_message(id) {
|
get_message: function MessageListView_get_message(id) {
|
||||||
return this.list.get(id);
|
return this.list.get(id);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
change_message_id: function MessageListView_change_message_id(old_id, new_id) {
|
||||||
|
if (this._rows[old_id] !== undefined) {
|
||||||
|
var row = this._rows[old_id];
|
||||||
|
delete this._rows[old_id];
|
||||||
|
|
||||||
|
var prev_recipient_row = $(row).prev('.recipient_row');
|
||||||
|
if (prev_recipient_row.length > 0 &&
|
||||||
|
parseFloat(prev_recipient_row.attr('zid')) === old_id) {
|
||||||
|
prev_recipient_row.attr('zid', new_id);
|
||||||
|
|
||||||
|
var messages = prev_recipient_row.attr('data-messages').split();
|
||||||
|
var fixed_messages = _.map(messages, function (msgid) {
|
||||||
|
if (parseFloat(msgid) === old_id) {
|
||||||
|
return String(new_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
prev_recipient_row.attr('data-messages', fixed_messages.join(" "));
|
||||||
|
|
||||||
|
}
|
||||||
|
row.setAttribute('zid', new_id);
|
||||||
|
row.setAttribute('id', this.table_name + new_id);
|
||||||
|
this._rows[new_id] = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ exports.activate = function (operators, opts) {
|
|||||||
|
|
||||||
var defer_selecting_closest = narrowed_msg_list.empty();
|
var defer_selecting_closest = narrowed_msg_list.empty();
|
||||||
load_old_messages({
|
load_old_messages({
|
||||||
anchor: then_select_id,
|
anchor: then_select_id.toFixed(),
|
||||||
num_before: 50,
|
num_before: 50,
|
||||||
num_after: 50,
|
num_after: 50,
|
||||||
msg_list: narrowed_msg_list,
|
msg_list: narrowed_msg_list,
|
||||||
|
|||||||
@@ -700,26 +700,33 @@ function sync_message_star(message, starred) {
|
|||||||
sync_message_flag([message], "starred", starred);
|
sync_message_flag([message], "starred", starred);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function update_message_in_all_views(message_id, callback) {
|
||||||
|
_.each([all_msg_list, home_msg_list, narrowed_msg_list], function (list) {
|
||||||
|
if (list === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var row = list.get_row(message_id);
|
||||||
|
if (row === undefined) {
|
||||||
|
// The row may not exist, e.g. if you do an action on a message in
|
||||||
|
// a narrowed view
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
exports.update_starred = function (message_id, starred) {
|
exports.update_starred = function (message_id, starred) {
|
||||||
|
// Update the message object pointed to by the various message
|
||||||
|
// lists.
|
||||||
|
var message = current_msg_list.get(message_id);
|
||||||
|
|
||||||
|
mark_message_as_read(message);
|
||||||
|
|
||||||
|
message.starred = message.starred !== true;
|
||||||
|
|
||||||
// Avoid a full re-render, but update the star in each message
|
// Avoid a full re-render, but update the star in each message
|
||||||
// table in which it is visible.
|
// table in which it is visible.
|
||||||
_.each([all_msg_list, home_msg_list, narrowed_msg_list], function (msg_list) {
|
update_message_in_all_views(message_id, function update_row(row) {
|
||||||
if (msg_list === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = msg_list.get(message_id);
|
|
||||||
if (message === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
message.starred = starred;
|
|
||||||
|
|
||||||
var row = msg_list.get_row(message.id);
|
|
||||||
if (row === undefined) {
|
|
||||||
// The row may not exist, e.g. if you star a message in the all
|
|
||||||
// messages table from a stream that isn't in your home view.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var elt = row.find(".message_star");
|
var elt = row.find(".message_star");
|
||||||
if (starred) {
|
if (starred) {
|
||||||
elt.addClass("icon-vector-star").removeClass("icon-vector-star-empty").removeClass("empty-star");
|
elt.addClass("icon-vector-star").removeClass("icon-vector-star-empty").removeClass("empty-star");
|
||||||
@@ -740,6 +747,35 @@ function toggle_star(message_id) {
|
|||||||
sync_message_star(message, message.starred);
|
sync_message_star(message, message.starred);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var local_messages_to_show = [];
|
||||||
|
var show_message_timestamps = _.throttle(function () {
|
||||||
|
_.each(local_messages_to_show, function (message_id) {
|
||||||
|
update_message_in_all_views(message_id, function update_row(row) {
|
||||||
|
row.find('.message_time').toggleClass('notvisible', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
local_messages_to_show = [];
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
exports.show_local_message_arrived = function (message_id) {
|
||||||
|
local_messages_to_show.push(message_id);
|
||||||
|
show_message_timestamps();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.show_message_failed = function (message_id) {
|
||||||
|
// Failed to send message, so display inline retry/cancel
|
||||||
|
update_message_in_all_views(message_id, function update_row(row) {
|
||||||
|
row.find('.message_failed').toggleClass('notvisible', false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.show_failed_message_success = function (message_id) {
|
||||||
|
// Previously failed message succeeded
|
||||||
|
update_message_in_all_views(message_id, function update_row(row) {
|
||||||
|
row.find('.message_failed').toggleClass('notvisible', true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
exports.small_avatar_url = function (message) {
|
exports.small_avatar_url = function (message) {
|
||||||
// Try to call this function in all places where we need 25px
|
// Try to call this function in all places where we need 25px
|
||||||
// avatar images, so that the browser can help
|
// avatar images, so that the browser can help
|
||||||
|
|||||||
@@ -320,18 +320,18 @@ function message_range(msg_list, start, end) {
|
|||||||
|
|
||||||
function batched_flag_updater(flag, op) {
|
function batched_flag_updater(flag, op) {
|
||||||
var queue = [];
|
var queue = [];
|
||||||
|
var on_success;
|
||||||
function on_success(data, status, jqXHR) {
|
|
||||||
queue = _.filter(queue, function (message) {
|
|
||||||
return data.messages.indexOf(message) === -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function server_request() {
|
function server_request() {
|
||||||
|
// Wait for server IDs before sending flags
|
||||||
|
var real_msgs = _.filter(queue, function (msg) {
|
||||||
|
return msg.local_id === undefined;
|
||||||
|
});
|
||||||
|
|
||||||
channel.post({
|
channel.post({
|
||||||
url: '/json/update_message_flags',
|
url: '/json/update_message_flags',
|
||||||
idempotent: true,
|
idempotent: true,
|
||||||
data: {messages: JSON.stringify(queue),
|
data: {messages: JSON.stringify(real_msgs),
|
||||||
op: op,
|
op: op,
|
||||||
flag: flag},
|
flag: flag},
|
||||||
success: on_success
|
success: on_success
|
||||||
@@ -340,6 +340,20 @@ function batched_flag_updater(flag, op) {
|
|||||||
|
|
||||||
var start = _.debounce(server_request, 1000);
|
var start = _.debounce(server_request, 1000);
|
||||||
|
|
||||||
|
on_success = function on_success(data, status, jqXHR) {
|
||||||
|
if (data === undefined || data.messages === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue = _.filter(queue, function (message) {
|
||||||
|
return data.messages.indexOf(message) === -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (queue.length > 0) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function add(message) {
|
function add(message) {
|
||||||
if (message.flags === undefined) {
|
if (message.flags === undefined) {
|
||||||
message.flags = [];
|
message.flags = [];
|
||||||
@@ -900,6 +914,9 @@ function get_updates_success(data) {
|
|||||||
case 'message':
|
case 'message':
|
||||||
var msg = event.message;
|
var msg = event.message;
|
||||||
msg.flags = event.flags;
|
msg.flags = event.flags;
|
||||||
|
if (event.local_message_id !== undefined) {
|
||||||
|
msg.local_id = event.local_message_id;
|
||||||
|
}
|
||||||
messages.push(msg);
|
messages.push(msg);
|
||||||
break;
|
break;
|
||||||
case 'pointer':
|
case 'pointer':
|
||||||
@@ -995,6 +1012,7 @@ function get_updates_success(data) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (messages.length !== 0) {
|
if (messages.length !== 0) {
|
||||||
|
messages = echo.process_from_server(messages);
|
||||||
insert_new_messages(messages);
|
insert_new_messages(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1356,8 +1374,11 @@ function main() {
|
|||||||
if (event.id === -1) {
|
if (event.id === -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Additionally, don't advance the pointer server-side
|
||||||
|
// if the selected message is local-only
|
||||||
if (event.msg_list === home_msg_list && page_params.narrow_stream === undefined) {
|
if (event.msg_list === home_msg_list && page_params.narrow_stream === undefined) {
|
||||||
if (event.id > furthest_read) {
|
if (event.id > furthest_read &&
|
||||||
|
home_msg_list.get(event.id).local_id === undefined) {
|
||||||
furthest_read = event.id;
|
furthest_read = event.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1429,6 +1450,36 @@ function main() {
|
|||||||
} else {
|
} else {
|
||||||
get_updates();
|
get_updates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(document).on('message_id_changed', function (event) {
|
||||||
|
var old_id = event.old_id, new_id = event.new_id;
|
||||||
|
if (furthest_read === old_id) {
|
||||||
|
furthest_read = new_id;
|
||||||
|
}
|
||||||
|
if (get_updates_params.pointer === old_id) {
|
||||||
|
get_updates_params.pointer = new_id;
|
||||||
|
}
|
||||||
|
if (msg_metadata_cache[old_id]) {
|
||||||
|
msg_metadata_cache[new_id] = msg_metadata_cache[old_id];
|
||||||
|
delete msg_metadata_cache[old_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handler cannot be in the MessageList constructor, which is the logical place
|
||||||
|
// If it's there, the event handler creates a closure with a reference to the message
|
||||||
|
// list itself. When narrowing, the old narrow message list is discarded and a new one
|
||||||
|
// created, but due to the closure, the old list is not garbage collected. This also leads
|
||||||
|
// to the old list receiving the change id events, and throwing errors as it does not
|
||||||
|
// have the messages that you would expect in its internal data structures.
|
||||||
|
_.each([all_msg_list, home_msg_list, narrowed_msg_list], function (msg_list) {
|
||||||
|
if (msg_list !== undefined) {
|
||||||
|
msg_list.change_message_id(old_id, new_id);
|
||||||
|
|
||||||
|
if (msg_list.view !== undefined) {
|
||||||
|
msg_list.view.change_message_id(old_id, new_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function install_main_scroll_handler() {
|
function install_main_scroll_handler() {
|
||||||
|
|||||||
@@ -1066,6 +1066,24 @@ just a temporary hack.
|
|||||||
color: #0088CC;
|
color: #0088CC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message_failed,
|
||||||
|
.message_local {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_failed {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red;
|
||||||
|
padding: 0px 1px 0px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message_failed i {
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
a.message_label_clickable:hover {
|
a.message_label_clickable:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #08C;
|
color: #08C;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/include_recipient}}
|
{{/include_recipient}}
|
||||||
<div zid="{{id}}" id="{{dom_id}}"
|
<div zid="{{id}}" id="{{dom_id}}"
|
||||||
class="message_row{{^is_stream}} private-message{{/is_stream}}{{#include_sender}} include-sender{{/include_sender}}{{#contains_mention}} mention{{/contains_mention}}{{#include_footer}} last_message{{/include_footer}}{{#unread}} unread{{/unread}} selectable_row">
|
class="message_row{{^is_stream}} private-message{{/is_stream}}{{#include_sender}} include-sender{{/include_sender}}{{#contains_mention}} mention{{/contains_mention}}{{#include_footer}} last_message{{/include_footer}}{{#unread}} unread{{/unread}} {{#if local_id}}local{{/if}} selectable_row">
|
||||||
<div class="unread_marker"></div>
|
<div class="unread_marker"></div>
|
||||||
<div class="messagebox{{^include_sender}} prev_is_same_sender{{/include_sender}}{{^is_stream}} private-message{{/is_stream}}">
|
<div class="messagebox{{^include_sender}} prev_is_same_sender{{/include_sender}}{{^is_stream}} private-message{{/is_stream}}">
|
||||||
<div class="messagebox-border" style="box-shadow:inset 9px 0px {{background_color}};">
|
<div class="messagebox-border" style="box-shadow:inset 9px 0px {{background_color}};">
|
||||||
@@ -194,10 +194,13 @@
|
|||||||
<span class="message_star {{#if starred}}icon-vector-star{{else}}icon-vector-star-empty empty-star{{/if}}"
|
<span class="message_star {{#if starred}}icon-vector-star{{else}}icon-vector-star-empty empty-star{{/if}}"
|
||||||
title="{{#if starred}}Unstar{{else}}Star{{/if}} this message"></span>
|
title="{{#if starred}}Unstar{{else}}Star{{/if}} this message"></span>
|
||||||
</div>
|
</div>
|
||||||
<span class="message_time">{{timestr}}</span>
|
<span class="message_time {{#if local_id}}notvisible{{/if}}">{{timestr}}</span>
|
||||||
<div class="info actions_hover">
|
<div class="info actions_hover">
|
||||||
<i class="icon-vector-chevron-down"></i>
|
<i class="icon-vector-chevron-down"></i>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="message_failed {{#unless failed_request}}notvisible{{/unless}}">
|
||||||
|
<span class="failed_text">Not delivered </span><i class="icon-vector-refresh refresh-failed-message"></i><i class="icon-vector-remove-sign remove-failed-message"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="message_content">{{#unless status_message}}{{#if ../../../../use_match_properties}}{{{match_content}}}{{else}}{{{content}}}{{/if}}{{/unless}}</div>
|
<div class="message_content">{{#unless status_message}}{{#if ../../../../use_match_properties}}{{{match_content}}}{{else}}{{{content}}}{{/if}}{{/unless}}</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
var globals =
|
var globals =
|
||||||
// Third-party libraries
|
// Third-party libraries
|
||||||
' $ _ jQuery Spinner Handlebars XDate zxcvbn Intl mixpanel Notification'
|
' $ _ jQuery Spinner Handlebars XDate zxcvbn Intl mixpanel Notification'
|
||||||
+ ' LazyLoad Dropbox SockJS'
|
+ ' LazyLoad Dropbox SockJS marked'
|
||||||
|
|
||||||
// Node-based unit tests
|
// Node-based unit tests
|
||||||
+ ' module'
|
+ ' module'
|
||||||
@@ -41,6 +41,9 @@ var globals =
|
|||||||
// alert_words.js
|
// alert_words.js
|
||||||
+ ' alert_words'
|
+ ' alert_words'
|
||||||
|
|
||||||
|
// echo.js
|
||||||
|
+ ' echo'
|
||||||
|
|
||||||
// zulip.js
|
// zulip.js
|
||||||
+ ' all_msg_list home_msg_list narrowed_msg_list current_msg_list get_updates_params'
|
+ ' all_msg_list home_msg_list narrowed_msg_list current_msg_list get_updates_params'
|
||||||
+ ' add_messages'
|
+ ' add_messages'
|
||||||
|
|||||||
Reference in New Issue
Block a user