mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	The changes made in this commit are as follows:
* We remove the now unused `ui.find_message` which was added
  in commit 1666403850.
* We change the function paramter to now accept message ids
  instead of messages to eliminate redundant message ids to
  message convertion as only the id is required.
* The remove method in MessageListData did not remove the
  messages from the hash, it removed only from the items,
  this fixes it.
* This commit also fixes a bug where messages are not added
  to the current message list if an event is recieved where
  messages are moved to this current narrow.
  Only the message removal logic was present, which has been
  refactored in this commit.
		
	
		
			
				
	
	
		
			395 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
 | 
						|
const people = require("./people");
 | 
						|
const util = require("./util");
 | 
						|
// Docs: https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html
 | 
						|
 | 
						|
const waiting_for_id = new Map();
 | 
						|
let waiting_for_ack = new Map();
 | 
						|
 | 
						|
function failed_message_success(message_id) {
 | 
						|
    message_store.get(message_id).failed_request = false;
 | 
						|
    ui.show_failed_message_success(message_id);
 | 
						|
}
 | 
						|
 | 
						|
function resend_message(message, row) {
 | 
						|
    message.content = message.raw_content;
 | 
						|
    const retry_spinner = row.find(".refresh-failed-message");
 | 
						|
    retry_spinner.toggleClass("rotating", true);
 | 
						|
 | 
						|
    // Always re-set queue_id if we've gotten a new one
 | 
						|
    // since the time when the message object was initially created
 | 
						|
    message.queue_id = page_params.queue_id;
 | 
						|
 | 
						|
    const local_id = message.local_id;
 | 
						|
 | 
						|
    function on_success(data) {
 | 
						|
        const message_id = data.id;
 | 
						|
        const locally_echoed = true;
 | 
						|
 | 
						|
        retry_spinner.toggleClass("rotating", false);
 | 
						|
 | 
						|
        compose.send_message_success(local_id, message_id, locally_echoed);
 | 
						|
 | 
						|
        // Resend succeeded, so mark as no longer failed
 | 
						|
        failed_message_success(message_id);
 | 
						|
    }
 | 
						|
 | 
						|
    function on_error(response) {
 | 
						|
        exports.message_send_error(message.id, response);
 | 
						|
        setTimeout(() => {
 | 
						|
            retry_spinner.toggleClass("rotating", false);
 | 
						|
        }, 300);
 | 
						|
        blueslip.log("Manual resend of message failed");
 | 
						|
    }
 | 
						|
 | 
						|
    sent_messages.start_resend(local_id);
 | 
						|
    transmit.send_message(message, on_success, on_error);
 | 
						|
}
 | 
						|
 | 
						|
exports.build_display_recipient = function (message) {
 | 
						|
    if (message.type === "stream") {
 | 
						|
        return message.stream;
 | 
						|
    }
 | 
						|
 | 
						|
    // Build a display recipient with the full names of each
 | 
						|
    // recipient.  Note that it's important that use
 | 
						|
    // util.extract_pm_recipients, which filters out any spurious
 | 
						|
    // ", " at the end of the recipient list
 | 
						|
    const emails = util.extract_pm_recipients(message.private_message_recipient);
 | 
						|
 | 
						|
    let sender_in_display_recipients = false;
 | 
						|
    const display_recipient = emails.map((email) => {
 | 
						|
        email = email.trim();
 | 
						|
        const person = people.get_by_email(email);
 | 
						|
        if (person === undefined) {
 | 
						|
            // For unknown users, we return a skeleton object.
 | 
						|
            //
 | 
						|
            // This allows us to support zephyr mirroring situations
 | 
						|
            // where the server might dynamically create users in
 | 
						|
            // response to messages being sent to their email address.
 | 
						|
            //
 | 
						|
            // TODO: It might be cleaner for the webapp for such
 | 
						|
            // dynamic user creation to happen inside a separate API
 | 
						|
            // call when the pill is constructed, and then enforcing
 | 
						|
            // the requirement that we have an actual user object in
 | 
						|
            // `people.js` when sending messages.
 | 
						|
            return {
 | 
						|
                email,
 | 
						|
                full_name: email,
 | 
						|
                unknown_local_echo_user: true,
 | 
						|
            };
 | 
						|
        }
 | 
						|
 | 
						|
        if (person.user_id === message.sender_id) {
 | 
						|
            sender_in_display_recipients = true;
 | 
						|
        }
 | 
						|
 | 
						|
        // NORMAL PATH
 | 
						|
        //
 | 
						|
        // This should match the format of display_recipient
 | 
						|
        // objects generated by the backend code in models.py,
 | 
						|
        // which is why we create a new object with a `.id` field
 | 
						|
        // rather than a `.user_id` field.
 | 
						|
        return {
 | 
						|
            id: person.user_id,
 | 
						|
            email: person.email,
 | 
						|
            full_name: person.full_name,
 | 
						|
        };
 | 
						|
    });
 | 
						|
 | 
						|
    if (!sender_in_display_recipients) {
 | 
						|
        // Ensure that the current user is included in
 | 
						|
        // display_recipient for group PMs.
 | 
						|
        display_recipient.push({
 | 
						|
            id: message.sender_id,
 | 
						|
            email: message.sender_email,
 | 
						|
            full_name: message.sender_full_name,
 | 
						|
        });
 | 
						|
    }
 | 
						|
    return display_recipient;
 | 
						|
};
 | 
						|
 | 
						|
