<commit_before>var subs = (function () {

var exports = {};

var stream_info = {}; // Maps lowercase stream name to stream properties object
// We fetch the stream colors asynchronous while the message feed is
// getting constructed, so we may need to go back and color streams
// that have already been rendered.
var initial_color_fetch = true;

var default_color = "#c2c2c2";
// Auto-assigned colors should be from the default palette so it's easy to undo
// changes, so if that pallete changes, change these colors.
var stream_assignment_colors = ["#76ce90", "#fae589", "#a6c7e5", "#e79ab5",
                                "#bfd56f", "#f4ae55", "#b0a5fd", "#addfe5",
                                "#f5ce6e", "#c2726a", "#94c849", "#bd86e5",
                                "#ee7e4a", "#a6dcbf", "#95a5fd", "#53a063",
                                "#9987e1", "#e4523d", "#c2c2c2", "#4f8de4",
                                "#c6a8ad", "#e7cc4d", "#c8bebf", "#a47462"];

var next_sub_id = 0;

function add_sub(stream_name, sub) {
    stream_info[stream_name.toLowerCase()] = sub;
}

function get_sub(stream_name) {
    return stream_info[stream_name.toLowerCase()];
}

// Classes which could be returned by get_color_class.
exports.color_classes = 'dark_background';

function pick_color() {
    var my_streams = exports.subscribed_streams();
    var used_colors = my_streams.map(function (stream_name) {
        return exports.get_color(stream_name);
    });

    var available_colors = stream_assignment_colors.filter(function (color) {
        return ($.inArray(color, used_colors) === -1);
    });

    if (available_colors.length === 0) {
        // We've used all the palette colors, so start re-using them.
        return stream_assignment_colors[my_streams.length % stream_assignment_colors.length];
    }

    return available_colors[0];
}

exports.subscribed_streams = function () {
    // TODO: Object.keys() compatibility
    var list = [];
    $.each(Object.keys(stream_info), function (idx, key) {
        var sub = stream_info[key];
        if (sub.subscribed) {
            list.push(sub.name);
        }
    });
    list.sort();
    return list;
};

exports.maybe_toggle_all_messages = function () {
    var show_all_messages = false;
    $.each(stream_info, function (idx, stream) {
        if (!stream.in_home_view) {
            show_all_messages = true;
            return false;
        }
    });

    var all_messages = $("#global_filters [data-name='all']")[0];
    if (!show_all_messages) {
        $(all_messages).addClass('hidden-filter');
    } else {
        $(all_messages).removeClass('hidden-filter');
    }
};

function should_render_subscribers() {
    return page_params.domain !== 'mit.edu';
}

function should_list_all_streams() {
    return page_params.domain !== 'mit.edu';
}

function update_table_stream_color(table, stream_name, color) {
    var color_class = exports.get_color_class(color);
    function fixup(elem) {
        elem.css("background-color", color)
            .removeClass(exports.color_classes)
            .addClass(color_class);
    }

    $.each(table.find(".stream_label"), function () {
        if ($(this).text() === stream_name) {
            var parent_label = $(this).parent("td");
            fixup(parent_label);
            fixup(parent_label.prev("td"));
        }
    });
}

exports.stream_id = function(stream_name) {
    var sub = get_sub(stream_name);
    return parseInt(sub.id, 10);
};

function update_stream_sidebar_swatch_color(stream_name, color) {
    var id = exports.stream_id(stream_name);
    $("#stream_sidebar_swatch_" + id).css('background-color', color);
}

function update_historical_message_color(stream_name, color) {
    update_table_stream_color($(".focused_table"), stream_name, color);
    if ($(".focused_table").attr("id") !== "#zhome") {
        update_table_stream_color($("#zhome"), stream_name, color);
    }
}

function update_stream_color(stream_name, color, opts) {
    opts = $.extend({}, {update_historical: false}, opts);
    var sub = get_sub(stream_name);
    sub.color = color;
    var id = parseInt(sub.id, 10);
    $("#subscription_" + id + " .color_swatch").css('background-color', color);
    if (opts.update_historical) {
        update_historical_message_color(stream_name, color);
    }
    update_stream_sidebar_swatch_color(stream_name, color);
}

function set_stream_property(stream_name, property, value) {
    $.ajax({
        type:     'POST',
        url:      '/json/subscriptions/property',
        dataType: 'json',
        data: {
            "property": property,
            "stream_name": stream_name,
            "value": value
        },
        timeout:  10*1000
    });
}

function stream_home_view_clicked(e) {
    var sub_row = $(e.target).closest('.subscription_row');
    var stream = sub_row.find('.subscription_name').text();

    var sub = get_sub(stream);
    sub.in_home_view = ! sub.in_home_view;

    setTimeout(function () {
        home_msg_list.clear({clear_selected_id: false});

        // Remember the scroll position as the adding or removing this
        // number of rows might cause the page to scroll in unexpected ways
        var saved_ypos = window.scrollY;

        // Recreate the home_msg_list with the newly filtered all_msg_list
        add_messages(all_msg_list.all(), home_msg_list);

        // Ensure we're still at the same scroll position
        window.scrollTo(0, saved_ypos);

        // In case we added messages to what's visible in the home view, we need to re-scroll to make
        // sure the pointer is still visible. We don't want the auto-scroll handler to move our pointer
        // to the old scroll location before we have a chance to update it.
        recenter_pointer_on_display = true;
        suppress_scroll_pointer_update = true;

        if (! home_msg_list.empty()) {
            process_loaded_for_unread(home_msg_list.all());
        }
    }, 0);

    exports.maybe_toggle_all_messages();
    set_stream_property(stream, 'in_home_view', sub.in_home_view);
}

function stream_notifications_clicked(e) {
    var sub_row = $(e.target).closest('.subscription_row');
    var stream = sub_row.find('.subscription_name').text();

    var sub = get_sub(stream);
    sub.notifications = ! sub.notifications;
    set_stream_property(stream, 'notifications', sub.notifications);
}

function set_color(stream_name, color) {
    update_stream_color(stream_name, color, {update_historical: true});
    set_stream_property(stream_name, 'color', color);
}

var colorpicker_options = {
    clickoutFiresChange: true,
    showPalette: true,
    palette: [
        ['a47462', 'c2726a', 'e4523d', 'e7664d', 'ee7e4a', 'f4ae55'],
        ['76ce90', '53a063', '94c849', 'bfd56f', 'fae589', 'f5ce6e'],
        ['a6dcbf', 'addfe5', 'a6c7e5', '4f8de4', '95a5fd', 'b0a5fd'],
        ['c2c2c2', 'c8bebf', 'c6a8ad', 'e79ab5', 'bd86e5', '9987e1']
    ],
    change: function (color) {
        // TODO: Kind of a hack.
        var sub_row = $(this).closest('.subscription_row');
        var stream_name = sub_row.find('.subscription_name').text();
        var hex_color = color.toHexString();
        set_color(stream_name, hex_color);
    }
};

function create_sub(stream_name, attrs) {
    var sub = get_sub(stream_name);
    if (sub !== undefined) {
        // We've already created this subscription, no need to continue.
        return sub;
    }

    sub = $.extend({}, {name: stream_name, color: pick_color(), id: next_sub_id++,
                        render_subscribers: should_render_subscribers(),
                        subscribed: true, in_home_view: true, invite_only: false,
                        notifications: false}, attrs);

    add_sub(stream_name, sub);
    if (sub.subscribed) {
        // This will do nothing on MIT
        ui.add_narrow_filter(stream_name, "stream", "#narrow/stream/" + encodeURIComponent(stream_name));
    }
    return sub;
}

function button_for_sub(sub) {
    var id = parseInt(sub.id, 10);
    return $("#subscription_" + id + " .sub_unsub_button");
}

function settings_for_sub(sub) {
    var id = parseInt(sub.id, 10);
    return $("#subscription_settings_" + id);
}

function add_sub_to_table(sub) {
    $('#create_stream_row').after(
        templates.render('subscription', {subscriptions: [sub]}));
    settings_for_sub(sub).collapse('show');
}

function format_member_list_elem(name, email) {
    return name + ' <' + email + '>';
}

function add_to_member_list(ul, name, email) {
    var member;
    if (email === undefined) {
        member = name;
    } else {
        member = format_member_list_elem(name, email);
    }
    $('<li>').prependTo(ul).text(member);
}

function mark_subscribed(stream_name, attrs) {
    var sub = get_sub(stream_name);

    if (sub === undefined) {
        // Create a new stream.
        sub = create_sub(stream_name, attrs);
        add_sub_to_table(sub);
    } else if (! sub.subscribed) {
        // Add yourself to an existing stream.
        sub.subscribed = true;
        set_color(stream_name, pick_color());
        // This will do nothing on MIT
        ui.add_narrow_filter(stream_name, "stream", "#narrow/stream/" + encodeURIComponent(stream_name));
        var button = button_for_sub(sub);
        if (button.length !== 0) {
            button.text("Unsubscribe").removeClass("btn-primary");
        } else {
            add_sub_to_table(sub);
        }

        // Add the user to the member list if they're currently
        // viewing the members of this stream
        var settings = settings_for_sub(sub);
        if (sub.render_subscribers && settings.hasClass('in')) {
            var members = settings.find(".subscriber_list_container ul");
            add_to_member_list(members, page_params.fullname, page_params.email);
        }

        // Display the swatch and subscription settings
        var sub_row = settings.closest('.subscription_row');
        sub_row.find(".color_swatch").addClass('in');
        sub_row.find(".regular_subscription_settings").collapse('show');
    } else {
        // Already subscribed
        return;
    }

    if (current_msg_list.narrowed) {
        current_msg_list.update_trailing_bookend();
    }

    // Update unread counts as the new stream in sidebar might
    // need its unread counts re-calculated
    process_loaded_for_unread(all_msg_list.all());

    typeahead_helper.update_autocomplete();
}

function mark_unsubscribed(stream_name) {
    var sub = get_sub(stream_name);

    ui.remove_narrow_filter(stream_name, 'stream');

    if (sub === undefined) {
        // We don't know about this stream
        return;
    } else if (sub.subscribed) {
        sub.subscribed = false;
        button_for_sub(sub).text("Subscribe").addClass("btn-primary");
        var settings = settings_for_sub(sub);
        if (settings.hasClass('in')) {
            settings.collapse('hide');
        }

        // Hide the swatch and subscription settings
        var sub_row = settings.closest('.subscription_row');
        sub_row.find(".color_swatch").removeClass('in');
        if (sub.render_subscribers) {
            // TODO: having a completely empty settings div messes
            // with Bootstrap's collapser.  We currently just ensure
            // that it's not empty on the MIT realm, even though it
            // looks weird
            sub_row.find(".regular_subscription_settings").collapse('hide');
        }
    } else {
        // Already unsubscribed
        return;
    }

    if (current_msg_list.narrowed) {
        current_msg_list.update_trailing_bookend();
    }

    typeahead_helper.update_autocomplete();
}

$(function () {
    $(document).on('subscription_add.zephyr', function (e) {
        mark_subscribed(e.subscription.name, e.subscription);
    });
    $(document).on('subscription_remove.zephyr', function (e) {
        mark_unsubscribed(e.subscription.name);
    });

    $(document).on('click', '.subs_set_all_users', function (e) {
        $('#people_to_add :checkbox').attr('checked', true);
        e.preventDefault();
    });
    $(document).on('click', '.subs_unset_all_users', function (e) {
        $('#people_to_add :checkbox').attr('checked', false);
        e.preventDefault();
    });
});

exports.get_color = function (stream_name) {
    var sub = get_sub(stream_name);
    if (sub === undefined) {
        return default_color;
    }
    return sub.color;
};

var lightness_threshold;
$(function () {
    // sRGB color component for dark label text.
    // 0x33 to match the color #333333 set by Bootstrap.
    var label_color = 0x33;
    var lightness = colorspace.luminance_to_lightness(
        colorspace.sRGB_to_linear(label_color));

    // Compute midpoint lightness between that and white (100).
    lightness_threshold = (lightness + 100) / 2;
});

// From a background color (in format "#fff" or "#ffffff")
// pick a CSS class (or empty string) to determine the
// text label color etc.
//
// It would be better to work with an actual data structure
// rather than a hex string, but we have to deal with values
// already saved on the server, etc.
//
// This gets called on every message, so cache the results.
exports.get_color_class = util.memoize(function (color) {
    var match, i, lightness, channel = [0, 0, 0], mult = 1;

    match = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/.exec(color);
    if (!match) {
        // 3-digit shorthand; Spectrum gives this e.g. for pure black.
        // Multiply each digit by 16+1.
        mult = 17;

        match = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])$/.exec(color);
        if (!match) {
            // Can't understand color.
            return '';
        }
    }

    // CSS colors are specified in the sRGB color space.
    // Convert to linear intensity values.
    for (i=0; i<3; i++) {
        channel[i] = colorspace.sRGB_to_linear(mult * parseInt(match[i+1], 16));
    }

    // Compute perceived lightness as CIE L*.
    lightness = colorspace.luminance_to_lightness(
        colorspace.rgb_luminance(channel));

    // Determine if we're past the midpoint between the
    // dark and light label lightness.
    return (lightness < lightness_threshold) ? 'dark_background' : '';
});

