mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	js: Extract FoldDict class.
We have ~5 years of proof that we'll probably never extend Dict with more options. Breaking the classes into makes both a little faster (no options to check), and we remove some options in FoldDict that are never used (from/from_array). A possible next step is to fine-tune the Dict to use Map internally. Note that the TypeScript types for FoldDict are now more specific (requiring string keys). Of course, this isn't really enforced until we convert other modules to TS.
This commit is contained in:
		@@ -13,10 +13,12 @@ run_test('basic', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    d.set('foo', 'baz');
 | 
					    d.set('foo', 'baz');
 | 
				
			||||||
    assert.equal(d.get('foo'), 'baz');
 | 
					    assert.equal(d.get('foo'), 'baz');
 | 
				
			||||||
 | 
					    assert.equal(d.num_items(), 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    d.set('bar', 'qux');
 | 
					    d.set('bar', 'qux');
 | 
				
			||||||
    assert.equal(d.get('foo'), 'baz');
 | 
					    assert.equal(d.get('foo'), 'baz');
 | 
				
			||||||
    assert.equal(d.get('bar'), 'qux');
 | 
					    assert.equal(d.get('bar'), 'qux');
 | 
				
			||||||
 | 
					    assert.equal(d.num_items(), 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert.equal(d.has('bar'), true);
 | 
					    assert.equal(d.has('bar'), true);
 | 
				
			||||||
    assert.equal(d.has('baz'), false);
 | 
					    assert.equal(d.has('baz'), false);
 | 
				
			||||||
@@ -52,41 +54,14 @@ run_test('filter_values', () => {
 | 
				
			|||||||
    assert.deepEqual(d.filter_values(pred).sort(), ['fay', 'foo', 'fred']);
 | 
					    assert.deepEqual(d.filter_values(pred).sort(), ['fay', 'foo', 'fred']);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_test('fold_case', () => {
 | 
					 | 
				
			||||||
    const d = new Dict({fold_case: true});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assert.deepEqual(d.keys(), []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assert(!d.has('foo'));
 | 
					 | 
				
			||||||
    d.set('fOO', 'Hello World');
 | 
					 | 
				
			||||||
    assert.equal(d.get('foo'), 'Hello World');
 | 
					 | 
				
			||||||
    assert(d.has('foo'));
 | 
					 | 
				
			||||||
    assert(d.has('FOO'));
 | 
					 | 
				
			||||||
    assert(!d.has('not_a_key'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assert.deepEqual(d.keys(), ['fOO']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    d.del('Foo');
 | 
					 | 
				
			||||||
    assert.equal(d.has('foo'), false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assert.deepEqual(d.keys(), []);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
run_test('undefined_keys', () => {
 | 
					run_test('undefined_keys', () => {
 | 
				
			||||||
    blueslip.set_test_data('error', 'Tried to call a Dict method with an undefined key.');
 | 
					    blueslip.set_test_data('error', 'Tried to call a Dict method with an undefined key.');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let d = new Dict();
 | 
					    const d = new Dict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assert.equal(d.has(undefined), false);
 | 
					    assert.equal(d.has(undefined), false);
 | 
				
			||||||
    assert.strictEqual(d.get(undefined), undefined);
 | 
					    assert.strictEqual(d.get(undefined), undefined);
 | 
				
			||||||
 | 
					    assert.equal(blueslip.get_test_logs('error').length, 2);
 | 
				
			||||||
    d = new Dict({fold_case: true});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assert.equal(d.has(undefined), false);
 | 
					 | 
				
			||||||
    assert.strictEqual(d.get(undefined), undefined);
 | 
					 | 
				
			||||||
    assert.equal(blueslip.get_test_logs('error').length, 4);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    blueslip.clear_test_data();
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_test('restricted_keys', () => {
 | 
					run_test('restricted_keys', () => {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										98
									
								
								frontend_tests/node_tests/fold_dict.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								frontend_tests/node_tests/fold_dict.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					const FoldDict = zrequire('fold_dict').FoldDict;
 | 
				
			||||||
 | 
					set_global('blueslip', global.make_zblueslip());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					run_test('basic', () => {
 | 
				
			||||||
 | 
					    const d = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert(d.is_empty());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepEqual(d.keys(), []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    d.set('foo', 'bar');
 | 
				
			||||||
 | 
					    assert.equal(d.get('foo'), 'bar');
 | 
				
			||||||
 | 
					    assert(!d.is_empty());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    d.set('foo', 'baz');
 | 
				
			||||||
 | 
					    assert.equal(d.get('foo'), 'baz');
 | 
				
			||||||
 | 
					    assert.equal(d.num_items(), 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    d.set('bar', 'qux');
 | 
				
			||||||
 | 
					    assert.equal(d.get('foo'), 'baz');
 | 
				
			||||||
 | 
					    assert.equal(d.get('bar'), 'qux');
 | 
				
			||||||
 | 
					    assert.equal(d.num_items(), 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.equal(d.has('bar'), true);
 | 
				
			||||||
 | 
					    assert.equal(d.has('baz'), false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepEqual(d.keys(), ['foo', 'bar']);
 | 
				
			||||||
 | 
					    assert.deepEqual(d.values(), ['baz', 'qux']);
 | 
				
			||||||
 | 
					    assert.deepEqual(d.items(), [['foo', 'baz'], ['bar', 'qux']]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    d.del('bar');
 | 
				
			||||||
 | 
					    assert.equal(d.has('bar'), false);
 | 
				
			||||||
 | 
					    assert.strictEqual(d.get('bar'), undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepEqual(d.keys(), ['foo']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const val = ['foo'];
 | 
				
			||||||
 | 
					    const res = d.set('abc', val);
 | 
				
			||||||
 | 
					    assert.equal(val, res);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					run_test('case insensitivity', () => {
 | 
				
			||||||
 | 
					    const d = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepEqual(d.keys(), []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert(!d.has('foo'));
 | 
				
			||||||
 | 
					    d.set('fOO', 'Hello World');
 | 
				
			||||||
 | 
					    assert.equal(d.get('foo'), 'Hello World');
 | 
				
			||||||
 | 
					    assert(d.has('foo'));
 | 
				
			||||||
 | 
					    assert(d.has('FOO'));
 | 
				
			||||||
 | 
					    assert(!d.has('not_a_key'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepEqual(d.keys(), ['fOO']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    d.del('Foo');
 | 
				
			||||||
 | 
					    assert.equal(d.has('foo'), false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepEqual(d.keys(), []);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					run_test('clear', () => {
 | 
				
			||||||
 | 
					    const d = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function populate() {
 | 
				
			||||||
 | 
					        d.set('fOO', 1);
 | 
				
			||||||
 | 
					        assert.equal(d.get('foo'), 1);
 | 
				
			||||||
 | 
					        d.set('bAR', 2);
 | 
				
			||||||
 | 
					        assert.equal(d.get('bar'), 2);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    populate();
 | 
				
			||||||
 | 
					    assert.equal(d.num_items(), 2);
 | 
				
			||||||
 | 
					    assert(!d.is_empty());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    d.clear();
 | 
				
			||||||
 | 
					    assert.equal(d.get('fOO'), undefined);
 | 
				
			||||||
 | 
					    assert.equal(d.get('bAR'), undefined);
 | 
				
			||||||
 | 
					    assert.equal(d.num_items(), 0);
 | 
				
			||||||
 | 
					    assert(d.is_empty());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // make sure it still works after clearing
 | 
				
			||||||
 | 
					    populate();
 | 
				
			||||||
 | 
					    assert.equal(d.num_items(), 2);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					run_test('undefined_keys', () => {
 | 
				
			||||||
 | 
					    blueslip.set_test_data('error', 'Tried to call a FoldDict method with an undefined key.');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const d = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.equal(d.has(undefined), false);
 | 
				
			||||||
 | 
					    assert.strictEqual(d.get(undefined), undefined);
 | 
				
			||||||
 | 
					    assert.equal(blueslip.get_test_logs('error').length, 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blueslip.clear_test_data();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
set_global('document', 'document-stub');
 | 
					set_global('document', 'document-stub');
 | 
				
			||||||
set_global('$', global.make_zjquery());
 | 
					set_global('$', global.make_zjquery());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FoldDict = zrequire('fold_dict').FoldDict;
 | 
				
			||||||
zrequire('unread_ui');
 | 
					zrequire('unread_ui');
 | 
				
			||||||
zrequire('Filter', 'js/filter');
 | 
					zrequire('Filter', 'js/filter');
 | 
				
			||||||
zrequire('util');
 | 
					zrequire('util');
 | 
				
			||||||
@@ -670,7 +671,7 @@ run_test('update_count_in_dom', () => {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const topic_count = new Dict({fold_case: true});
 | 
					    const topic_count = new FoldDict();
 | 
				
			||||||
    topic_count.set('lunch', '555');
 | 
					    topic_count.set('lunch', '555');
 | 
				
			||||||
    counts.topic_count.set(stream_id, topic_count);
 | 
					    counts.topic_count.set(stream_id, topic_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ zrequire('stream_data');
 | 
				
			|||||||
zrequire('util');
 | 
					zrequire('util');
 | 
				
			||||||
zrequire('unread');
 | 
					zrequire('unread');
 | 
				
			||||||
zrequire('settings_notifications');
 | 
					zrequire('settings_notifications');
 | 
				
			||||||
 | 
					const FoldDict = zrequire('fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set_global('page_params', {});
 | 
					set_global('page_params', {});
 | 
				
			||||||
set_global('blueslip', {});
 | 
					set_global('blueslip', {});
 | 
				
			||||||
@@ -285,7 +286,7 @@ run_test('num_unread_for_topic', () => {
 | 
				
			|||||||
    msg_ids = unread.get_msg_ids_for_stream(stream_id);
 | 
					    msg_ids = unread.get_msg_ids_for_stream(stream_id);
 | 
				
			||||||
    assert.deepEqual(msg_ids, _.range(1, 501));
 | 
					    assert.deepEqual(msg_ids, _.range(1, 501));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const topic_dict = new Dict({fold_case: true});
 | 
					    const topic_dict = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let missing_topics = unread.get_missing_topics({
 | 
					    let missing_topics = unread.get_missing_topics({
 | 
				
			||||||
        stream_id: stream_id,
 | 
					        stream_id: stream_id,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,7 @@ import "../lightbox_canvas.js";
 | 
				
			|||||||
import "../rtl.js";
 | 
					import "../rtl.js";
 | 
				
			||||||
import "../lazy_set.js";
 | 
					import "../lazy_set.js";
 | 
				
			||||||
import "../dict.ts";
 | 
					import "../dict.ts";
 | 
				
			||||||
 | 
					import "../fold_dict.ts";
 | 
				
			||||||
import "../scroll_util.js";
 | 
					import "../scroll_util.js";
 | 
				
			||||||
import "../components.js";
 | 
					import "../components.js";
 | 
				
			||||||
import "../feedback_widget.js";
 | 
					import "../feedback_widget.js";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,44 +1,23 @@
 | 
				
			|||||||
import * as _ from 'underscore';
 | 
					import * as _ from 'underscore';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Implementation detail of the Dict class. `key` is `k` converted to a string,
 | 
					 | 
				
			||||||
 * in lowercase if the `fold_case` option is enabled.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
type KeyValue<K, V> = { k: K; v: V };
 | 
					type KeyValue<K, V> = { k: K; v: V };
 | 
				
			||||||
type Items<K, V> = {
 | 
					type Items<K, V> = {
 | 
				
			||||||
    [key: string]: KeyValue<K, V>;
 | 
					    [key: string]: KeyValue<K, V>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * This class primarily exists to support the fold_case option, because so many
 | 
					 | 
				
			||||||
 * string keys in Zulip are case-insensitive (emails, stream names, topics,
 | 
					 | 
				
			||||||
 * etc.). Dict also accepts any key that can be converted to a string.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export class Dict<K, V> {
 | 
					export class Dict<K, V> {
 | 
				
			||||||
    private _items: Items<K, V> = {};
 | 
					    private _items: Items<K, V> = {};
 | 
				
			||||||
    private _fold_case: boolean;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @param opts - setting `fold_case` to true will make `has()` and `get()`
 | 
					 | 
				
			||||||
     *               case-insensitive. `keys()` and other methods that
 | 
					 | 
				
			||||||
     *               implicitly return keys return the original casing/type
 | 
					 | 
				
			||||||
     *               of the key passed into `set()`.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    constructor(opts?: {fold_case: boolean}) {
 | 
					 | 
				
			||||||
        this._fold_case = opts ? opts.fold_case : false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Constructs a Dict object from an existing object's keys and values.
 | 
					     * Constructs a Dict object from an existing object's keys and values.
 | 
				
			||||||
     * @param obj - A javascript object
 | 
					     * @param obj - A javascript object
 | 
				
			||||||
     * @param opts - Options to be passed to the Dict constructor
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static from<V>(obj: { [key: string]: V }, opts?: {fold_case: boolean}): Dict<string, V> {
 | 
					    static from<V>(obj: { [key: string]: V }): Dict<string, V> {
 | 
				
			||||||
        if (typeof obj !== "object" || obj === null) {
 | 
					        if (typeof obj !== "object" || obj === null) {
 | 
				
			||||||
            throw new TypeError("Cannot convert argument to Dict");
 | 
					            throw new TypeError("Cannot convert argument to Dict");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const dict = new Dict<string, V>(opts);
 | 
					        const dict = new Dict<string, V>();
 | 
				
			||||||
        _.each(obj, function (val: V, key: string) {
 | 
					        _.each(obj, function (val: V, key: string) {
 | 
				
			||||||
            dict.set(key, val);
 | 
					            dict.set(key, val);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -50,14 +29,13 @@ export class Dict<K, V> {
 | 
				
			|||||||
     * Construct a Dict object from an array with each element set to `true`.
 | 
					     * Construct a Dict object from an array with each element set to `true`.
 | 
				
			||||||
     * Intended for use as a set data structure.
 | 
					     * Intended for use as a set data structure.
 | 
				
			||||||
     * @param arr - An array of keys
 | 
					     * @param arr - An array of keys
 | 
				
			||||||
     * @param opts - Options to be passed to the Dict constructor
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static from_array<K, V>(arr: K[], opts?: {fold_case: boolean}): Dict<K, V | true> {
 | 
					    static from_array<K, V>(arr: K[]): Dict<K, V | true> {
 | 
				
			||||||
        if (!(arr instanceof Array)) {
 | 
					        if (!(arr instanceof Array)) {
 | 
				
			||||||
            throw new TypeError("Argument is not an array");
 | 
					            throw new TypeError("Argument is not an array");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const dict = new Dict<K, V | true>(opts);
 | 
					        const dict = new Dict<K, V | true>();
 | 
				
			||||||
        for (const key of arr) {
 | 
					        for (const key of arr) {
 | 
				
			||||||
            dict.set(key, true);
 | 
					            dict.set(key, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -65,7 +43,7 @@ export class Dict<K, V> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    clone(): Dict<K, V> {
 | 
					    clone(): Dict<K, V> {
 | 
				
			||||||
        const dict = new Dict<K, V>({fold_case: this._fold_case});
 | 
					        const dict = new Dict<K, V>();
 | 
				
			||||||
        dict._items = { ...this._items };
 | 
					        dict._items = { ...this._items };
 | 
				
			||||||
        return dict;
 | 
					        return dict;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -144,13 +122,13 @@ export class Dict<K, V> {
 | 
				
			|||||||
        this._items = {};
 | 
					        this._items = {};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Handle case-folding of keys and the empty string.
 | 
					    // Convert keys to strings and handle undefined.
 | 
				
			||||||
    private _munge(key: K): string | undefined {
 | 
					    private _munge(key: K): string | undefined {
 | 
				
			||||||
        if (key === undefined) {
 | 
					        if (key === undefined) {
 | 
				
			||||||
            blueslip.error("Tried to call a Dict method with an undefined key.");
 | 
					            blueslip.error("Tried to call a Dict method with an undefined key.");
 | 
				
			||||||
            return undefined;
 | 
					            return undefined;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const str_key = ':' + key.toString();
 | 
					        const str_key = ':' + key.toString();
 | 
				
			||||||
        return this._fold_case ? str_key.toLowerCase() : str_key;
 | 
					        return str_key;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										85
									
								
								static/js/fold_dict.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								static/js/fold_dict.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					import * as _ from 'underscore';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					    Use this class to manage keys where you don't care
 | 
				
			||||||
 | 
					    about case (i.e. case-insensitive).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Keys for FoldDict should be strings.  We "fold" all
 | 
				
			||||||
 | 
					    casings of "alice" (e.g. "ALICE", "Alice", "ALIce", etc.)
 | 
				
			||||||
 | 
					    to "alice" as the key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Examples of case-insensitive data in Zulip are:
 | 
				
			||||||
 | 
					        - emails
 | 
				
			||||||
 | 
					        - stream names
 | 
				
			||||||
 | 
					        - topics
 | 
				
			||||||
 | 
					        - etc.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					type KeyValue<V> = { k: string; v: V };
 | 
				
			||||||
 | 
					type Items<V> = {
 | 
				
			||||||
 | 
					    [key: string]: KeyValue<V>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FoldDict<V> {
 | 
				
			||||||
 | 
					    private _items: Items<V> = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get(key: string): V | undefined {
 | 
				
			||||||
 | 
					        const mapping = this._items[this._munge(key)];
 | 
				
			||||||
 | 
					        if (mapping === undefined) {
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return mapping.v;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set(key: string, value: V): V {
 | 
				
			||||||
 | 
					        this._items[this._munge(key)] = {k: key, v: value};
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    has(key: string): boolean {
 | 
				
			||||||
 | 
					        return _.has(this._items, this._munge(key));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    del(key: string): void {
 | 
				
			||||||
 | 
					        delete this._items[this._munge(key)];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    keys(): string[] {
 | 
				
			||||||
 | 
					        return _.pluck(_.values(this._items), 'k');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    values(): V[] {
 | 
				
			||||||
 | 
					        return _.pluck(_.values(this._items), 'v');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    items(): [string, V][] {
 | 
				
			||||||
 | 
					        return _.map(_.values(this._items),
 | 
				
			||||||
 | 
					            (mapping: KeyValue<V>): [string, V] => [mapping.k, mapping.v]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    num_items(): number {
 | 
				
			||||||
 | 
					        return _.keys(this._items).length;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    is_empty(): boolean {
 | 
				
			||||||
 | 
					        return _.isEmpty(this._items);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    each(f: (v: V, k?: string) => void): void {
 | 
				
			||||||
 | 
					        _.each(this._items, (mapping: KeyValue<V>) => f(mapping.v, mapping.k));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clear(): void {
 | 
				
			||||||
 | 
					        this._items = {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Handle case-folding of keys and the empty string.
 | 
				
			||||||
 | 
					    private _munge(key: string): string | undefined {
 | 
				
			||||||
 | 
					        if (key === undefined) {
 | 
				
			||||||
 | 
					            blueslip.error("Tried to call a FoldDict method with an undefined key.");
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const str_key = ':' + key.toString().toLowerCase();
 | 
				
			||||||
 | 
					        return str_key;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let muted_topics = new Dict();
 | 
					let muted_topics = new Dict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports.add_muted_topic = function (stream_id, topic) {
 | 
					exports.add_muted_topic = function (stream_id, topic) {
 | 
				
			||||||
    let sub_dict = muted_topics.get(stream_id);
 | 
					    let sub_dict = muted_topics.get(stream_id);
 | 
				
			||||||
    if (!sub_dict) {
 | 
					    if (!sub_dict) {
 | 
				
			||||||
        sub_dict = new Dict({fold_case: true});
 | 
					        sub_dict = new FoldDict();
 | 
				
			||||||
        muted_topics.set(stream_id, sub_dict);
 | 
					        muted_topics.set(stream_id, sub_dict);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    sub_dict.set(topic, true);
 | 
					    sub_dict.set(topic, true);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
require("unorm");  // String.prototype.normalize polyfill for IE11
 | 
					require("unorm");  // String.prototype.normalize polyfill for IE11
 | 
				
			||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let people_dict;
 | 
					let people_dict;
 | 
				
			||||||
let people_by_name_dict;
 | 
					let people_by_name_dict;
 | 
				
			||||||
@@ -17,8 +18,8 @@ exports.init = function () {
 | 
				
			|||||||
    // (all people we've seen), but people_dict can have duplicate
 | 
					    // (all people we've seen), but people_dict can have duplicate
 | 
				
			||||||
    // keys related to email changes.  We want to deprecate
 | 
					    // keys related to email changes.  We want to deprecate
 | 
				
			||||||
    // people_dict over time and always do lookups by user_id.
 | 
					    // people_dict over time and always do lookups by user_id.
 | 
				
			||||||
    people_dict = new Dict({fold_case: true});
 | 
					    people_dict = new FoldDict();
 | 
				
			||||||
    people_by_name_dict = new Dict({fold_case: true});
 | 
					    people_by_name_dict = new FoldDict();
 | 
				
			||||||
    people_by_user_id_dict = new Dict();
 | 
					    people_by_user_id_dict = new Dict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The next dictionary includes all active users (human/user)
 | 
					    // The next dictionary includes all active users (human/user)
 | 
				
			||||||
@@ -29,7 +30,7 @@ exports.init = function () {
 | 
				
			|||||||
    pm_recipient_count_dict = new Dict();
 | 
					    pm_recipient_count_dict = new Dict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The next Dict maintains a set of ids of people with same full names.
 | 
					    // The next Dict maintains a set of ids of people with same full names.
 | 
				
			||||||
    duplicate_full_name_data = new Dict({fold_case: true});
 | 
					    duplicate_full_name_data = new FoldDict();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WE INITIALIZE DATA STRUCTURES HERE!
 | 
					// WE INITIALIZE DATA STRUCTURES HERE!
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const partners = new Dict();
 | 
					const partners = new Dict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,7 +16,7 @@ exports.recent = (function () {
 | 
				
			|||||||
    // recent conversations with, sorted by time (implemented via
 | 
					    // recent conversations with, sorted by time (implemented via
 | 
				
			||||||
    // `message_id` sorting, since that's how we time-sort messages).
 | 
					    // `message_id` sorting, since that's how we time-sort messages).
 | 
				
			||||||
    const self = {};
 | 
					    const self = {};
 | 
				
			||||||
    const recent_message_ids = new Dict({fold_case: true}); // key is user_ids_string
 | 
					    const recent_message_ids = new FoldDict(); // key is user_ids_string
 | 
				
			||||||
    const recent_private_messages = [];
 | 
					    const recent_private_messages = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self.insert = function (user_ids, message_id) {
 | 
					    self.insert = function (user_ids, message_id) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const topic_senders = new Dict(); // key is stream-id, value is Dict
 | 
					const topic_senders = new Dict(); // key is stream-id, value is Dict
 | 
				
			||||||
const stream_senders = new Dict(); // key is stream-id, value is Dict
 | 
					const stream_senders = new Dict(); // key is stream-id, value is Dict
 | 
				
			||||||
@@ -8,7 +9,7 @@ exports.process_message_for_senders = function (message) {
 | 
				
			|||||||
    const topic = util.get_message_topic(message);
 | 
					    const topic = util.get_message_topic(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Process most recent sender to topic
 | 
					    // Process most recent sender to topic
 | 
				
			||||||
    const topic_dict = topic_senders.get(stream_id) || new Dict({fold_case: true});
 | 
					    const topic_dict = topic_senders.get(stream_id) || new FoldDict();
 | 
				
			||||||
    let sender_message_ids = topic_dict.get(topic) || new Dict();
 | 
					    let sender_message_ids = topic_dict.get(topic) || new Dict();
 | 
				
			||||||
    let old_message_id = sender_message_ids.get(message.sender_id);
 | 
					    let old_message_id = sender_message_ids.get(message.sender_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
const LazySet = require('./lazy_set').LazySet;
 | 
					const LazySet = require('./lazy_set').LazySet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const BinaryDict = function (pred) {
 | 
					const BinaryDict = function (pred) {
 | 
				
			||||||
@@ -18,8 +19,8 @@ const BinaryDict = function (pred) {
 | 
				
			|||||||
    */
 | 
					    */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const self = {};
 | 
					    const self = {};
 | 
				
			||||||
    self.trues = new Dict({fold_case: true});
 | 
					    self.trues = new FoldDict();
 | 
				
			||||||
    self.falses = new Dict({fold_case: true});
 | 
					    self.falses = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self.true_values = function () {
 | 
					    self.true_values = function () {
 | 
				
			||||||
        return self.trues.values();
 | 
					        return self.trues.values();
 | 
				
			||||||
@@ -83,13 +84,12 @@ let stream_info;
 | 
				
			|||||||
let subs_by_stream_id;
 | 
					let subs_by_stream_id;
 | 
				
			||||||
let filter_out_inactives = false;
 | 
					let filter_out_inactives = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const stream_ids_by_name = new Dict({fold_case: true});
 | 
					const stream_ids_by_name = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports.clear_subscriptions = function () {
 | 
					exports.clear_subscriptions = function () {
 | 
				
			||||||
    stream_info = new BinaryDict(function (sub) {
 | 
					    stream_info = new BinaryDict(function (sub) {
 | 
				
			||||||
        return sub.subscribed;
 | 
					        return sub.subscribed;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
    subs_by_stream_id = new Dict();
 | 
					    subs_by_stream_id = new Dict();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let stream_dict = new Dict(); // stream_id -> array of objects
 | 
					let stream_dict = new Dict(); // stream_id -> array of objects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13,7 +14,7 @@ exports.stream_has_topics = function (stream_id) {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports.topic_history = function (stream_id) {
 | 
					exports.topic_history = function (stream_id) {
 | 
				
			||||||
    const topics = new Dict({fold_case: true});
 | 
					    const topics = new FoldDict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const self = {};
 | 
					    const self = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
const render_more_topics = require('../templates/more_topics.hbs');
 | 
					const render_more_topics = require('../templates/more_topics.hbs');
 | 
				
			||||||
const render_topic_list_item = require('../templates/topic_list_item.hbs');
 | 
					const render_topic_list_item = require('../templates/topic_list_item.hbs');
 | 
				
			||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
    Track all active widgets with a Dict.
 | 
					    Track all active widgets with a Dict.
 | 
				
			||||||
@@ -84,7 +85,7 @@ exports.widget = function (parent_elem, my_stream_id) {
 | 
				
			|||||||
    const self = {};
 | 
					    const self = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self.build_list = function () {
 | 
					    self.build_list = function () {
 | 
				
			||||||
        self.topic_items = new Dict({fold_case: true});
 | 
					        self.topic_items = new FoldDict();
 | 
				
			||||||
        let topics_selected = 0;
 | 
					        let topics_selected = 0;
 | 
				
			||||||
        let more_topics_unreads = 0;
 | 
					        let more_topics_unreads = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// See https://zulip.readthedocs.io/en/latest/subsystems/pointer.html for notes on
 | 
					// See https://zulip.readthedocs.io/en/latest/subsystems/pointer.html for notes on
 | 
				
			||||||
// how this system is designed.
 | 
					// how this system is designed.
 | 
				
			||||||
@@ -65,8 +66,7 @@ const unread_messages = make_id_set();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function make_bucketer(options) {
 | 
					function make_bucketer(options) {
 | 
				
			||||||
    const self = {};
 | 
					    const self = {};
 | 
				
			||||||
 | 
					    const key_to_bucket = options.fold_case ? new FoldDict() : new Dict();
 | 
				
			||||||
    const key_to_bucket = new Dict({fold_case: options.fold_case});
 | 
					 | 
				
			||||||
    const reverse_lookup = new Dict();
 | 
					    const reverse_lookup = new Dict();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self.clear = function () {
 | 
					    self.clear = function () {
 | 
				
			||||||
@@ -273,7 +273,7 @@ exports.unread_topic_counter = (function () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    function str_dict() {
 | 
					    function str_dict() {
 | 
				
			||||||
        // Use this when keys are topics
 | 
					        // Use this when keys are topics
 | 
				
			||||||
        return new Dict({fold_case: true});
 | 
					        return new FoldDict();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function num_dict() {
 | 
					    function num_dict() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
const Dict = require('./dict').Dict;
 | 
					const Dict = require('./dict').Dict;
 | 
				
			||||||
 | 
					const FoldDict = require('./fold_dict').FoldDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let user_group_name_dict;
 | 
					let user_group_name_dict;
 | 
				
			||||||
let user_group_by_id_dict;
 | 
					let user_group_by_id_dict;
 | 
				
			||||||
@@ -6,7 +7,7 @@ let user_group_by_id_dict;
 | 
				
			|||||||
// We have an init() function so that our automated tests
 | 
					// We have an init() function so that our automated tests
 | 
				
			||||||
// can easily clear data.
 | 
					// can easily clear data.
 | 
				
			||||||
exports.init = function () {
 | 
					exports.init = function () {
 | 
				
			||||||
    user_group_name_dict = new Dict({fold_case: true});
 | 
					    user_group_name_dict = new FoldDict();
 | 
				
			||||||
    user_group_by_id_dict = new Dict();
 | 
					    user_group_by_id_dict = new Dict();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,7 @@ enforce_fully_covered = {
 | 
				
			|||||||
    'static/js/fenced_code.js',
 | 
					    'static/js/fenced_code.js',
 | 
				
			||||||
    'static/js/fetch_status.js',
 | 
					    'static/js/fetch_status.js',
 | 
				
			||||||
    'static/js/filter.js',
 | 
					    'static/js/filter.js',
 | 
				
			||||||
 | 
					    'static/js/fold_dict.ts',
 | 
				
			||||||
    'static/js/hash_util.js',
 | 
					    'static/js/hash_util.js',
 | 
				
			||||||
    'static/js/keydown_util.js',
 | 
					    'static/js/keydown_util.js',
 | 
				
			||||||
    'static/js/input_pill.js',
 | 
					    'static/js/input_pill.js',
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user