exports.insert_local_message = function (message_request, local_id_float) {
 | 
						|
    // 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
 | 
						|
    const message = {...message_request};
 | 
						|
 | 
						|
    // Locally delivered messages cannot be unread (since we sent them), nor
 | 
						|
    // can they alert the user.
 | 
						|
    message.unread = false;
 | 
						|
 | 
						|
    message.raw_content = message.content;
 | 
						|
 | 
						|
    // NOTE: This will parse synchronously. We're not using the async pipeline
 | 
						|
    markdown.apply_markdown(message);
 | 
						|
 | 
						|
    message.content_type = "text/html";
 | 
						|
    message.sender_email = people.my_current_email();
 | 
						|
    message.sender_full_name = people.my_full_name();
 | 
						|
    message.avatar_url = page_params.avatar_url;
 | 
						|
    message.timestamp = local_message.now();
 | 
						|
    message.local_id = local_id_float.toString();
 | 
						|
    message.locally_echoed = true;
 | 
						|
    message.id = local_id_float;
 | 
						|
    markdown.add_topic_links(message);
 | 
						|
 | 
						|
    waiting_for_id.set(message.local_id, message);
 | 
						|
    waiting_for_ack.set(message.local_id, message);
 | 
						|
 | 
						|
    message.display_recipient = exports.build_display_recipient(message);
 | 
						|
    local_message.insert_message(message);
 | 
						|
    return message;
 | 
						|
};
 | 
						|
 | 
						|
exports.is_slash_command = function (content) {
 | 
						|
    return !content.startsWith("/me") && content.startsWith("/");
 | 
						|
};
 | 
						|
 | 
						|