exports.get_invite_only = function (stream_name) {
    var sub = get_sub(stream_name);
    if (sub === undefined) {
        return false;
    }
    return sub.invite_only;
};

exports.receives_notifications = function (stream_name) {
    var sub = get_sub(stream_name);
    if (sub === undefined) {
        return false;
    }
    return sub.notifications;
};

function populate_subscriptions(subs) {
    var sub_rows = [];
    subs.sort(function (a, b) {
        return a.name.localeCompare(b.name);
    });
    subs.forEach(function (elem) {
        var stream_name = elem.name;
        var sub = create_sub(stream_name, {color: elem.color, in_home_view: elem.in_home_view,
                                           invite_only: elem.invite_only,
                                           notifications: elem.notifications, subscribed: true});
        add_sub(stream_name, sub);
        sub_rows.push(sub);
    });

    return sub_rows;
}

exports.reload_subscriptions = function (opts) {
    var on_success;
    opts = $.extend({}, {clear_first: false, custom_callbacks: false}, opts);

    if (! opts.custom_callbacks) {
        on_success = function (data) {
                         if (data) {
                             populate_subscriptions(data.subscriptions);
                         }
                     };
    }

    if (opts.clear_first) {
        stream_info = {};
        ui.remove_all_narrow_filters();
    }

    return $.ajax({
                    type:     'POST',
                    url:      '/json/subscriptions/list',
                    dataType: 'json',
                    timeout:  10*1000,
                    success: on_success
    });
};

