mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			441 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			441 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var message_list = (function () {
 | 
						|
 | 
						|
var exports = {};
 | 
						|
 | 
						|
exports.narrowed = undefined;
 | 
						|
 | 
						|
exports.MessageList = function (opts) {
 | 
						|
    if (opts.data) {
 | 
						|
        this.muting_enabled = opts.data.muting_enabled;
 | 
						|
        this.data = opts.data;
 | 
						|
    } else {
 | 
						|
        var filter = opts.filter;
 | 
						|
 | 
						|
        this.muting_enabled = opts.muting_enabled;
 | 
						|
        this.data = new MessageListData({
 | 
						|
            muting_enabled: this.muting_enabled,
 | 
						|
            filter: filter,
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    _.extend(opts, {
 | 
						|
        collapse_messages: true,
 | 
						|
    });
 | 
						|
 | 
						|
    var collapse_messages = opts.collapse_messages;
 | 
						|
    var table_name = opts.table_name;
 | 
						|
    this.view = new MessageListView(this, table_name, collapse_messages);
 | 
						|
    this.fetch_status = FetchStatus();
 | 
						|
    this.table_name = table_name;
 | 
						|
    this.narrowed = this.table_name === "zfilt";
 | 
						|
    this.num_appends = 0;
 | 
						|
 | 
						|
    return this;
 | 
						|
};
 | 
						|
 | 
						|
exports.MessageList.prototype = {
 | 
						|
    add_messages: function MessageList_add_messages(messages, opts) {
 | 
						|
        var self = this;
 | 
						|
 | 
						|
        // This adds all messages to our data, but only returns
 | 
						|
        // the currently viewable ones.
 | 
						|
        var info = this.data.add_messages(messages);
 | 
						|
 | 
						|
        var top_messages = info.top_messages;
 | 
						|
        var bottom_messages = info.bottom_messages;
 | 
						|
        var interior_messages = info.interior_messages;
 | 
						|
 | 
						|
        if (interior_messages.length > 0) {
 | 
						|
            self.view.rerender_the_whole_thing();
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        if (top_messages.length > 0) {
 | 
						|
            self.view.prepend(top_messages);
 | 
						|
        }
 | 
						|
 | 
						|
        if (bottom_messages.length > 0) {
 | 
						|
            self.append_to_view(bottom_messages, opts);
 | 
						|
        }
 | 
						|
 | 
						|
        if (self === exports.narrowed && !self.empty()) {
 | 
						|
            // If adding some new messages to the message tables caused
 | 
						|
            // our current narrow to no longer be empty, hide the empty
 | 
						|
            // feed placeholder text.
 | 
						|
            narrow.hide_empty_narrow_message();
 | 
						|
        }
 | 
						|
 | 
						|
        if (self === exports.narrowed && !self.empty() &&
 | 
						|
            self.selected_id() === -1) {
 | 
						|
            // And also select the newly arrived message.
 | 
						|
            self.select_id(self.selected_id(), {then_scroll: true, use_closest: true});
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    get: function (id) {
 | 
						|
        return this.data.get(id);
 | 
						|
    },
 | 
						|
 | 
						|
    num_items: function () {
 | 
						|
        return this.data.num_items();
 | 
						|
    },
 | 
						|
 | 
						|
    empty: function () {
 | 
						|
        return this.data.empty();
 | 
						|
    },
 | 
						|
 | 
						|
    first: function () {
 | 
						|
        return this.data.first();
 | 
						|
    },
 | 
						|
 | 
						|
    last: function () {
 | 
						|
        return this.data.last();
 | 
						|
    },
 | 
						|
 | 
						|
    prev: function () {
 | 
						|
        return this.data.prev();
 | 
						|
    },
 | 
						|
 | 
						|
    next: function () {
 | 
						|
        return this.data.next();
 | 
						|
    },
 | 
						|
 | 
						|
    is_at_end: function () {
 | 
						|
        return this.data.is_at_end();
 | 
						|
    },
 | 
						|
 | 
						|
    nth_most_recent_id: function (n) {
 | 
						|
        return this.data.nth_most_recent_id(n);
 | 
						|
    },
 | 
						|
 | 
						|
    is_search: function () {
 | 
						|
        return this.data.is_search();
 | 
						|
    },
 | 
						|
 | 
						|
    clear: function  MessageList_clear(opts) {
 | 
						|
        opts = _.extend({clear_selected_id: true}, opts);
 | 
						|
 | 
						|
        this.data.clear();
 | 
						|
        this.view.clear_rendering_state(true);
 | 
						|
 | 
						|
        if (opts.clear_selected_id) {
 | 
						|
            this.data.clear_selected_id();
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    selected_id: function () {
 | 
						|
        return this.data.selected_id();
 | 
						|
    },
 | 
						|
 | 
						|
    select_id: function MessageList_select_id(id, opts) {
 | 
						|
        opts = _.extend({
 | 
						|
            then_scroll: false,
 | 
						|
            target_scroll_offset: undefined,
 | 
						|
            use_closest: false,
 | 
						|
            empty_ok: false,
 | 
						|
            mark_read: true,
 | 
						|
            force_rerender: false,
 | 
						|
        }, opts, {
 | 
						|
            id: id,
 | 
						|
            msg_list: this,
 | 
						|
            previously_selected: this.data.selected_id(),
 | 
						|
        });
 | 
						|
 | 
						|
        function convert_id(str_id) {
 | 
						|
            var id = parseFloat(str_id);
 | 
						|
            if (isNaN(id)) {
 | 
						|
                blueslip.fatal("Bad message id " + str_id);
 | 
						|
            }
 | 
						|
            return id;
 | 
						|
        }
 | 
						|
 | 
						|
        id = convert_id(id);
 | 
						|
 | 
						|
        var closest_id = this.closest_id(id);
 | 
						|
 | 
						|
        var error_data;
 | 
						|
 | 
						|
        // The name "use_closest" option is a bit legacy.  We
 | 
						|
        // are always gonna move to the closest visible id; the flag
 | 
						|
        // just says whether we call blueslip.error or not.  The caller
 | 
						|
        // sets use_closest to true when it expects us to move the
 | 
						|
        // pointer as needed, so only generate an error if the flag is
 | 
						|
        // false.
 | 
						|
        if (!opts.use_closest && closest_id !== id) {
 | 
						|
            error_data = {
 | 
						|
                table_name: this.table_name,
 | 
						|
                id: id,
 | 
						|
                closest_id: closest_id,
 | 
						|
            };
 | 
						|
            blueslip.error("Selected message id not in MessageList",
 | 
						|
                           error_data);
 | 
						|
        }
 | 
						|
 | 
						|
        if (closest_id === -1 && !opts.empty_ok) {
 | 
						|
            error_data = {
 | 
						|
                table_name: this.table_name,
 | 
						|
                id: id,
 | 
						|
                items_length: this.data.num_items(),
 | 
						|
            };
 | 
						|
            blueslip.fatal("Cannot select id -1", error_data);
 | 
						|
        }
 | 
						|
 | 
						|
        id = closest_id;
 | 
						|
        opts.id = id;
 | 
						|
        this.data.set_selected_id(id);
 | 
						|
 | 
						|
        if (opts.force_rerender) {
 | 
						|
            this.rerender();
 | 
						|
        } else if (!opts.from_rendering) {
 | 
						|
            this.view.maybe_rerender();
 | 
						|
        }
 | 
						|
 | 
						|
        $(document).trigger($.Event('message_selected.zulip', opts));
 | 
						|
    },
 | 
						|
 | 
						|
    reselect_selected_id: function MessageList_select_closest_id() {
 | 
						|
        this.select_id(this.data.selected_id(), {from_rendering: true});
 | 
						|
    },
 | 
						|
 | 
						|
    selected_message: function MessageList_selected_message() {
 | 
						|
        return this.get(this.data.selected_id());
 | 
						|
    },
 | 
						|
 | 
						|
    selected_row: function MessageList_selected_row() {
 | 
						|
        return this.get_row(this.data.selected_id());
 | 
						|
    },
 | 
						|
 | 
						|
    closest_id: function (id) {
 | 
						|
        return this.data.closest_id(id);
 | 
						|
    },
 | 
						|
 | 
						|
    advance_past_messages: function (msg_ids) {
 | 
						|
        return this.data.advance_past_messages(msg_ids);
 | 
						|
    },
 | 
						|
 | 
						|
    selected_idx: function () {
 | 
						|
        return this.data.selected_idx();
 | 
						|
    },
 | 
						|
 | 
						|
    subscribed_bookend_content: function (stream_name) {
 | 
						|
        return i18n.t("You subscribed to stream __stream__",
 | 
						|
                      {stream: stream_name});
 | 
						|
    },
 | 
						|
 | 
						|
    unsubscribed_bookend_content: function (stream_name) {
 | 
						|
        return i18n.t("You unsubscribed from stream __stream__",
 | 
						|
                      {stream: stream_name});
 | 
						|
    },
 | 
						|
 | 
						|
    not_subscribed_bookend_content: function (stream_name) {
 | 
						|
        return i18n.t("You are not subscribed to stream __stream__",
 | 
						|
                      {stream: stream_name});
 | 
						|
    },
 | 
						|
 | 
						|
    // Maintains a trailing bookend element explaining any changes in
 | 
						|
    // your subscribed/unsubscribed status at the bottom of the
 | 
						|
    // message list.
 | 
						|
    update_trailing_bookend: function MessageList_update_trailing_bookend() {
 | 
						|
        this.view.clear_trailing_bookend();
 | 
						|
        if (!this.narrowed) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        var stream_name = narrow_state.stream();
 | 
						|
        if (stream_name === undefined) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        var trailing_bookend_content;
 | 
						|
        var show_button = true;
 | 
						|
        var subscribed = stream_data.is_subscribed(stream_name);
 | 
						|
        if (subscribed) {
 | 
						|
            trailing_bookend_content = this.subscribed_bookend_content(stream_name);
 | 
						|
        } else {
 | 
						|
            if (!this.last_message_historical) {
 | 
						|
                trailing_bookend_content = this.unsubscribed_bookend_content(stream_name);
 | 
						|
 | 
						|
                // For invite only streams or streams that no longer
 | 
						|
                // exist, hide the resubscribe button
 | 
						|
                var sub = stream_data.get_sub(stream_name);
 | 
						|
                if (sub !== undefined) {
 | 
						|
                    show_button = !sub.invite_only;
 | 
						|
                } else {
 | 
						|
                    show_button = false;
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                trailing_bookend_content = this.not_subscribed_bookend_content(stream_name);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (trailing_bookend_content !== undefined) {
 | 
						|
            this.view.render_trailing_bookend(trailing_bookend_content, subscribed, show_button);
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    unmuted_messages: function (messages) {
 | 
						|
        return this.data.unmuted_messages(messages);
 | 
						|
    },
 | 
						|
 | 
						|
    append: function MessageList_append(messages, opts) {
 | 
						|
        var viewable_messages = this.data.append(messages);
 | 
						|
        this.append_to_view(viewable_messages, opts);
 | 
						|
    },
 | 
						|
 | 
						|
    append_to_view: function (messages, opts) {
 | 
						|
        opts = _.extend({messages_are_new: false}, opts);
 | 
						|
 | 
						|
        this.num_appends += 1;
 | 
						|
        this.view.append(messages, opts.messages_are_new);
 | 
						|
    },
 | 
						|
 | 
						|
    remove_and_rerender: function MessageList_remove_and_rerender(messages) {
 | 
						|
        this.data.remove(messages);
 | 
						|
        this.rerender();
 | 
						|
    },
 | 
						|
 | 
						|
    show_edit_message: function MessageList_show_edit_message(row, edit_obj) {
 | 
						|
        row.find(".message_edit_form").empty().append(edit_obj.form);
 | 
						|
        row.find(".message_content, .status-message").hide();
 | 
						|
        row.find(".message_edit").css("display", "block");
 | 
						|
        row.find(".message_edit_content").autosize();
 | 
						|
    },
 | 
						|
 | 
						|
    hide_edit_message: function MessageList_hide_edit_message(row) {
 | 
						|
        row.find(".message_content, .status-message").show();
 | 
						|
        row.find(".message_edit").hide();
 | 
						|
        row.trigger("mouseleave");
 | 
						|
    },
 | 
						|
 | 
						|
    show_edit_topic: function MessageList_show_edit_topic(recipient_row, form) {
 | 
						|
        recipient_row.find(".topic_edit_form").empty().append(form);
 | 
						|
        recipient_row.find('.fa-pencil').hide();
 | 
						|
        recipient_row.find(".stream_topic").hide();
 | 
						|
        recipient_row.find(".topic_edit").show();
 | 
						|
    },
 | 
						|
 | 
						|
    hide_edit_topic: function MessageList_hide_edit_topic(recipient_row) {
 | 
						|
        recipient_row.find(".stream_topic").show();
 | 
						|
        recipient_row.find('.fa-pencil').show();
 | 
						|
        recipient_row.find(".topic_edit").hide();
 | 
						|
    },
 | 
						|
 | 
						|
    show_message_as_read: function (message, options) {
 | 
						|
        var row = this.get_row(message.id);
 | 
						|
        if (options.from === 'pointer' || options.from === "server") {
 | 
						|
            row.find('.unread_marker').addClass('fast_fade');
 | 
						|
        } else {
 | 
						|
            row.find('.unread_marker').addClass('slow_fade');
 | 
						|
        }
 | 
						|
        row.removeClass('unread');
 | 
						|
    },
 | 
						|
 | 
						|
    rerender: function MessageList_rerender() {
 | 
						|
        // We need to clear the rendering state, rather than just
 | 
						|
        // doing clear_table, since we want to potentially recollapse
 | 
						|
        // things.
 | 
						|
        this.data.reset_select_to_closest();
 | 
						|
        this.view.clear_rendering_state(false);
 | 
						|
        this.view.update_render_window(this.selected_idx(), false);
 | 
						|
 | 
						|
        if (this === exports.narrowed) {
 | 
						|
            if (this.empty()) {
 | 
						|
                narrow.show_empty_narrow_message();
 | 
						|
            } else {
 | 
						|
                narrow.hide_empty_narrow_message();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        this.view.rerender_preserving_scrolltop();
 | 
						|
        this.redo_selection();
 | 
						|
    },
 | 
						|
 | 
						|
    redo_selection: function () {
 | 
						|
        var selected_id = this.data.selected_id();
 | 
						|
 | 
						|
        if (selected_id !== -1) {
 | 
						|
            this.select_id(selected_id);
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    update_muting_and_rerender: function MessageList_update_muting_and_rerender() {
 | 
						|
        if (!this.muting_enabled) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        this.data.update_items_for_muting();
 | 
						|
        this.rerender();
 | 
						|
    },
 | 
						|
 | 
						|
    all_messages: function MessageList_all_messages() {
 | 
						|
        return this.data.all_messages();
 | 
						|
    },
 | 
						|
 | 
						|
    first_unread_message_id: function () {
 | 
						|
        return this.data.first_unread_message_id();
 | 
						|
    },
 | 
						|
 | 
						|
    message_range: function (start, end) {
 | 
						|
        return this.data.message_range(start, end);
 | 
						|
    },
 | 
						|
 | 
						|
    get_row: function (id) {
 | 
						|
        return this.view.get_row(id);
 | 
						|
    },
 | 
						|
 | 
						|
    update_user_full_name: function (user_id, full_name) {
 | 
						|
        this.data.update_user_full_name(user_id, full_name);
 | 
						|
        if (this.table_name !== undefined) {
 | 
						|
            this.view.rerender_preserving_scrolltop();
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    update_user_avatar: function (user_id, avatar_url) {
 | 
						|
        this.data.update_user_avatar(user_id, avatar_url);
 | 
						|
        if (this.table_name !== undefined) {
 | 
						|
            this.view.rerender_preserving_scrolltop();
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    update_stream_name: function (stream_id, new_stream_name) {
 | 
						|
        this.data.update_stream_name(stream_id, new_stream_name);
 | 
						|
        if (this.table_name !== undefined) {
 | 
						|
            this.view.rerender_preserving_scrolltop();
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    change_message_id: function MessageList_change_message_id(old_id, new_id) {
 | 
						|
        var self = this;
 | 
						|
        var opts = {
 | 
						|
            is_current_list: function () {
 | 
						|
                return current_msg_list === self;
 | 
						|
            },
 | 
						|
            re_render: function () {
 | 
						|
                self.view.rerender_preserving_scrolltop();
 | 
						|
                self.redo_selection();
 | 
						|
            },
 | 
						|
        };
 | 
						|
        this.data.change_message_id(old_id, new_id, opts);
 | 
						|
    },
 | 
						|
 | 
						|
    get_last_message_sent_by_me: function () {
 | 
						|
        return this.data.get_last_message_sent_by_me();
 | 
						|
    },
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
exports.all = new exports.MessageList({
 | 
						|
    muting_enabled: false,
 | 
						|
});
 | 
						|
 | 
						|
// We stop autoscrolling when the user is clearly in the middle of
 | 
						|
// doing something.  Be careful, though, if you try to capture
 | 
						|
// mousemove, then you will have to contend with the autoscroll
 | 
						|
// itself generating mousemove events.
 | 
						|
$(document).on('message_selected.zulip zuliphashchange.zulip wheel', function () {
 | 
						|
    message_viewport.stop_auto_scrolling();
 | 
						|
});
 | 
						|
 | 
						|
return exports;
 | 
						|
 | 
						|
}());
 | 
						|
if (typeof module !== 'undefined') {
 | 
						|
    module.exports = message_list;
 | 
						|
}
 | 
						|
window.message_list = message_list;
 |