exports.try_deliver_locally = function (message_request) {
 | 
						|
    if (markdown.contains_backend_only_syntax(message_request.content)) {
 | 
						|
        return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    if (narrow_state.active() && !narrow_state.filter().can_apply_locally(true)) {
 | 
						|
        return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    if (exports.is_slash_command(message_request.content)) {
 | 
						|
        return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!current_msg_list.data.fetch_status.has_found_newest()) {
 | 
						|
        // If the current message list doesn't yet have the latest
 | 
						|
        // messages before the one we just sent, local echo would make
 | 
						|
        // it appear as though there were no messages between what we
 | 
						|
        // have and the new message we just sent, when in fact we're
 | 
						|
        // in the process of fetching those from the server.  In this
 | 
						|
        // case, it's correct to skip local echo; we'll get the
 | 
						|
        // message we just sent placed appropriately when we get it
 | 
						|
        // from either server_events or message_fetch.
 | 
						|
        blueslip.info("Skipping local echo until newest messages get loaded.");
 | 
						|
        return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    const local_id_float = local_message.get_next_id_float();
 | 
						|
 | 
						|
    if (!local_id_float) {
 | 
						|
        // This can happen for legit reasons.
 | 
						|
        return undefined;
 | 
						|
    }
 | 
						|
 | 
						|
    const message = exports.insert_local_message(message_request, local_id_float);
 | 
						|
    return message;
 | 
						|
};
 | 
						|
 | 
						|
exports.edit_locally = function (message, request) {
 | 
						|
    // Responsible for doing the rendering work of locally editing the
 | 
						|
    // content ofa message.  This is used in several code paths:
 | 
						|
    // * Editing a message where a message was locally echoed but
 | 
						|
    //   it got an error back from the server
 | 
						|
    // * Locally echoing any content-only edits to fully sent messages
 | 
						|
    // * Restoring the original content should the server return an
 | 
						|
    //   error after having locally echoed content-only messages.
 | 
						|
    // The details of what should be changed are encoded in the request.
 | 
						|
    const raw_content = request.raw_content;
 | 
						|
    const message_content_edited = raw_content !== undefined && message.raw_content !== raw_content;
 | 
						|
 | 
						|
    if (request.new_topic !== undefined || request.new_stream_id !== undefined) {
 | 
						|
        const new_stream_id = request.new_stream_id;
 | 
						|
        const new_topic = request.new_topic;
 | 
						|
        stream_topic_history.remove_messages({
 | 
						|
            stream_id: message.stream_id,
 | 
						|
            topic_name: message.topic,
 | 
						|
            num_messages: 1,
 | 
						|
            max_removed_msg_id: message.id,
 | 
						|
        });
 | 
						|
 | 
						|
        if (new_stream_id !== undefined) {
 | 
						|
            message.stream_id = new_stream_id;
 | 
						|
        }
 | 
						|
        if (new_topic !== undefined) {
 | 
						|
            message.topic = new_topic;
 | 
						|
        }
 | 
						|
 | 
						|
        stream_topic_history.add_message({
 | 
						|
            stream_id: message.stream_id,
 | 
						|
            topic_name: message.topic,
 | 
						|
            message_id: message.id,
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    if (message_content_edited) {
 | 
						|
        message.raw_content = raw_content;
 | 
						|
        if (request.content !== undefined) {
 | 
						|
            // This happens in the code path where message editing
 | 
						|
            // failed and we're trying to undo the local echo.  We use
 | 
						|
            // the saved content and flags rather than rendering; this
 | 
						|
            // is important in case
 | 
						|
            // markdown.contains_backend_only_syntax(message) is true.
 | 
						|
            message.content = request.content;
 | 
						|
            message.mentioned = request.mentioned;
 | 
						|
            message.mentioned_me_directly = request.mentioned_me_directly;
 | 
						|
            message.alerted = request.alerted;
 | 
						|
        } else {
 | 
						|
            // Otherwise, we Markdown-render the message; this resets
 | 
						|
            // all flags, so we need to restore those flags that are
 | 
						|
            // properties of how the user has interacted with the
 | 
						|
            // message, and not its rendering.
 | 
						|
            markdown.apply_markdown(message);
 | 
						|
            if (request.starred !== undefined) {
 | 
						|
                message.starred = request.starred;
 | 
						|
            }
 | 
						|
            if (request.historical !== undefined) {
 | 
						|
                message.historical = request.historical;
 | 
						|
            }
 | 
						|
            if (request.collapsed !== undefined) {
 | 
						|
                message.collapsed = request.collapsed;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // We don't have logic to adjust unread counts, because message
 | 
						|
    // reaching this code path must either have been sent by us or the
 | 
						|
    // topic isn't being edited, so unread counts can't have changed.
 | 
						|
 | 
						|
    home_msg_list.view.rerender_messages([message]);
 | 
						|
    if (current_msg_list === message_list.narrowed) {
 | 
						|
        message_list.narrowed.view.rerender_messages([message]);
 | 
						|
    }
 | 
						|
    stream_list.update_streams_sidebar();
 | 
						|
    pm_list.update_private_messages();
 | 
						|
};
 | 
						|
 | 
						|
exports.reify_message_id = function (local_id, server_id) {
 | 
						|
    const message = waiting_for_id.get(local_id);
 | 
						|
    waiting_for_id.delete(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;
 | 
						|
    }
 | 
						|
 | 
						|
    message.id = server_id;
 | 
						|
    message.locally_echoed = false;
 | 
						|
 | 
						|
    const opts = {old_id: Number.parseFloat(local_id), new_id: server_id};
 | 
						|
 | 
						|
    message_store.reify_message_id(opts);
 | 
						|
    notifications.reify_message_id(opts);
 | 
						|
    recent_topics.reify_message_id_if_available(opts);
 | 
						|
};
 | 
						|
 | 
						|
exports.process_from_server = function (messages) {
 | 
						|
    const msgs_to_rerender = [];
 | 
						|
    const non_echo_messages = [];
 | 
						|
 | 
						|
    for (const message of messages) {
 | 
						|
        // In case we get the sent message before we get the send ACK, reify here
 | 
						|
 | 
						|
        const local_id = message.local_id;
 | 
						|
        const client_message = waiting_for_ack.get(local_id);
 | 
						|
        if (client_message === undefined) {
 | 
						|
            // For messages that weren't locally echoed, we go through
 | 
						|
            // the "main" codepath that doesn't have to id reconciliation.
 | 
						|
            // We simply return non-echo messages to our caller.
 | 
						|
            non_echo_messages.push(message);
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        exports.reify_message_id(local_id, message.id);
 | 
						|
 | 
						|
        if (message_store.get(message.id).failed_request) {
 | 
						|
            failed_message_success(message.id);
 | 
						|
        }
 | 
						|
 | 
						|
        if (client_message.content !== message.content) {
 | 
						|
            client_message.content = message.content;
 | 
						|
            sent_messages.mark_disparity(local_id);
 | 
						|
        }
 | 
						|
 | 
						|
        message_store.update_booleans(client_message, message.flags);
 | 
						|
 | 
						|
        // We don't try to highlight alert words locally, so we have to
 | 
						|
        // do it now.  (Note that we will indeed highlight alert words in
 | 
						|
        // messages that we sent to ourselves, since we might want to test
 | 
						|
        // that our alert words are set up correctly.)
 | 
						|
        alert_words.process_message(client_message);
 | 
						|
 | 
						|
        // Previously, the message had the "local echo" timestamp set
 | 
						|
        // by the browser; if there was some round-trip delay to the
 | 
						|
        // server, the actual server-side timestamp could be slightly
 | 
						|
        // different.  This corrects the frontend timestamp to match
 | 
						|
        // the backend.
 | 
						|
        client_message.timestamp = message.timestamp;
 | 
						|
 | 
						|
        client_message.topic_links = message.topic_links;
 | 
						|
        client_message.is_me_message = message.is_me_message;
 | 
						|
        client_message.submessages = message.submessages;
 | 
						|
 | 
						|
        msgs_to_rerender.push(client_message);
 | 
						|
        waiting_for_ack.delete(local_id);
 | 
						|
    }
 | 
						|
 | 
						|
    if (msgs_to_rerender.length > 0) {
 | 
						|
        // In theory, we could just rerender messages where there were
 | 
						|
        // changes in either the rounded timestamp we display or the
 | 
						|
        // message content, but in practice, there's no harm to just
 | 
						|
        // doing it unconditionally.
 | 
						|
        home_msg_list.view.rerender_messages(msgs_to_rerender);
 | 
						|
        if (current_msg_list === message_list.narrowed) {
 | 
						|
            message_list.narrowed.view.rerender_messages(msgs_to_rerender);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return non_echo_messages;
 | 
						|
};
 | 
						|
 | 
						|
exports._patch_waiting_for_ack = function (data) {
 | 
						|
    // Only for testing
 | 
						|
    waiting_for_ack = data;
 | 
						|
};
 | 
						|
 | 
						|
exports.message_send_error = function (message_id, error_response) {
 | 
						|
    // Error sending message, show inline
 | 
						|
    message_store.get(message_id).failed_request = true;
 | 
						|
    ui.show_message_failed(message_id, error_response);
 | 
						|
};
 | 
						|
 | 
						|
function abort_message(message) {
 | 
						|
    // Remove in all lists in which it exists
 | 
						|
    for (const msg_list of [message_list.all, home_msg_list, current_msg_list]) {
 | 
						|
        msg_list.remove_and_rerender([message.id]);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
exports.initialize = function () {
 | 
						|
    function on_failed_action(action, callback) {
 | 
						|
        $("#main_div").on("click", "." + action + "-failed-message", function (e) {
 | 
						|
            e.stopPropagation();
 | 
						|
            popovers.hide_all();
 | 
						|
            const row = $(this).closest(".message_row");
 | 
						|
            const local_id = rows.local_echo_id(row);
 | 
						|
            // Message should be waiting for ack and only have a local id,
 | 
						|
            // otherwise send would not have failed
 | 
						|
            const message = waiting_for_ack.get(local_id);
 | 
						|
            if (message === undefined) {
 | 
						|
                blueslip.warn(
 | 
						|
                    "Got resend or retry on failure request but did not find message in ack list " +
 | 
						|
                        local_id,
 | 
						|
                );
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            callback(message, row);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    on_failed_action("remove", abort_message);
 | 
						|
    on_failed_action("refresh", resend_message);
 | 
						|
};
 | 
						|
 | 
						|
window.echo = exports;
 |