exports.setup_page = function () {
    util.make_loading_indicator($('#subs_page_loading_indicator'));

    function populate_and_fill(stream_data, subscription_data) {
        var all_streams = [];
        var our_subs = [];

        /* arguments are [ "success", statusText, jqXHR ] */
        if (stream_data.length > 2 && stream_data[2]) {
            var stream_response = JSON.parse(stream_data[2].responseText);
            all_streams = stream_response.streams;
        }
        if (subscription_data.length > 2 && subscription_data[2]) {
            var subs_response = JSON.parse(subscription_data[2].responseText);
            our_subs = subs_response.subscriptions;
        }

        var sub_rows = populate_subscriptions(our_subs);

        all_streams.sort();
        all_streams.forEach(function (stream) {
            if (exports.have(stream)) {
                return;
            }
            var sub = create_sub(stream, {subscribed: false});
            add_sub(stream, sub);
            sub_rows.push(sub);
        });

        $('#subscriptions_table tr:gt(0)').remove();
        $('#subscriptions_table').append(
            templates.render('subscription', {subscriptions: sub_rows}));

        util.destroy_loading_indicator($('#subs_page_loading_indicator'));
        $('#create_stream_name').focus().select();
    }
<commit_msg>subs: remove redundant call to add_sub after create_sub.<commit_after>var subs = (function () {

var exports = {};

var stream_info = {}; // Maps lowercase stream name to stream properties object
// We fetch the stream colors asynchronous while the message feed is
// getting constructed, so we may need to go back and color streams
// that have already been rendered.
var initial_color_fetch = true;

var default_color = "#c2c2c2";
// Auto-assigned colors should be from the default palette so it's easy to undo
// changes, so if that pallete changes, change these colors.
var stream_assignment_colors = ["#76ce90", "#fae589", "#a6c7e5", "#e79ab5",
                                "#bfd56f", "#f4ae55", "#b0a5fd", "#addfe5",
                                "#f5ce6e", "#c2726a", "#94c849", "#bd86e5",
                                "#ee7e4a", "#a6dcbf", "#95a5fd", "#53a063",
                                "#9987e1", "#e4523d", "#c2c2c2", "#4f8de4",
                                "#c6a8ad", "#e7cc4d", "#c8bebf", "#a47462"];

var next_sub_id = 0;

function add_sub(stream_name, sub) {
    stream_info[stream_name.toLowerCase()] = sub;
}

function get_sub(stream_name) {
    return stream_info[stream_name.toLowerCase()];
}

// Classes which could be returned by get_color_class.
exports.color_classes = 'dark_background';

function pick_color() {
    var my_streams = exports.subscribed_streams();
    var used_colors = my_streams.map(function (stream_name) {
        return exports.get_color(stream_name);
    });

    var available_colors = stream_assignment_colors.filter(function (color) {
        return ($.inArray(color, used_colors) === -1);
    });

    if (available_colors.length === 0) {
        // We've used all the palette colors, so start re-using them.
        return stream_assignment_colors[my_streams.length % stream_assignment_colors.length];
    }

    return available_colors[0];
}

exports.subscribed_streams = function () {
    // TODO: Object.keys() compatibility
    var list = [];
    $.each(Object.keys(stream_info), function (idx, key) {
        var sub = stream_info[key];
        if (sub.subscribed) {
            list.push(sub.name);
        }
    });
    list.sort();
    return list;
};

exports.maybe_toggle_all_messages = function () {
    var show_all_messages = false;
    $.each(stream_info, function (idx, stream) {
        if (!stream.in_home_view) {
            show_all_messages = true;
            return false;
        }
    });

    var all_messages = $("#global_filters [data-name='all']")[0];
    if (!show_all_messages) {
        $(all_messages).addClass('hidden-filter');
    } else {
        $(all_messages).removeClass('hidden-filter');
    }
};

function should_render_subscribers() {
    return page_params.domain !== 'mit.edu';
}

function should_list_all_streams() {
    return page_params.domain !== 'mit.edu';
}

function update_table_stream_color(table, stream_name, color) {
    var color_class = exports.get_color_class(color);
    function fixup(elem) {
        elem.css("background-color", color)
            .removeClass(exports.color_classes)
            .addClass(color_class);
    }

    $.each(table.find(".stream_label"), function () {
        if ($(this).text() === stream_name) {
            var parent_label = $(this).parent("td");
            fixup(parent_label);
            fixup(parent_label.prev("td"));
        }
    });
}

exports.stream_id = function(stream_name) {
    var sub = get_sub(stream_name);
    return parseInt(sub.id, 10);
};

function update_stream_sidebar_swatch_color(stream_name, color) {
    var id = exports.stream_id(stream_name);
    $("#stream_sidebar_swatch_" + id).css('background-color', color);
}

function update_historical_message_color(stream_name, color) {
    update_table_stream_color($(".focused_table"), stream_name, color);
    if ($(".focused_table").attr("id") !== "#zhome") {
        update_table_stream_color($("#zhome"), stream_name, color);
    }
}

function update_stream_color(stream_name, color, opts) {
    opts = $.extend({}, {update_historical: false}, opts);
    var sub = get_sub(stream_name);
    sub.color = color;
    var id = parseInt(sub.id, 10);
    $("#subscription_" + id + " .color_swatch").css('background-color', color);
    if (opts.update_historical) {
        update_historical_message_color(stream_name, color);
    }
    update_stream_sidebar_swatch_color(stream_name, color);
}

function set_stream_property(stream_name, property, value) {
    $.ajax({
        type:     'POST',
        url:      '/json/subscriptions/property',
        dataType: 'json',
        data: {
            "property": property,
            "stream_name": stream_name,
            "value": value
        },
        timeout:  10*1000
    });
}

function stream_home_view_clicked(e) {
    var sub_row = $(e.target).closest('.subscription_row');
    var stream = sub_row.find('.subscription_name').text();

    var sub = get_sub(stream);
    sub.in_home_view = ! sub.in_home_view;

    setTimeout(function () {
        home_msg_list.clear({clear_selected_id: false});

        // Remember the scroll position as the adding or removing this
        // number of rows might cause the page to scroll in unexpected ways
        var saved_ypos = window.scrollY;

        // Recreate the home_msg_list with the newly filtered all_msg_list
        add_messages(all_msg_list.all(), home_msg_list);

        // Ensure we're still at the same scroll position
        window.scrollTo(0, saved_ypos);

        // In case we added messages to what's visible in the home view, we need to re-scroll to make
        // sure the pointer is still visible. We don't want the auto-scroll handler to move our pointer
        // to the old scroll location before we have a chance to update it.
        recenter_pointer_on_display = true;
        suppress_scroll_pointer_update = true;

        if (! home_msg_list.empty()) {
            process_loaded_for_unread(home_msg_list.all());
        }
    }, 0);

    exports.maybe_toggle_all_messages();
    set_stream_property(stream, 'in_home_view', sub.in_home_view);
}

function stream_notifications_clicked(e) {
    var sub_row = $(e.target).closest('.subscription_row');
    var stream = sub_row.find('.subscription_name').text();

    var sub = get_sub(stream);
    sub.notifications = ! sub.notifications;
    set_stream_property(stream, 'notifications', sub.notifications);
}

function set_color(stream_name, color) {
    update_stream_color(stream_name, color, {update_historical: true});
    set_stream_property(stream_name, 'color', color);
}

var colorpicker_options = {
    clickoutFiresChange: true,
    showPalette: true,
    palette: [
        ['a47462', 'c2726a', 'e4523d', 'e7664d', 'ee7e4a', 'f4ae55'],
        ['76ce90', '53a063', '94c849', 'bfd56f', 'fae589', 'f5ce6e'],
        ['a6dcbf', 'addfe5', 'a6c7e5', '4f8de4', '95a5fd', 'b0a5fd'],
        ['c2c2c2', 'c8bebf', 'c6a8ad', 'e79ab5', 'bd86e5', '9987e1']
    ],
    change: function (color) {
        // TODO: Kind of a hack.
        var sub_row = $(this).closest('.subscription_row');
        var stream_name = sub_row.find('.subscription_name').text();
        var hex_color = color.toHexString();
        set_color(stream_name, hex_color);
    }
};

function create_sub(stream_name, attrs) {
    var sub = get_sub(stream_name);
    if (sub !== undefined) {
        // We've already created this subscription, no need to continue.
        return sub;
    }

    sub = $.extend({}, {name: stream_name, color: pick_color(), id: next_sub_id++,
                        render_subscribers: should_render_subscribers(),
                        subscribed: true, in_home_view: true, invite_only: false,
                        notifications: false}, attrs);

    add_sub(stream_name, sub);
    if (sub.subscribed) {
        // This will do nothing on MIT
        ui.add_narrow_filter(stream_name, "stream", "#narrow/stream/" + encodeURIComponent(stream_name));
    }
    return sub;
}

function button_for_sub(sub) {
    var id = parseInt(sub.id, 10);
    return $("#subscription_" + id + " .sub_unsub_button");
}

function settings_for_sub(sub) {
    var id = parseInt(sub.id, 10);
    return $("#subscription_settings_" + id);
}

function add_sub_to_table(sub) {
    $('#create_stream_row').after(
        templates.render('subscription', {subscriptions: [sub]}));
    settings_for_sub(sub).collapse('show');
}

function format_member_list_elem(name, email) {
    return name + ' <' + email + '>';
}

function add_to_member_list(ul, name, email) {
    var member;
    if (email === undefined) {
        member = name;
    } else {
        member = format_member_list_elem(name, email);
    }
    $('<li>').prependTo(ul).text(member);
}

function mark_subscribed(stream_name, attrs) {
    var sub = get_sub(stream_name);

    if (sub === undefined) {
        // Create a new stream.
        sub = create_sub(stream_name, attrs);
        add_sub_to_table(sub);
    } else if (! sub.subscribed) {
        // Add yourself to an existing stream.
        sub.subscribed = true;
        set_color(stream_name, pick_color());
        // This will do nothing on MIT
        ui.add_narrow_filter(stream_name, "stream", "#narrow/stream/" + encodeURIComponent(stream_name));
        var button = button_for_sub(sub);
        if (button.length !== 0) {
            button.text("Unsubscribe").removeClass("btn-primary");
        } else {
            add_sub_to_table(sub);
        }

        // Add the user to the member list if they're currently
        // viewing the members of this stream
        var settings = settings_for_sub(sub);
        if (sub.render_subscribers && settings.hasClass('in')) {
            var members = settings.find(".subscriber_list_container ul");
            add_to_member_list(members, page_params.fullname, page_params.email);
        }

        // Display the swatch and subscription settings
        var sub_row = settings.closest('.subscription_row');
        sub_row.find(".color_swatch").addClass('in');
        sub_row.find(".regular_subscription_settings").collapse('show');
    } else {
        // Already subscribed
        return;
    }

    if (current_msg_list.narrowed) {
        current_msg_list.update_trailing_bookend();
    }

    // Update unread counts as the new stream in sidebar might
    // need its unread counts re-calculated
    process_loaded_for_unread(all_msg_list.all());

    typeahead_helper.update_autocomplete();
}

function mark_unsubscribed(stream_name) {
    var sub = get_sub(stream_name);

    ui.remove_narrow_filter(stream_name, 'stream');

    if (sub === undefined) {
        // We don't know about this stream
        return;
    } else if (sub.subscribed) {
        sub.subscribed = false;
        button_for_sub(sub).text("Subscribe").addClass("btn-primary");
        var settings = settings_for_sub(sub);
        if (settings.hasClass('in')) {
            settings.collapse('hide');
        }

        // Hide the swatch and subscription settings
        var sub_row = settings.closest('.subscription_row');
        sub_row.find(".color_swatch").removeClass('in');
        if (sub.render_subscribers) {
            // TODO: having a completely empty settings div messes
            // with Bootstrap's collapser.  We currently just ensure
            // that it's not empty on the MIT realm, even though it
            // looks weird
            sub_row.find(".regular_subscription_settings").collapse('hide');
        }
    } else {
        // Already unsubscribed
        return;
    }

    if (current_msg_list.narrowed) {
        current_msg_list.update_trailing_bookend();
    }

    typeahead_helper.update_autocomplete();
}

$(function () {
    $(document).on('subscription_add.zephyr', function (e) {
        mark_subscribed(e.subscription.name, e.subscription);
    });
    $(document).on('subscription_remove.zephyr', function (e) {
        mark_unsubscribed(e.subscription.name);
    });

    $(document).on('click', '.subs_set_all_users', function (e) {
        $('#people_to_add :checkbox').attr('checked', true);
        e.preventDefault();
    });
    $(document).on('click', '.subs_unset_all_users', function (e) {
        $('#people_to_add :checkbox').attr('checked', false);
        e.preventDefault();
    });
});

exports.get_color = function (stream_name) {
    var sub = get_sub(stream_name);
    if (sub === undefined) {
        return default_color;
    }
    return sub.color;
};

var lightness_threshold;
$(function () {
    // sRGB color component for dark label text.
    // 0x33 to match the color #333333 set by Bootstrap.
    var label_color = 0x33;
    var lightness = colorspace.luminance_to_lightness(
        colorspace.sRGB_to_linear(label_color));

    // Compute midpoint lightness between that and white (100).
    lightness_threshold = (lightness + 100) / 2;
});

// From a background color (in format "#fff" or "#ffffff")
// pick a CSS class (or empty string) to determine the
// text label color etc.
//
// It would be better to work with an actual data structure
// rather than a hex string, but we have to deal with values
// already saved on the server, etc.
//
// This gets called on every message, so cache the results.
exports.get_color_class = util.memoize(function (color) {
    var match, i, lightness, channel = [0, 0, 0], mult = 1;

    match = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/.exec(color);
    if (!match) {
        // 3-digit shorthand; Spectrum gives this e.g. for pure black.
        // Multiply each digit by 16+1.
        mult = 17;

        match = /^#([\da-fA-F])([\da-fA-F])([\da-fA-F])$/.exec(color);
        if (!match) {
            // Can't understand color.
            return '';
        }
    }

    // CSS colors are specified in the sRGB color space.
    // Convert to linear intensity values.
    for (i=0; i<3; i++) {
        channel[i] = colorspace.sRGB_to_linear(mult * parseInt(match[i+1], 16));
    }

    // Compute perceived lightness as CIE L*.
    lightness = colorspace.luminance_to_lightness(
        colorspace.rgb_luminance(channel));

    // Determine if we're past the midpoint between the
    // dark and light label lightness.
    return (lightness < lightness_threshold) ? 'dark_background' : '';
});

exports.get_invite_only = function (stream_name) {
    var sub = get_sub(stream_name);
    if (sub === undefined) {
        return false;
    }
    return sub.invite_only;
};

exports.receives_notifications = function (stream_name) {
    var sub = get_sub(stream_name);
    if (sub === undefined) {
        return false;
    }
    return sub.notifications;
};

function populate_subscriptions(subs) {
    var sub_rows = [];
    subs.sort(function (a, b) {
        return a.name.localeCompare(b.name);
    });
    subs.forEach(function (elem) {
        var stream_name = elem.name;
        var sub = create_sub(stream_name, {color: elem.color, in_home_view: elem.in_home_view,
                                           invite_only: elem.invite_only,
                                           notifications: elem.notifications, subscribed: true});
        sub_rows.push(sub);
    });

    return sub_rows;
}

exports.reload_subscriptions = function (opts) {
    var on_success;
    opts = $.extend({}, {clear_first: false, custom_callbacks: false}, opts);

    if (! opts.custom_callbacks) {
        on_success = function (data) {
                         if (data) {
                             populate_subscriptions(data.subscriptions);
                         }
                     };
    }

    if (opts.clear_first) {
        stream_info = {};
        ui.remove_all_narrow_filters();
    }

    return $.ajax({
                    type:     'POST',
                    url:      '/json/subscriptions/list',
                    dataType: 'json',
                    timeout:  10*1000,
                    success: on_success
    });
};

exports.setup_page = function () {
    util.make_loading_indicator($('#subs_page_loading_indicator'));

    function populate_and_fill(stream_data, subscription_data) {
        var all_streams = [];
        var our_subs = [];

        /* arguments are [ "success", statusText, jqXHR ] */
        if (stream_data.length > 2 && stream_data[2]) {
            var stream_response = JSON.parse(stream_data[2].responseText);
            all_streams = stream_response.streams;
        }
        if (subscription_data.length > 2 && subscription_data[2]) {
            var subs_response = JSON.parse(subscription_data[2].responseText);
            our_subs = subs_response.subscriptions;
        }

        var sub_rows = populate_subscriptions(our_subs);

        all_streams.sort();
        all_streams.forEach(function (stream) {
            if (exports.have(stream)) {
                return;
            }
            var sub = create_sub(stream, {subscribed: false});
            sub_rows.push(sub);
        });

        $('#subscriptions_table tr:gt(0)').remove();
        $('#subscriptions_table').append(
            templates.render('subscription', {subscriptions: sub_rows}));

        util.destroy_loading_indicator($('#subs_page_loading_indicator'));
        $('#create_stream_name').focus().select();
    }

{'fraction_non_alphanumeric': 0.13100133202061737, 'fraction_numerical': 0.012683152834887357, 'mean_word_length': 2.649476910070802, 'pattern_counts': {'":': 6, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '86319', 'n_tokens_mistral': 10860, 'n_tokens_neox': 10563, 'n_words': 2361}