mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	This commit was originally automatically generated using `tools/lint --only=eslint --fix`. It was then modified by tabbott to contain only changes to a set of files that are unlikely to result in significant merge conflicts with any open pull request, excluding about 20 files. His plan is to merge the remaining changes with more precise care, potentially involving merging parts of conflicting pull requests before running the `eslint --fix` operation. Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
		
			
				
	
	
		
			345 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			345 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const render_user_presence_row = require('../templates/user_presence_row.hbs');
 | 
						|
const render_user_presence_rows = require('../templates/user_presence_rows.hbs');
 | 
						|
 | 
						|
function buddy_list_conf() {
 | 
						|
    const 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) {
 | 
						|
        const user_info = opts.items;
 | 
						|
        const html = render_user_presence_rows({users: user_info});
 | 
						|
        return html;
 | 
						|
    };
 | 
						|
 | 
						|
    conf.item_to_html = function (opts) {
 | 
						|
        const html = render_user_presence_row(opts.item);
 | 
						|
        return html;
 | 
						|
    };
 | 
						|
 | 
						|
    conf.get_li_from_key = function (opts) {
 | 
						|
        const user_id = opts.key;
 | 
						|
        const container = $(conf.container_sel);
 | 
						|
        const sel = conf.item_sel + "[data-user-id='" + user_id + "']";
 | 
						|
        return container.find(sel);
 | 
						|
    };
 | 
						|
 | 
						|
    conf.get_key_from_li = function (opts) {
 | 
						|
        const user_id = opts.li.expectOne().attr('data-user-id');
 | 
						|
        return user_id;
 | 
						|
    };
 | 
						|
 | 
						|
    conf.get_data_from_keys = function (opts) {
 | 
						|
        const keys = opts.keys;
 | 
						|
        const 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.
 | 
						|
        const height = message_viewport.height();
 | 
						|
        return height;
 | 
						|
    };
 | 
						|
 | 
						|
    return conf;
 | 
						|
}
 | 
						|
 | 
						|
function buddy_list_create() {
 | 
						|
    const conf = buddy_list_conf();
 | 
						|
 | 
						|
    const 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;
 | 
						|
 | 
						|
    const 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) {
 | 
						|
        const chunk_size = opts.chunk_size;
 | 
						|
 | 
						|
        const begin = self.render_count;
 | 
						|
        const end = begin + chunk_size;
 | 
						|
 | 
						|
        const more_keys = self.keys.slice(begin, end);
 | 
						|
 | 
						|
        if (more_keys.length === 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        const items = self.get_data_from_keys({
 | 
						|
            keys: more_keys,
 | 
						|
        });
 | 
						|
 | 
						|
        const 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 () {
 | 
						|
        const 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) {
 | 
						|
        const i = self.keys.indexOf(key.toString());
 | 
						|
 | 
						|
        if (i <= 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        return self.keys[i - 1];
 | 
						|
    };
 | 
						|
 | 
						|
    self.next_key = function (key) {
 | 
						|
        const i = self.keys.indexOf(key.toString());
 | 
						|
 | 
						|
        if (i < 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        return self.keys[i + 1];
 | 
						|
    };
 | 
						|
 | 
						|
    self.maybe_remove_key = function (opts) {
 | 
						|
        const pos = self.keys.indexOf(opts.key);
 | 
						|
 | 
						|
        if (pos < 0) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        self.keys.splice(pos, 1);
 | 
						|
 | 
						|
        if (pos < self.render_count) {
 | 
						|
            self.render_count -= 1;
 | 
						|
            const li = self.find_li({key: opts.key});
 | 
						|
            li.remove();
 | 
						|
            self.update_padding();
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    self.find_position = function (opts) {
 | 
						|
        const key = opts.key;
 | 
						|
        let i;
 | 
						|
 | 
						|
        for (i = 0; i < self.keys.length; i += 1) {
 | 
						|
            const list_key = self.keys[i];
 | 
						|
 | 
						|
            if (self.compare_function(key, list_key) < 0) {
 | 
						|
                return i;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return self.keys.length;
 | 
						|
    };
 | 
						|
 | 
						|
    self.force_render = function (opts) {
 | 
						|
        const pos = opts.pos;
 | 
						|
 | 
						|
        // Try to render a bit optimistically here.
 | 
						|
        const cushion_size = 3;
 | 
						|
        const 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) {
 | 
						|
        const key = opts.key.toString();
 | 
						|
 | 
						|
        // Try direct DOM lookup first for speed.
 | 
						|
        let 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;
 | 
						|
        }
 | 
						|
 | 
						|
        const 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) {
 | 
						|
        const other_key = opts.other_key;
 | 
						|
        const html = opts.html;
 | 
						|
        const 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;
 | 
						|
            const li = self.find_li({key: other_key});
 | 
						|
            li.before(html);
 | 
						|
            self.update_padding();
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    self.insert_or_move = function (opts) {
 | 
						|
        const key = opts.key.toString();
 | 
						|
        const item = opts.item;
 | 
						|
 | 
						|
        self.maybe_remove_key({key: key});
 | 
						|
 | 
						|
        const pos = self.find_position({
 | 
						|
            key: key,
 | 
						|
        });
 | 
						|
 | 
						|
        // Order is important here--get the other_key
 | 
						|
        // before mutating our list.  An undefined value
 | 
						|
        // corresponds to appending.
 | 
						|
        const other_key = self.keys[pos];
 | 
						|
 | 
						|
        self.keys.splice(pos, 0, key);
 | 
						|
 | 
						|
        const 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 () {
 | 
						|
        let height = self.height_to_fill();
 | 
						|
 | 
						|
        const elem = ui.get_scroll_element($(self.scroll_container_sel)).expectOne()[0];
 | 
						|
 | 
						|
        // Add a fudge factor.
 | 
						|
        height += 10;
 | 
						|
 | 
						|
        while (self.render_count < self.keys.length) {
 | 
						|
            const padding_height = $(self.padding_sel).height();
 | 
						|
            const bottom_offset = elem.scrollHeight - elem.scrollTop - padding_height;
 | 
						|
 | 
						|
            if (bottom_offset > height) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            const 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.
 | 
						|
        const scroll_container = ui.get_scroll_element($(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;
 | 
						|
}
 | 
						|
 | 
						|
const buddy_list = buddy_list_create();
 | 
						|
 | 
						|
module.exports = buddy_list;
 | 
						|
 | 
						|
window.buddy_list = buddy_list;
 |