mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	This moves us closer to having a generic widget that we can use for other things like stream settings.
		
			
				
	
	
		
			349 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var buddy_list = (function () {
 | 
						|
 | 
						|
function buddy_list_conf() {
 | 
						|
    var conf = {};
 | 
						|
 | 
						|
    conf.container_sel = '#user_presences';
 | 
						|
    conf.scroll_container_sel = '#buddy_list_wrapper';
 | 
						|
    conf.item_sel = 'li.user_sidebar_entry';
 | 
						|
    conf.padding_sel = '#buddy_list_wrapper_padding';
 | 
						|
 | 
						|
    conf.items_to_html = function (opts) {
 | 
						|
        var user_info = opts.items;
 | 
						|
        var html = templates.render('user_presence_rows', {users: user_info});
 | 
						|
        return html;
 | 
						|
    };
 | 
						|
 | 
						|
    conf.item_to_html = function (opts) {
 | 
						|
        var html = templates.render('user_presence_row', opts.item);
 | 
						|
        return html;
 | 
						|
    };
 | 
						|
 | 
						|
    conf.get_li_from_key = function (opts) {
 | 
						|
        var user_id = opts.key;
 | 
						|
        var container = $(conf.container_sel);
 | 
						|
        var sel = conf.item_sel + "[data-user-id='" + user_id + "']";
 | 
						|
        return container.find(sel);
 | 
						|
    };
 | 
						|
 | 
						|
    conf.get_key_from_li = function (opts) {
 | 
						|
        var user_id = opts.li.expectOne().attr('data-user-id');
 | 
						|
        return user_id;
 | 
						|
    };
 | 
						|
 | 
						|
    conf.get_data_from_keys = function (opts) {
 | 
						|
        var keys = opts.keys;
 | 
						|
        var data = buddy_data.get_items_for_users(keys);
 | 
						|
        return data;
 | 
						|
    };
 | 
						|
 | 
						|
    conf.compare_function = buddy_data.compare_function;
 | 
						|
 | 
						|
    conf.height_to_fill = function () {
 | 
						|
        // Because the buddy list gets sized dynamically, we err on the side
 | 
						|
        // of using the height of the entire viewport for deciding
 | 
						|
        // how much content to render.  Even on tall monitors this should
 | 
						|
        // still be a significant optimization for orgs with thousands of
 | 
						|
        // users.
 | 
						|
        var height = message_viewport.height();
 | 
						|
        return height;
 | 
						|
    };
 | 
						|
 | 
						|
    return conf;
 | 
						|
}
 | 
						|
 | 
						|
function buddy_list_create() {
 | 
						|
    var conf = buddy_list_conf();
 | 
						|
 | 
						|
    var self = {};
 | 
						|
 | 
						|
    self.container_sel = conf.container_sel;
 | 
						|
    self.scroll_container_sel = conf.scroll_container_sel;
 | 
						|
    self.item_sel = conf.item_sel;
 | 
						|
    self.padding_sel = conf.padding_sel;
 | 
						|
 | 
						|
    var func_names = [
 | 
						|
        'items_to_html',
 | 
						|
        'item_to_html',
 | 
						|
        'get_li_from_key',
 | 
						|
        'get_key_from_li',
 | 
						|
        'get_data_from_keys',
 | 
						|
        'compare_function',
 | 
						|
        'height_to_fill',
 | 
						|
    ];
 | 
						|
 | 
						|
    _.each(func_names, function (func_name) {
 | 
						|
        self[func_name] = conf[func_name];
 | 
						|
    });
 | 
						|
 | 
						|
    self.keys = [];
 | 
						|
 | 
						|
    self.populate = function (opts) {
 | 
						|
        self.render_count = 0;
 | 
						|
        self.container.html('');
 | 
						|
 | 
						|
        // We rely on our caller to give us items
 | 
						|
        // in already-sorted order.
 | 
						|
        self.keys = _.map(opts.keys, function (k) {
 | 
						|
            return k.toString();
 | 
						|
        });
 | 
						|
 | 
						|
        self.fill_screen_with_content();
 | 
						|
    };
 | 
						|
 | 
						|
    self.render_more = function (opts) {
 | 
						|
        var chunk_size = opts.chunk_size;
 | 
						|
 | 
						|
        var begin = self.render_count;
 | 
						|
        var end = begin + chunk_size;
 | 
						|
 | 
						|
        var more_keys = self.keys.slice(begin, end);
 | 
						|
 | 
						|
        if (more_keys.length === 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        var items = self.get_data_from_keys({
 | 
						|
            keys: more_keys,
 | 
						|
        });
 | 
						|
 | 
						|
        var html = self.items_to_html({
 | 
						|
            items: items,
 | 
						|
        });
 | 
						|
        self.container = $(self.container_sel);
 | 
						|
        self.container.append(html);
 | 
						|
 | 
						|
        // Invariant: more_keys.length >= items.length.
 | 
						|
        // (Usually they're the same, but occasionally keys
 | 
						|
        // won't return valid items.  Even though we don't
 | 
						|
        // actually render these keys, we still "count" them
 | 
						|
        // as rendered.
 | 
						|
 | 
						|
        self.render_count += more_keys.length;
 | 
						|
        self.update_padding();
 | 
						|
    };
 | 
						|
 | 
						|
    self.get_items = function () {
 | 
						|
        var obj = self.container.find(self.item_sel);
 | 
						|
        return obj.map(function (i, elem) {
 | 
						|
            return $(elem);
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    self.first_key = function () {
 | 
						|
        return self.keys[0];
 | 
						|
    };
 | 
						|
 | 
						|
    self.prev_key = function (key) {
 | 
						|
        var i = self.keys.indexOf(key.toString());
 | 
						|
 | 
						|
        if (i <= 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        return self.keys[i - 1];
 | 
						|
    };
 | 
						|
 | 
						|
    self.next_key = function (key) {
 | 
						|
        var i = self.keys.indexOf(key.toString());
 | 
						|
 | 
						|
        if (i < 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        return self.keys[i + 1];
 | 
						|
    };
 | 
						|
 | 
						|
    self.maybe_remove_key = function (opts) {
 | 
						|
        var pos = self.keys.indexOf(opts.key);
 | 
						|
 | 
						|
        if (pos < 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        self.keys.splice(pos, 1);
 | 
						|
 | 
						|
        if (pos < self.render_count) {
 | 
						|
            self.render_count -= 1;
 | 
						|
            var li = self.find_li({key: opts.key});
 | 
						|
            li.remove();
 | 
						|
            self.update_padding();
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    self.find_position = function (opts) {
 | 
						|
        var key = opts.key;
 | 
						|
        var i;
 | 
						|
 | 
						|
        for (i = 0; i < self.keys.length; i += 1) {
 | 
						|
            var list_key = self.keys[i];
 | 
						|
 | 
						|
            if (self.compare_function(key, list_key) < 0) {
 | 
						|
                return i;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return self.keys.length;
 | 
						|
    };
 | 
						|
 | 
						|
    self.force_render = function (opts) {
 | 
						|
        var pos = opts.pos;
 | 
						|
 | 
						|
        // Try to render a bit optimistically here.
 | 
						|
        var cushion_size = 3;
 | 
						|
        var chunk_size = pos + cushion_size - self.render_count;
 | 
						|
 | 
						|
        if (chunk_size <= 0) {
 | 
						|
            blueslip.error('cannot show key at this position: ' + pos);
 | 
						|
        }
 | 
						|
 | 
						|
        self.render_more({
 | 
						|
            chunk_size: chunk_size,
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    self.find_li = function (opts) {
 | 
						|
        var key = opts.key.toString();
 | 
						|
 | 
						|
        // Try direct DOM lookup first for speed.
 | 
						|
        var li = self.get_li_from_key({
 | 
						|
            key: key,
 | 
						|
        });
 | 
						|
 | 
						|
        if (li.length === 1) {
 | 
						|
            return li;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!opts.force_render) {
 | 
						|
            // Most callers don't force us to render a list
 | 
						|
            // item that wouldn't be on-screen anyway.
 | 
						|
            return li;
 | 
						|
        }
 | 
						|
 | 
						|
        var pos = self.keys.indexOf(key);
 | 
						|
 | 
						|
        if (pos < 0) {
 | 
						|
            // TODO: See list_cursor.get_row() for why this is
 | 
						|
            //       a bit janky now.
 | 
						|
            return [];
 | 
						|
        }
 | 
						|
 | 
						|
        self.force_render({
 | 
						|
            pos: pos,
 | 
						|
        });
 | 
						|
 | 
						|
        li = self.get_li_from_key({
 | 
						|
            key: key,
 | 
						|
        });
 | 
						|
 | 
						|
        return li;
 | 
						|
    };
 | 
						|
 | 
						|
    self.insert_new_html = function (opts) {
 | 
						|
        var other_key = opts.other_key;
 | 
						|
        var html = opts.html;
 | 
						|
        var pos = opts.pos;
 | 
						|
 | 
						|
        if (other_key === undefined) {
 | 
						|
            if (pos === self.render_count) {
 | 
						|
                self.render_count += 1;
 | 
						|
                self.container.append(html);
 | 
						|
                self.update_padding();
 | 
						|
            }
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if (pos < self.render_count) {
 | 
						|
            self.render_count += 1;
 | 
						|
            var li = self.find_li({key: other_key});
 | 
						|
            li.before(html);
 | 
						|
            self.update_padding();
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    self.insert_or_move = function (opts) {
 | 
						|
        var key = opts.key.toString();
 | 
						|
        var item = opts.item;
 | 
						|
 | 
						|
        self.maybe_remove_key({key: key});
 | 
						|
 | 
						|
        var pos = self.find_position({
 | 
						|
            key: key,
 | 
						|
        });
 | 
						|
 | 
						|
        // Order is important here--get the other_key
 | 
						|
        // before mutating our list.  An undefined value
 | 
						|
        // corresponds to appending.
 | 
						|
        var other_key = self.keys[pos];
 | 
						|
 | 
						|
        self.keys.splice(pos, 0, key);
 | 
						|
 | 
						|
        var html = self.item_to_html({item: item});
 | 
						|
        self.insert_new_html({
 | 
						|
            pos: pos,
 | 
						|
            html: html,
 | 
						|
            other_key: other_key,
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    self.fill_screen_with_content = function () {
 | 
						|
        var height_to_fill = self.height_to_fill();
 | 
						|
 | 
						|
        var elem = $(self.scroll_container_sel).expectOne()[0];
 | 
						|
 | 
						|
        // Add a fudge factor.
 | 
						|
        height_to_fill += 10;
 | 
						|
 | 
						|
        while (self.render_count < self.keys.length) {
 | 
						|
            var padding_height = $(self.padding_sel).height();
 | 
						|
            var bottom_offset = elem.scrollHeight - elem.scrollTop - padding_height;
 | 
						|
 | 
						|
            if (bottom_offset > height_to_fill) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            var chunk_size = 20;
 | 
						|
 | 
						|
            self.render_more({
 | 
						|
                chunk_size: chunk_size,
 | 
						|
            });
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    // This is a bit of a hack to make sure we at least have
 | 
						|
    // an empty list to start, before we get the initial payload.
 | 
						|
    self.container = $(self.container_sel);
 | 
						|
 | 
						|
    self.start_scroll_handler = function () {
 | 
						|
        // We have our caller explicitly call this to make
 | 
						|
        // sure everything's in place.
 | 
						|
        var scroll_container = $(self.scroll_container_sel);
 | 
						|
 | 
						|
        scroll_container.scroll(function () {
 | 
						|
            self.fill_screen_with_content();
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    self.update_padding = function () {
 | 
						|
        padded_widget.update_padding({
 | 
						|
            shown_rows: self.render_count,
 | 
						|
            total_rows: self.keys.length,
 | 
						|
            content_sel: self.container_sel,
 | 
						|
            padding_sel: self.padding_sel,
 | 
						|
        });
 | 
						|
    };
 | 
						|
 | 
						|
    return self;
 | 
						|
}
 | 
						|
 | 
						|
var exports = buddy_list_create();
 | 
						|
 | 
						|
return exports;
 | 
						|
 | 
						|
}());
 | 
						|
if (typeof module !== 'undefined') {
 | 
						|
    module.exports = buddy_list;
 | 
						|
}
 | 
						|
 | 
						|
window.buddy_list = buddy_list;
 |