<commit_before>/* Read the Docs - Documentation promotions */

var constants = require('./constants');
var rtddata = require('./rtd-data');

var bowser = require('bowser');

var rtd;

var EXPLICIT_PLACEMENT_SELECTOR = '#ethical-ad-placement';

/*
 * Create an explicit placement if the
 */
function create_explicit_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.LEFTNAV;
    var class_name;         // Used for theme specific CSS customizations

    if(rtd.is_rtd_like_theme()) {
        class_name = 'ethical-rtd ethical-dark-theme';
    } else {
        class_name = 'ethical-alabaster';
    }

    if ($(EXPLICIT_PLACEMENT_SELECTOR).length > 0) {
        $('
').attr('id', element_id)
            .addClass(class_name).appendTo(EXPLICIT_PLACEMENT_SELECTOR);

        return {
            'div_id': element_id,
            'display_type': display_type,
        };
    }
    return null;
}

/*
 *  Creates a sidebar div where an ad could go
 */
function create_sidebar_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.LEFTNAV;
    var priority = constants.DEFAULT_PROMO_PRIORITY;
    var selector = null;
    var class_name;         // Used for theme specific CSS customizations
    var offset;

    if (rtd.is_mkdocs_builder() && rtd.is_rtd_like_theme()) {
        selector = 'nav.wy-nav-side';
        class_name = 'ethical-rtd ethical-dark-theme';
    } else if (rtd.is_rtd_like_theme()) {
        selector = 'nav.wy-nav-side > div.wy-side-scroll';
        class_name = 'ethical-rtd ethical-dark-theme';
    } else if (rtd.is_alabaster_like_theme()) {
        selector = 'div.sphinxsidebar > div.sphinxsidebarwrapper';
        class_name = 'ethical-alabaster';
    }

    if (selector) {
        $('
').attr('id', element_id)
            .addClass(class_name).appendTo(selector);

        // Determine if this element is above the fold
        offset = $('#' + element_id).offset();
        if (!offset || offset.top > $(window).height()) {
            // If this is off screen, lower the priority
            priority = constants.LOW_PROMO_PRIORITY;
        }

        return {
            'div_id': element_id,
            'display_type': display_type,
            'priority': priority,
        };
    }

    return null;
}

/*
 *  Creates a sidebar div where an ad could go
 *  Returns the ID of the div or none if no footer ad is possible
 */
function create_footer_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.FOOTER;
    var priority = constants.DEFAULT_PROMO_PRIORITY;
    var selector = null;
    var class_name;
    var offset;

    if (rtd.is_rtd_like_theme()) {
        selector = $('
').insertAfter('footer hr');
        class_name = 'ethical-rtd';
    } else if (rtd.is_alabaster_like_theme()) {
        selector = 'div.bodywrapper .body';
        class_name = 'ethical-alabaster';
    }

    if (selector) {
        $('
').attr('id', element_id)
            .addClass(class_name).appendTo(selector);

        // Determine if this element is above the fold
        offset = $('#' + element_id).offset();
        if (!offset || offset.top < $(window).height()) {
            // If the screen is short, lower the priority
            // We don't want the ad to take up too much of the screen
            priority = constants.LOW_PROMO_PRIORITY;
        }

        return {
            'div_id': element_id,
            'display_type': display_type,
            'priority': priority,
        };
    }

    return null;
}

/*
 *  Creates a fixed footer placmenet
 *  Returns the ID of the div or none if a fixed footer ad shouldn't be used
 */
function create_fixed_footer_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.FIXED_FOOTER;

    // Only propose the fixed footer ad for mobile
    if (bowser && bowser.mobile) {
        $('
').attr('id', element_id).appendTo('body');
        return {
            'div_id': element_id,
            'display_type': display_type,

            // Prioritize mobile ads when on mobile
            'priority': constants.MAXIMUM_PROMO_PRIORITY,
        };
    }

    return null;
}

function Promo(data) {
    this.id = data.id;                              // analytics id
    this.div_id = data.div_id || '';
    this.html = data.html || '';
    this.display_type = data.display_type || '';

    // Handler when a promo receives a click
    this.click_handler = function () {
        // This needs to handle both old style legacy analytics for previously built docs
        // as well as the newer universal analytics
        if (typeof ga !== 'undefined') {
            ga('rtfd.send', 'event', 'Promo', 'Click', data.id);
        } else if (typeof _gaq !== 'undefined') {
            _gaq.push(
                ['rtfd._setAccount', 'UA-17997319-1'],
                ['rtfd._trackEvent', 'Promo', 'Click', data.id]
            );
        }
    };
}

/*
 *  Position and inject the promo
 */
Promo.prototype.display = function () {
    $('#' + this.div_id).html(this.html);
    $('#' + this.div_id).find('a[href*="/sustainability/click/"]')
        .on('click', this.click_handler);

    this.post_promo_display();
};

Promo.prototype.disable = function () {
    $('#' + this.div_id).hide();
};

/*
 * Perform any additional tweaks after the ad is successfully injected
 */
Promo.prototype.post_promo_display = function () {
    if (this.display_type === constants.PROMO_TYPES.FOOTER) {
        $('
').insertAfter('#' + this.div_id);

        // Alabaster only
        $('
').insertBefore('#' + this.div_id + '.ethical-alabaster .ethical-footer');
    }
};

function detect_adblock() {
    // Status codes are not correctly reported on JSONP requests
    // So we resort to different ways to detect adblockers
    var detected = false;

    // Check if our ad element is blocked
    $('
')
        .attr('id', 'rtd-detection')
        .attr('class', 'ethical-rtd')
        .html(' ')
        .appendTo('body');
    if ($('#rtd-detection').height() === 0) {
        detected = true;
    }

    // Remove the test element regardless
    $('#rtd-detection').remove();

    return detected;
}

function adblock_admonition() {
    console.log('---------------------------------------------------------------------------------------');
    console.log('Read the Docs hosts documentation for tens of thousands of open source projects.');
    console.log('We fund our development (we are open source) and operations through advertising.');

    console.log('We promise to:');
    console.log(' - never let advertisers run 3rd party JavaScript');
    console.log(' - never sell user data to advertisers or other 3rd parties');
    console.log(' - only show advertisements of interest to developers');
    console.log('Read more about our approach to advertising here: https://docs.readthedocs.io/en/latest/ethical-advertising.html');
    console.log('%cPlease allow our Ethical Ads or go ad-free:', 'font-size: 2em');
    console.log('https://docs.readthedocs.io/en/latest/advertising/ad-blocking.html');
    console.log('--------------------------------------------------------------------------------------');
}

function adblock_nag() {
    // Place an ad block nag into the sidebar
    var placement = create_sidebar_placement();
    var unblock_url = 'https://docs.readthedocs.io/en/latest/advertising/ad-blocking.html#allowing-ethical-ads';
    var ad_free_url = 'https://readthedocs.org/sustainability/';
    var container = null;

    if (placement && placement.div_id) {
        container = $('#' + placement.div_id).attr('class', 'keep-us-sustainable');

        $('
').text('Support Read the Docs!').appendTo(container);
        $('
').html('Please help keep us sustainable by allowing our Ethical Ads in your ad blocker or go ad-free by subscribing.').appendTo(container);
        $('
').text('Thank you! \u2764\ufe0f').appendTo(container);
    }
}

function init() {
    var request_data = {format: "jsonp"};
    var div_ids = [];
    var display_types = [];
    var priorities = [];
    var placement_funcs = [
        create_footer_placement,
        create_sidebar_placement,
        create_fixed_footer_placement,
    ];
    var params;
    var placement;

    rtd = rtddata.get();

    // Check if these docs have specified an explicit ad placement for us
    placement = create_explicit_placement();

    if (placement) {
        div_ids.push(placement.div_id);
        display_types.push(placement.display_type);
        priorities.push(placement.priority || constants.DEFAULT_PROMO_PRIORITY);
    } else {
        // Standard placements
        if (!rtd.show_promo()) {
            return;
        }

        for (var i = 0; i < placement_funcs.length; i += 1) {
            placement = placement_funcs[i]();
            if (placement) {
                div_ids.push(placement.div_id);
                display_types.push(placement.display_type);
                priorities.push(placement.priority || constants.DEFAULT_PROMO_PRIORITY);
            }
        }
    }

    request_data.div_ids = div_ids.join('|');
    request_data.display_types = display_types.join('|');
    request_data.priorities = priorities.join('|');
    request_data.project = rtd.project;

    if (typeof URL !== 'undefined' && typeof URLSearchParams !== 'undefined') {
        // Force a specific promo to be displayed
        params = new URL(window.location).searchParams;
        if (params.get('force_promo')) {
            request_data.force_promo = params.get('force_promo');
        }

        // Force a promo from a specific campaign
        if (params.get('force_campaign')) {
            request_data.force_campaign = params.get('force_campaign');
        }
    }

    // Request a promo to inject onto the page
    $.ajax({
        url: rtd.api_host + "/api/v2/sustainability/",
        crossDomain: true,
        xhrFields: {
            withCredentials: true,
        },
        dataType: "jsonp",
        data: request_data,
        success: function (data) {
            var promo;
            if (data && data.div_id && data.html) {
                promo = new Promo(data);
                promo.display();
            }
        },
        error: function () {
            console.error('Error loading Read the Docs promo');

            if (!rtd.ad_free && rtd.api_host === 'https://readthedocs.org' && detect_adblock()) {
                adblock_admonition();
                adblock_nag();
            }
        },
    });
}

module.exports = {
    Promo: Promo,
    init: init,
};
<commit_msg>Complete the comment I didn't finish<commit_after>/* Read the Docs - Documentation promotions */

var constants = require('./constants');
var rtddata = require('./rtd-data');

var bowser = require('bowser');

var rtd;

var EXPLICIT_PLACEMENT_SELECTOR = '#ethical-ad-placement';

/*
 * Create an explicit placement if the project has specified one
 */
function create_explicit_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.LEFTNAV;
    var class_name;         // Used for theme specific CSS customizations

    if(rtd.is_rtd_like_theme()) {
        class_name = 'ethical-rtd ethical-dark-theme';
    } else {
        class_name = 'ethical-alabaster';
    }

    if ($(EXPLICIT_PLACEMENT_SELECTOR).length > 0) {
        $('
').attr('id', element_id)
            .addClass(class_name).appendTo(EXPLICIT_PLACEMENT_SELECTOR);

        return {
            'div_id': element_id,
            'display_type': display_type,
        };
    }
    return null;
}

/*
 *  Creates a sidebar div where an ad could go
 */
function create_sidebar_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.LEFTNAV;
    var priority = constants.DEFAULT_PROMO_PRIORITY;
    var selector = null;
    var class_name;         // Used for theme specific CSS customizations
    var offset;

    if (rtd.is_mkdocs_builder() && rtd.is_rtd_like_theme()) {
        selector = 'nav.wy-nav-side';
        class_name = 'ethical-rtd ethical-dark-theme';
    } else if (rtd.is_rtd_like_theme()) {
        selector = 'nav.wy-nav-side > div.wy-side-scroll';
        class_name = 'ethical-rtd ethical-dark-theme';
    } else if (rtd.is_alabaster_like_theme()) {
        selector = 'div.sphinxsidebar > div.sphinxsidebarwrapper';
        class_name = 'ethical-alabaster';
    }

    if (selector) {
        $('
').attr('id', element_id)
            .addClass(class_name).appendTo(selector);

        // Determine if this element is above the fold
        offset = $('#' + element_id).offset();
        if (!offset || offset.top > $(window).height()) {
            // If this is off screen, lower the priority
            priority = constants.LOW_PROMO_PRIORITY;
        }

        return {
            'div_id': element_id,
            'display_type': display_type,
            'priority': priority,
        };
    }

    return null;
}

/*
 *  Creates a sidebar div where an ad could go
 *  Returns the ID of the div or none if no footer ad is possible
 */
function create_footer_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.FOOTER;
    var priority = constants.DEFAULT_PROMO_PRIORITY;
    var selector = null;
    var class_name;
    var offset;

    if (rtd.is_rtd_like_theme()) {
        selector = $('
').insertAfter('footer hr');
        class_name = 'ethical-rtd';
    } else if (rtd.is_alabaster_like_theme()) {
        selector = 'div.bodywrapper .body';
        class_name = 'ethical-alabaster';
    }

    if (selector) {
        $('
').attr('id', element_id)
            .addClass(class_name).appendTo(selector);

        // Determine if this element is above the fold
        offset = $('#' + element_id).offset();
        if (!offset || offset.top < $(window).height()) {
            // If the screen is short, lower the priority
            // We don't want the ad to take up too much of the screen
            priority = constants.LOW_PROMO_PRIORITY;
        }

        return {
            'div_id': element_id,
            'display_type': display_type,
            'priority': priority,
        };
    }

    return null;
}

/*
 *  Creates a fixed footer placmenet
 *  Returns the ID of the div or none if a fixed footer ad shouldn't be used
 */
function create_fixed_footer_placement() {
    var element_id = 'rtd-' + (Math.random() + 1).toString(36).substring(4);
    var display_type = constants.PROMO_TYPES.FIXED_FOOTER;

    // Only propose the fixed footer ad for mobile
    if (bowser && bowser.mobile) {
        $('
').attr('id', element_id).appendTo('body');
        return {
            'div_id': element_id,
            'display_type': display_type,

            // Prioritize mobile ads when on mobile
            'priority': constants.MAXIMUM_PROMO_PRIORITY,
        };
    }

    return null;
}

function Promo(data) {
    this.id = data.id;                              // analytics id
    this.div_id = data.div_id || '';
    this.html = data.html || '';
    this.display_type = data.display_type || '';

    // Handler when a promo receives a click
    this.click_handler = function () {
        // This needs to handle both old style legacy analytics for previously built docs
        // as well as the newer universal analytics
        if (typeof ga !== 'undefined') {
            ga('rtfd.send', 'event', 'Promo', 'Click', data.id);
        } else if (typeof _gaq !== 'undefined') {
            _gaq.push(
                ['rtfd._setAccount', 'UA-17997319-1'],
                ['rtfd._trackEvent', 'Promo', 'Click', data.id]
            );
        }
    };
}

/*
 *  Position and inject the promo
 */
Promo.prototype.display = function () {
    $('#' + this.div_id).html(this.html);
    $('#' + this.div_id).find('a[href*="/sustainability/click/"]')
        .on('click', this.click_handler);

    this.post_promo_display();
};

Promo.prototype.disable = function () {
    $('#' + this.div_id).hide();
};

/*
 * Perform any additional tweaks after the ad is successfully injected
 */
Promo.prototype.post_promo_display = function () {
    if (this.display_type === constants.PROMO_TYPES.FOOTER) {
        $('
').insertAfter('#' + this.div_id);

        // Alabaster only
        $('
').insertBefore('#' + this.div_id + '.ethical-alabaster .ethical-footer');
    }
};

function detect_adblock() {
    // Status codes are not correctly reported on JSONP requests
    // So we resort to different ways to detect adblockers
    var detected = false;

    // Check if our ad element is blocked
    $('
')
        .attr('id', 'rtd-detection')
        .attr('class', 'ethical-rtd')
        .html(' ')
        .appendTo('body');
    if ($('#rtd-detection').height() === 0) {
        detected = true;
    }

    // Remove the test element regardless
    $('#rtd-detection').remove();

    return detected;
}

function adblock_admonition() {
    console.log('---------------------------------------------------------------------------------------');
    console.log('Read the Docs hosts documentation for tens of thousands of open source projects.');
    console.log('We fund our development (we are open source) and operations through advertising.');

    console.log('We promise to:');
    console.log(' - never let advertisers run 3rd party JavaScript');
    console.log(' - never sell user data to advertisers or other 3rd parties');
    console.log(' - only show advertisements of interest to developers');
    console.log('Read more about our approach to advertising here: https://docs.readthedocs.io/en/latest/ethical-advertising.html');
    console.log('%cPlease allow our Ethical Ads or go ad-free:', 'font-size: 2em');
    console.log('https://docs.readthedocs.io/en/latest/advertising/ad-blocking.html');
    console.log('--------------------------------------------------------------------------------------');
}

function adblock_nag() {
    // Place an ad block nag into the sidebar
    var placement = create_sidebar_placement();
    var unblock_url = 'https://docs.readthedocs.io/en/latest/advertising/ad-blocking.html#allowing-ethical-ads';
    var ad_free_url = 'https://readthedocs.org/sustainability/';
    var container = null;

    if (placement && placement.div_id) {
        container = $('#' + placement.div_id).attr('class', 'keep-us-sustainable');

        $('
').text('Support Read the Docs!').appendTo(container);
        $('
').html('Please help keep us sustainable by allowing our Ethical Ads in your ad blocker or go ad-free by subscribing.').appendTo(container);
        $('
').text('Thank you! \u2764\ufe0f').appendTo(container);
    }
}

function init() {
    var request_data = {format: "jsonp"};
    var div_ids = [];
    var display_types = [];
    var priorities = [];
    var placement_funcs = [
        create_footer_placement,
        create_sidebar_placement,
        create_fixed_footer_placement,
    ];
    var params;
    var placement;

    rtd = rtddata.get();

    // Check if these docs have specified an explicit ad placement for us
    placement = create_explicit_placement();

    if (placement) {
        div_ids.push(placement.div_id);
        display_types.push(placement.display_type);
        priorities.push(placement.priority || constants.DEFAULT_PROMO_PRIORITY);
    } else {
        // Standard placements
        if (!rtd.show_promo()) {
            return;
        }

        for (var i = 0; i < placement_funcs.length; i += 1) {
            placement = placement_funcs[i]();
            if (placement) {
                div_ids.push(placement.div_id);
                display_types.push(placement.display_type);
                priorities.push(placement.priority || constants.DEFAULT_PROMO_PRIORITY);
            }
        }
    }

    request_data.div_ids = div_ids.join('|');
    request_data.display_types = display_types.join('|');
    request_data.priorities = priorities.join('|');
    request_data.project = rtd.project;

    if (typeof URL !== 'undefined' && typeof URLSearchParams !== 'undefined') {
        // Force a specific promo to be displayed
        params = new URL(window.location).searchParams;
        if (params.get('force_promo')) {
            request_data.force_promo = params.get('force_promo');
        }

        // Force a promo from a specific campaign
        if (params.get('force_campaign')) {
            request_data.force_campaign = params.get('force_campaign');
        }
    }

    // Request a promo to inject onto the page
    $.ajax({
        url: rtd.api_host + "/api/v2/sustainability/",
        crossDomain: true,
        xhrFields: {
            withCredentials: true,
        },
        dataType: "jsonp",
        data: request_data,
        success: function (data) {
            var promo;
            if (data && data.div_id && data.html) {
                promo = new Promo(data);
                promo.display();
            }
        },
        error: function () {
            console.error('Error loading Read the Docs promo');

            if (!rtd.ad_free && rtd.api_host === 'https://readthedocs.org' && detect_adblock()) {
                adblock_admonition();
                adblock_nag();
            }
        },
    });
}

module.exports = {
    Promo: Promo,
    init: init,
};
<|endoftext|>


<commit_before>var client_form;
onLoad.queue(function() {
    client_form = new ClientForm({valid_loginids: page.settings.get('valid_loginids')});
});

var select_user_country = function() {
    if ($('#residence').length > 0) {
        var selected_country = $('#residence').val();
        var c_config = page.settings.get('countries_list');
        if (selected_country.length > 0) {
            if (c_config[selected_country]['gaming_company'] == 'none' && c_config[selected_country]['financial_company'] == 'none') {
                selected_country = '';
            }
            $('#residence').val(selected_country).change();
        } else {
            $.ajax({
                crossDomain: true,
                url: page.url.url_for('country'),
                async: true,
                dataType: "json"
            }).done(function(response) {
                selected_country = response.country;
                if (c_config[selected_country]['gaming_company'] == 'none' && c_config[selected_country]['financial_company'] == 'none') {
                    selected_country = '';
                }
                $('#residence').val(selected_country).change();
            });
        }
    }
};

var disable_residence = function () {
    var vr_residence = page.client.residence;
    if (vr_residence.length > 0 && vr_residence == $('#residence').val()) {
        $('#residence').attr('disabled', true);
    }
};

var enable_residence_form_submit = function () {
    $('form#openAccForm').submit(function (event) {
        var field_error = false;
        $("form#openAccForm").find('p.errorfield:visible').each(function() {
            if ($(this).text().length > 0) {
                field_error = true;
                return false;
            }
        });
        if (!field_error) {
            $('#residence').removeAttr('disabled');
        }
    });
};

pjax_config_page('new_account/real', function() {
    return {
        onLoad: function() {
            client_form.on_residence_change();
            select_user_country();
            disable_residence();
            enable_residence_form_submit();
            if (page.client.is_logged_in) {
                client_form.set_virtual_email_id(page.client.email);
            }
        }
    };
});

var upgrade_investment_disabled_field = function () {
    if (page.client.is_real) {
        var fields = ['mrms', 'fname', 'lname', 'dobdd', 'dobmm', 'dobyy', 'residence', 'secretquestion', 'secretanswer'];
        fields.forEach(function (element, index, array) {
            var obj = $('#'+element);
            if (obj.length > 0) {
                $('#'+element).attr('disabled', true);
            }
        });
    } else {
        $('#residence').attr('disabled', true);
    }
};

var financial_enable_fields_form_submit = function () {
    $('form#openAccForm').submit(function (event) {
        var field_error = false;
        $("form#openAccForm").find('p.errorfield:visible').each(function() {
            if ($(this).text().length > 0) {
                field_error = true;
                return false;
            }
        });
        if (field_error) {
            return;
        }

        if (page.client.is_real) {
            var fields = ['mrms', 'fname', 'lname', 'dobdd', 'dobmm', 'dobyy', 'residence', 'secretquestion', 'secretanswer'];
            fields.forEach(function (element, index, array) {
                var obj = $('#'+element);
                if (obj.length > 0) {
                    obj.removeAttr('disabled');
                }
            });
        } else {
            $('#residence').removeAttr('disabled');
        }
    });
};

var hide_account_opening_for_risk_disclaimer = function () {
    var risk_section = $('#risk_disclaimer_section');
    if (risk_section.length > 0) {
        $('.formObject fieldset').not("#risk_disclaimer_section").hide();
    }
};

var toggle_hedging_assets = function() {
    var trading_purpose = $('#trading_purpose');
    var hedging_assets_fields = $('.hedging_assets');

    if (trading_purpose.val() === 'Hedging') {
        hedging_assets_fields.show();
    } else {
        hedging_assets_fields.hide();
    }
};

pjax_config_page('new_account/maltainvest', function() {
    return {
        onLoad: function() {
            if (!page.client.is_real) {
                client_form.on_residence_change();
                select_user_country();
            }
            upgrade_investment_disabled_field();
            financial_enable_fields_form_submit();
            hide_account_opening_for_risk_disclaimer();
        }
    };
});

pjax_config_page('new_account/japan', function() {
    return {
        onLoad: function() {
            client_form.set_idd_for_residence('jp');
            toggle_hedging_assets();
            $('#trading_purpose').on('change', function() {
                toggle_hedging_assets();
            });
        }
    };
});

pjax_config_page('user/assessment', function() {
    return {
        onLoad: function() {
            hide_account_opening_for_risk_disclaimer();
        }
    };
});
<commit_msg>minor function rename<commit_after>var client_form;
onLoad.queue(function() {
    client_form = new ClientForm({valid_loginids: page.settings.get('valid_loginids')});
});

var select_user_country = function() {
    if ($('#residence').length > 0) {
        var selected_country = $('#residence').val();
        var c_config = page.settings.get('countries_list');
        if (selected_country.length > 0) {
            if (c_config[selected_country]['gaming_company'] == 'none' && c_config[selected_country]['financial_company'] == 'none') {
                selected_country = '';
            }
            $('#residence').val(selected_country).change();
        } else {
            $.ajax({
                crossDomain: true,
                url: page.url.url_for('country'),
                async: true,
                dataType: "json"
            }).done(function(response) {
                selected_country = response.country;
                if (c_config[selected_country]['gaming_company'] == 'none' && c_config[selected_country]['financial_company'] == 'none') {
                    selected_country = '';
                }
                $('#residence').val(selected_country).change();
            });
        }
    }
};

var disable_residence = function () {
    var vr_residence = page.client.residence;
    if (vr_residence.length > 0 && vr_residence == $('#residence').val()) {
        $('#residence').attr('disabled', true);
    }
};

var enable_residence_form_submit = function () {
    $('form#openAccForm').submit(function (event) {
        var field_error = false;
        $("form#openAccForm").find('p.errorfield:visible').each(function() {
            if ($(this).text().length > 0) {
                field_error = true;
                return false;
            }
        });
        if (!field_error) {
            $('#residence').removeAttr('disabled');
        }
    });
};

pjax_config_page('new_account/real', function() {
    return {
        onLoad: function() {
            client_form.on_residence_change();
            select_user_country();
            disable_residence();
            enable_residence_form_submit();
            if (page.client.is_logged_in) {
                client_form.set_virtual_email_id(page.client.email);
            }
        }
    };
});

var upgrade_investment_disabled_field = function () {
    if (page.client.is_real) {
        var fields = ['mrms', 'fname', 'lname', 'dobdd', 'dobmm', 'dobyy', 'residence', 'secretquestion', 'secretanswer'];
        fields.forEach(function (element, index, array) {
            var obj = $('#'+element);
            if (obj.length > 0) {
                $('#'+element).attr('disabled', true);
            }
        });
    } else {
        $('#residence').attr('disabled', true);
    }
};

var financial_enable_fields_form_submit = function () {
    $('form#openAccForm').submit(function (event) {
        var field_error = false;
        $("form#openAccForm").find('p.errorfield:visible').each(function() {
            if ($(this).text().length > 0) {
                field_error = true;
                return false;
            }
        });
        if (field_error) {
            return;
        }

        if (page.client.is_real) {
            var fields = ['mrms', 'fname', 'lname', 'dobdd', 'dobmm', 'dobyy', 'residence', 'secretquestion', 'secretanswer'];
            fields.forEach(function (element, index, array) {
                var obj = $('#'+element);
                if (obj.length > 0) {
                    obj.removeAttr('disabled');
                }
            });
        } else {
            $('#residence').removeAttr('disabled');
        }
    });
};

var hide_account_opening_for_risk_disclaimer = function () {
    var risk_section = $('#risk_disclaimer_section');
    if (risk_section.length > 0) {
        $('.formObject fieldset').not("#risk_disclaimer_section").hide();
    }
};

var toggle_hedging_assets_japan = function() {
    var trading_purpose = $('#trading_purpose');
    var hedging_assets_fields = $('.hedging_assets');

    if (trading_purpose.val() === 'Hedging') {
        hedging_assets_fields.show();
    } else {
        hedging_assets_fields.hide();
    }
};

pjax_config_page('new_account/maltainvest', function() {
    return {
        onLoad: function() {
            if (!page.client.is_real) {
                client_form.on_residence_change();
                select_user_country();
            }
            upgrade_investment_disabled_field();
            financial_enable_fields_form_submit();
            hide_account_opening_for_risk_disclaimer();
        }
    };
});

pjax_config_page('new_account/japan', function() {
    return {
        onLoad: function() {
            client_form.set_idd_for_residence('jp');
            toggle_hedging_assets_japan();
            $('#trading_purpose').on('change', function() {
                toggle_hedging_assets_japan();
            });
        }
    };
});

pjax_config_page('user/assessment', function() {
    return {
        onLoad: function() {
            hide_account_opening_for_risk_disclaimer();
        }
    };
});
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import TransitionGroup from 'react-addons-transition-group';
import classnames from 'classnames';

import FontIcon from '../FontIcons';
import DatePicker from './DatePicker';

import { ESC } from '../constants/keyCodes';
import { addDate, subtractDate, DateTimeFormat } from '../utils';
import TextField from '../TextFields';
import Dialog from '../Dialogs';
import Height from '../Transitions';

export default class DatePickerContainer extends Component {
  constructor(props) {
    super(props);

    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);

    let date, value;
    const { defaultValue, DateTimeFormat, locales } = props;
    if(defaultValue) {
      date = typeof defaultValue === 'string' ? new Date(defaultValue) : defaultValue;
      value = typeof defaultValue === 'string' ? defaultValue : DateTimeFormat(locales).format(defaultValue);
    } else {
      date = new Date();
    }

    this.state = {
      value,
      isOpen: props.initiallyOpen,
      calendarDate: date,
      calendarTempDate: date,
      calendarMode: props.initialCalendarMode,
    };
  }

  static propTypes = {
    className: PropTypes.string,
    icon: PropTypes.node,
    initiallyOpen: PropTypes.bool,
    label: PropTypes.string,
    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.instanceOf(Date),
    ]),
    defaultValue: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.instanceOf(Date),
    ]),
    onChange: PropTypes.func,
    floatingLabel: PropTypes.bool,
    DateTimeFormat: PropTypes.func.isRequired,
    locales: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string),
    ]).isRequired,
    okLabel: PropTypes.string.isRequired,
    okPrimary: PropTypes.bool,
    cancelLabel: PropTypes.string.isRequired,
    cancelPrimary: PropTypes.bool,
    initialCalendarMode: PropTypes.oneOf(['calendar', 'year']),
    previousIcon: PropTypes.node.isRequired,
    nextIcon: PropTypes.node.isRequired,
    minDate: PropTypes.instanceOf(Date),
    maxDate: PropTypes.instanceOf(Date),
    autoOk: PropTypes.bool,
    initialYearsDisplayed: PropTypes.number,
    inline: PropTypes.bool,
    displayMode: PropTypes.oneOf(['landscape', 'portrait']),
  };

  static defaultProps = {
    initiallyOpen: false,
    previousIcon: <FontIcon>chevron_left</FontIcon>,
    nextIcon: <FontIcon>chevron_right</FontIcon>,
    autoOk: false,
    icon: <FontIcon>date_range</FontIcon>,
    initialYearsDisplayed: 100,
    initialCalendarMode: 'calendar',
    DateTimeFormat: DateTimeFormat,
    locales: window.navigator.userLanguage || window.navigator.language,
    okLabel: 'Ok',
    okPrimary: true,
    cancelLabel: 'Cancel',
    cancelPrimary: true,
  };

  componentWillUpdate(nextProps, nextState) {
    if(this.state.isOpen && !nextState.isOpen) {
      if(nextProps.inline) {
        window.removeEventListener('click', this.closeOnOutside);
      }

      window.removeEventListener('keydown', this.closeOnEsc);
    } else if(!this.state.isOpen && nextState.isOpen) {
      if(nextProps.inline) {
        window.addEventListener('click', this.closeOnOutside);
      }

      window.addEventListener('keydown', this.closeOnEsc);
    }
  }

  componentWillUnmount() {
    if(this.state.isOpen) {
      if(this.props.inline) {
        window.removeEventListener('click', this.closeOnOutside);
      }

      window.removeEventListener('keydown', this.closeOnEsc);
    }
  }

  closeOnEsc = (e) => {
    if((e.which || e.keyCode) === ESC) {
      this.handleCancelClick();
    }
  };

  closeOnOutside = (e) => {
    const { container } = this.refs;
    let target = e.target;
    while(target.parentNode) {
      if(target === container) { return; }
      target = target.parentNode;
    }

    this.close();
  };

  toggleOpen = () => {
    this.setState({ isOpen: !this.state.isOpen });
  };

  close = () => {
    this.setState({ isOpen: false });
  };

  handleOkClick = (e) => {
    const { DateTimeFormat, locales, onChange } = this.props;
    const value = DateTimeFormat(locales).format(this.state.calendarTempDate);
    if(typeof this.props.value !== 'undefined' && onChange) {
      onChange(value, new Date(this.state.calendarTempDate), e);
    }

    this.setState({ value, isOpen: false });
  };

  handleCancelClick = () => {
    this.setState({ calendarTempDate: this.state.calendarDate, isOpen: false });
  };

  changeCalendarMode = (calendarMode) => {
    if(this.state.calendarMode === calendarMode) { return; }

    this.setState({ calendarMode });
  };

  previousMonth = () => {
    const calendarDate = subtractDate(this.state.calendarDate, 1, 'M');
    this.setState({ calendarDate });
  };

  nextMonth = () => {
    const calendarDate = addDate(this.state.calendarDate, 1, 'M');
    this.setState({ calendarDate });
  };

  setCalendarTempDate = (calendarTempDate) => {
    const { autoOk, DateTimeFormat, locales, onChange } = this.props;

    if(autoOk) {
      const value = DateTimeFormat(locales).format(calendarTempDate);
      if(onChange && typeof this.props.value !== 'undefined') {
        onChange(value, new Date(calendarTempDate));
      }

      this.setState({
        isOpen: false,
        value,
        calendarTempDate,
      });
    } else {
      this.setState({ calendarTempDate });
    }
  };

  setCalendarTempYear = (year) => {
    const { calendarTempDate, calendarDate } = this.state;
    if(calendarTempDate.getFullYear() === year) { return; }

    const { minDate, maxDate } = this.props;
    let nextDate = new Date(calendarDate.setFullYear(year));
    let nextTemp = new Date(calendarTempDate.setFullYear(year));

    if(minDate && nextTemp < minDate) {
      nextDate = new Date(minDate);
      nextTemp = new Date(minDate);
    }

    if(maxDate && nextTemp > maxDate) {
      nextDate = new Date(maxDate);
      nextTemp = new Date(maxDate);
    }

    this.setState({
      calendarDate: nextDate,
      calendarTempDate: nextTemp,
    });
  };

  render() {
    const { label, floatingLabel, value, onChange, icon, inline, displayMode, ...props } = this.props;
    const { isOpen, ...state } = this.state;
    const pickerProps = {
      ...state,
      ...props,
      className: classnames('md-picker', displayMode, { inline, 'with-icon': inline && icon }),
      onCancelClick: this.handleCancelClick,
      onOkClick: this.handleOkClick,
      changeCalendarMode: this.changeCalendarMode,
      onPreviousClick: this.previousMonth,
      onNextClick: this.nextMonth,
      onCalendarDateClick: this.setCalendarTempDate,
      onCalendarYearClick: this.setCalendarTempYear,
    };

    let textFieldValue = typeof value === 'undefined' ? state.value : value;
    if(isOpen && inline) {
      textFieldValue = new props.DateTimeFormat(props.locale).format(state.calendarTempDate);
    }

    return (

        <TextField
          icon={icon}
          onClick={this.toggleOpen}
          label={label}
          floatingLabel={floatingLabel}
          value={textFieldValue}
          onChange={onChange}
          readOnly={true}
        />
        {inline ?
          <TransitionGroup>
            {isOpen &&
              <Height transitionEnterTimeout={150} transitionLeaveTimeout={150}>
                <DatePicker {...pickerProps} />
              </Height>
            }
          </TransitionGroup> :
          <Dialog isOpen={isOpen} close={this.close}>
            {isOpen && <DatePicker {...pickerProps} />}
          </Dialog>
        }

    );
  }
}
<commit_msg>Date Picker AutoOK Delay<commit_after>import React, { Component, PropTypes } from 'react';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import TransitionGroup from 'react-addons-transition-group';
import classnames from 'classnames';

import FontIcon from '../FontIcons';
import DatePicker from './DatePicker';

import { ESC } from '../constants/keyCodes';
import { addDate, subtractDate, DateTimeFormat } from '../utils';
import TextField from '../TextFields';
import Dialog from '../Dialogs';
import Height from '../Transitions';

export default class DatePickerContainer extends Component {
  constructor(props) {
    super(props);

    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);

    let date, value;
    const { defaultValue, DateTimeFormat, locales } = props;
    if(defaultValue) {
      date = typeof defaultValue === 'string' ? new Date(defaultValue) : defaultValue;
      value = typeof defaultValue === 'string' ? defaultValue : DateTimeFormat(locales).format(defaultValue);
    } else {
      date = new Date();
    }

    this.state = {
      value,
      isOpen: props.initiallyOpen,
      calendarDate: date,
      calendarTempDate: date,
      calendarMode: props.initialCalendarMode,
    };
  }

  static propTypes = {
    className: PropTypes.string,
    icon: PropTypes.node,
    initiallyOpen: PropTypes.bool,
    label: PropTypes.string,
    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.instanceOf(Date),
    ]),
    defaultValue: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.instanceOf(Date),
    ]),
    onChange: PropTypes.func,
    floatingLabel: PropTypes.bool,
    DateTimeFormat: PropTypes.func.isRequired,
    locales: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string),
    ]).isRequired,
    okLabel: PropTypes.string.isRequired,
    okPrimary: PropTypes.bool,
    cancelLabel: PropTypes.string.isRequired,
    cancelPrimary: PropTypes.bool,
    initialCalendarMode: PropTypes.oneOf(['calendar', 'year']),
    previousIcon: PropTypes.node.isRequired,
    nextIcon: PropTypes.node.isRequired,
    minDate: PropTypes.instanceOf(Date),
    maxDate: PropTypes.instanceOf(Date),
    autoOk: PropTypes.bool,
    initialYearsDisplayed: PropTypes.number,
    inline: PropTypes.bool,
    displayMode: PropTypes.oneOf(['landscape', 'portrait']),
  };

  static defaultProps = {
    initiallyOpen: false,
    previousIcon: <FontIcon>chevron_left</FontIcon>,
    nextIcon: <FontIcon>chevron_right</FontIcon>,
    autoOk: false,
    icon: <FontIcon>date_range</FontIcon>,
    initialYearsDisplayed: 100,
    initialCalendarMode: 'calendar',
    DateTimeFormat: DateTimeFormat,
    locales: window.navigator.userLanguage || window.navigator.language,
    okLabel: 'Ok',
    okPrimary: true,
    cancelLabel: 'Cancel',
    cancelPrimary: true,
  };

  componentWillUpdate(nextProps, nextState) {
    if(this.state.isOpen && !nextState.isOpen) {
      if(nextProps.inline) {
        window.removeEventListener('click', this.closeOnOutside);
      }

      window.removeEventListener('keydown', this.closeOnEsc);
    } else if(!this.state.isOpen && nextState.isOpen) {
      if(nextProps.inline) {
        window.addEventListener('click', this.closeOnOutside);
      }

      window.addEventListener('keydown', this.closeOnEsc);
    }
  }

  componentWillUnmount() {
    if(this.state.isOpen) {
      if(this.props.inline) {
        window.removeEventListener('click', this.closeOnOutside);
      }

      window.removeEventListener('keydown', this.closeOnEsc);
    }
  }

  closeOnEsc = (e) => {
    if((e.which || e.keyCode) === ESC) {
      this.handleCancelClick();
    }
  };

  closeOnOutside = (e) => {
    const { container } = this.refs;
    let target = e.target;
    while(target.parentNode) {
      if(target === container) { return; }
      target = target.parentNode;
    }

    this.close();
  };

  toggleOpen = () => {
    this.setState({ isOpen: !this.state.isOpen });
  };

  close = () => {
    this.setState({ isOpen: false });
  };

  handleOkClick = (e) => {
    const { DateTimeFormat, locales, onChange } = this.props;
    const value = DateTimeFormat(locales).format(this.state.calendarTempDate);
    if(typeof this.props.value !== 'undefined' && onChange) {
      onChange(value, new Date(this.state.calendarTempDate), e);
    }

    this.setState({ value, isOpen: false });
  };

  handleCancelClick = () => {
    this.setState({ calendarTempDate: this.state.calendarDate, isOpen: false });
  };

  changeCalendarMode = (calendarMode) => {
    if(this.state.calendarMode === calendarMode) { return; }

    this.setState({ calendarMode });
  };

  previousMonth = () => {
    const calendarDate = subtractDate(this.state.calendarDate, 1, 'M');
    this.setState({ calendarDate });
  };

  nextMonth = () => {
    const calendarDate = addDate(this.state.calendarDate, 1, 'M');
    this.setState({ calendarDate });
  };

  setCalendarTempDate = (calendarTempDate) => {
    const { autoOk, DateTimeFormat, locales, onChange } = this.props;

    if(autoOk) {
      const value = DateTimeFormat(locales).format(calendarTempDate);
      if(onChange && typeof this.props.value !== 'undefined') {
        onChange(value, new Date(calendarTempDate));
      }

      this.setState({
        value,
        calendarTempDate,
        // wait for date to be picked then hide
        timeout: setTimeout(() => {
          this.setState({ timeout: null, isOpen: false });
        }, 300),
      });
    } else {
      this.setState({ calendarTempDate });
    }
  };

  setCalendarTempYear = (year) => {
    const { calendarTempDate, calendarDate } = this.state;
    if(calendarTempDate.getFullYear() === year) { return; }

    const { minDate, maxDate } = this.props;
    let nextDate = new Date(calendarDate.setFullYear(year));
    let nextTemp = new Date(calendarTempDate.setFullYear(year));

    if(minDate && nextTemp < minDate) {
      nextDate = new Date(minDate);
      nextTemp = new Date(minDate);
    }

    if(maxDate && nextTemp > maxDate) {
      nextDate = new Date(maxDate);
      nextTemp = new Date(maxDate);
    }

    this.setState({
      calendarDate: nextDate,
      calendarTempDate: nextTemp,
    });
  };

  render() {
    const { label, floatingLabel, value, onChange, icon, inline, displayMode, ...props } = this.props;
    const { isOpen, ...state } = this.state;
    const pickerProps = {
      ...state,
      ...props,
      className: classnames('md-picker', displayMode, { inline, 'with-icon': inline && icon }),
      onCancelClick: this.handleCancelClick,
      onOkClick: this.handleOkClick,
      changeCalendarMode: this.changeCalendarMode,
      onPreviousClick: this.previousMonth,
      onNextClick: this.nextMonth,
      onCalendarDateClick: this.setCalendarTempDate,
      onCalendarYearClick: this.setCalendarTempYear,
    };

    let textFieldValue = typeof value === 'undefined' ? state.value : value;
    if(isOpen && inline) {
      textFieldValue = new props.DateTimeFormat(props.locale).format(state.calendarTempDate);
    }

    return (

        <TextField
          icon={icon}
          onClick={this.toggleOpen}
          label={label}
          floatingLabel={floatingLabel}
          value={textFieldValue}
          onChange={onChange}
          readOnly={true}
        />
        {inline ?
          <TransitionGroup>
            {isOpen &&
              <Height transitionEnterTimeout={150} transitionLeaveTimeout={150}>
                <DatePicker {...pickerProps} />
              </Height>
            }
          </TransitionGroup> :
          <Dialog isOpen={isOpen} close={this.close}>
            {isOpen && <DatePicker {...pickerProps} />}
          </Dialog>
        }

    );
  }
}
<|endoftext|>


<commit_before>(function ($) {

  $.fn.mediumInsert.registerAddon('embeds', {

    /**
    * Embed default options
    */

    default: {
      urlPlaceholder: 'Paste or type a link'
      //,oembedProxy: 'http://medium.iframe.ly/api/oembed?iframe=1'
    },

    /**
     * Embeds initial function
     * @return {void}
     */
    init : function (options) {
      this.options = $.extend(this.default, options);
      this.$el = $.fn.mediumInsert.insert.$el;
      this.setEmbedButtonEvents();
      this.preparePreviousEmbeds();
    },

    insertButton : function (buttonLabels) {
      var label = 'Embed';
      if (buttonLabels == 'fontawesome' || typeof buttonLabels === 'object' && !!(buttonLabels.fontawesome)) {
        label = '';
      }
	  if (typeof buttonLabels === 'object' && buttonLabels.embed) {
		  label = buttonLabels.embed;
	  }

      return '<button data-addon="embeds" data-action="add" class="medium-editor-action mediumInsert-action">' + label + '</button>';
    },

    /**
     * Add embed to $placeholder
     * @param {element} $placeholder $placeholder to add embed to
     * @return {void}
     */
    add : function ($placeholder) {
      $.fn.mediumInsert.insert.deselect();

      var formHtml = '
<input type="text" value="" placeholder="' + this.options.urlPlaceholder + '" class="mediumInsert-embedsText medium-editor-toolbar-anchor-input">
';
      $(formHtml).appendTo($placeholder.prev());
      setTimeout(function () {
        $placeholder.prev().find('input').focus();
      }, 50);

      $.fn.mediumInsert.insert.deselect();

      this.currentPlaceholder = $placeholder;
      $(".mediumInsert-embedsText").focus();
    },

    /**
    * Make existing embeds interactive
    *
    * @return {void}
    */

    preparePreviousEmbeds: function () {
      this.$el.find('.mediumInsert-embeds').each(function() {
        var $parent = $(this).parent();
        $parent.html('
' + $parent.html() + '
');
      });
    },

    setEmbedButtonEvents : function () {
      var that = this;
      $(document).on('keypress', 'input.mediumInsert-embedsText', function (e) {
        if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
          that.setEnterActionEvents();
          that.removeToolbar();
        }
      });

      this.$el.on('blur', '.mediumInsert-embedsText', function () {
        that.removeToolbar();
      });

    },
    setEnterActionEvents : function () {
      var that = this;
      if ($.fn.mediumInsert.settings.enabled === false) {
        return false;
      }

      var url = $("input.mediumInsert-embedsText").val();
      if (!url) {
        return false;
      }

        function processEmbedTag(embed_tag) {
            if (!embed_tag) {
                alert('Incorrect URL format specified');
                return false;
            } else {
                var returnedTag = embed_tag;
                var tagId = new Date().getTime();
                embed_tag = $('

').append(embed_tag);
                that.currentPlaceholder.append(embed_tag);
                that.currentPlaceholder.closest('[data-medium-element]').trigger('keyup').trigger('input');

                if(returnedTag.indexOf("facebook") != -1) {
                  if (typeof(FB) != 'undefined') {
                    setTimeout(function() { FB.XFBML.parse();}, 2000);
                  }
                }
            }
        }

        if (this.options.oembedProxy) {
          that.getOEmbedHTML(url, function(error, oebmed) {

              var html = !error && oebmed && oebmed.html;

              if (oebmed && !oebmed.html && oebmed.type === 'photo' && oebmed.url) {
                  html = '';
              }

              processEmbedTag(html);
          });
        } else {
            var embed_tag = that.convertUrlToEmbedTag(url);
            return processEmbedTag(embed_tag);
        }

    },

    removeToolbar : function () {
      $(".mediumInsert-embedsWire").remove();
    },

      getOEmbedHTML: function(url, cb) {
          $.ajax({
              url: this.options.oembedProxy,
              dataType: "json",
              data: {
                  url: url
              },
              success: function(data, textStatus, jqXHR) {
                  cb(null, data, jqXHR);
              },
              error: function(jqXHR, textStatus, errorThrown) {
                  var responseJSON = function() {
                      try {
                          return JSON.parse(jqXHR.responseText);
                      } catch(e) {}
                  }();

                  cb((responseJSON && responseJSON.error) || jqXHR.status || errorThrown.message, responseJSON, jqXHR);
              }
          });
      },

      convertUrlToEmbedTag : function (url) {
          // We didn't get something we expect so let's get out of here.
          if (!(new RegExp(['youtube', 'yout.be', 'vimeo', 'facebook', 'instagram'].join("|")).test(url))) return false;

          var embed_tag = url.replace(/\n?/g, '').replace(/^((http(s)?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/(watch\?v=|v\/)?)([a-zA-Z0-9-_]+)(.*)?$/, '

')
              .replace(/^http:\/\/vimeo\.com(\/.+)?\/([0-9]+)$/, '')
              //.replace(/^https:\/\/twitter\.com\/(\w+)\/status\/(\d+)\/?$/, '

')
              .replace(/^https:\/\/www\.facebook\.com\/(video.php|photo.php)\?v=(\d+).+$/, '

Post

')
              .replace(/^http:\/\/instagram\.com\/p\/(.+)\/?$/, '');

          return /<("[^"]*"|'[^']*'|[^'">])*>/.test(embed_tag) ? embed_tag : false;
      }

  });

}(jQuery));
<commit_msg>fix #72<commit_after>(function ($) {

  $.fn.mediumInsert.registerAddon('embeds', {

    /**
    * Embed default options
    */

    default: {
      urlPlaceholder: 'Paste or type a link'
      //,oembedProxy: 'http://medium.iframe.ly/api/oembed?iframe=1'
    },

    /**
     * Embeds initial function
     * @return {void}
     */
    init : function (options) {
      this.options = $.extend(this.default, options);
      this.$el = $.fn.mediumInsert.insert.$el;
      this.setEmbedButtonEvents();
      this.preparePreviousEmbeds();
    },

    insertButton : function (buttonLabels) {
      var label = 'Embed';
      if (buttonLabels == 'fontawesome' || typeof buttonLabels === 'object' && !!(buttonLabels.fontawesome)) {
        label = '';
      }
	  if (typeof buttonLabels === 'object' && buttonLabels.embed) {
		  label = buttonLabels.embed;
	  }

      return '<button data-addon="embeds" data-action="add" class="medium-editor-action mediumInsert-action">' + label + '</button>';
    },

    /**
     * Add embed to $placeholder
     * @param {element} $placeholder $placeholder to add embed to
     * @return {void}
     */
    add : function ($placeholder) {
      $.fn.mediumInsert.insert.deselect();

      var formHtml = '
<input type="text" value="" placeholder="' + this.options.urlPlaceholder + '" class="mediumInsert-embedsText medium-editor-toolbar-anchor-input">
';
      $(formHtml).appendTo($placeholder.prev());
      setTimeout(function () {
        $placeholder.prev().find('input').focus();
      }, 50);

      $.fn.mediumInsert.insert.deselect();

      this.currentPlaceholder = $placeholder;
      $(".mediumInsert-embedsText").focus();
    },

    /**
    * Make existing embeds interactive
    *
    * @return {void}
    */

    preparePreviousEmbeds: function () {
      this.$el.find('.mediumInsert-embeds').each(function() {
        var $parent = $(this).parent();
        $parent.html('
' + $parent.html() + '
');
      });
    },

    setEmbedButtonEvents : function () {
      var that = this;
      $(document).on('keypress', 'input.mediumInsert-embedsText', function (e) {
        if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
          that.setEnterActionEvents();
          that.removeToolbar();
        }
      });

      this.$el
        .on('blur', '.mediumInsert-embedsText', function () {
          that.removeToolbar();
        })
        // Fix #72
        // Workaround for CTRL+V not working in FF, when cleanPastedHTML and forcePlainText options on editor are set to true,
        // because editor steals the event and doesn't pass it to the plugin
        // https://github.com/orthes/medium-editor-insert-plugin/issues/72
        .on('paste', '.mediumInsert-embedsText', function (e) {
          if ($.fn.mediumInsert.insert.isFirefox && e.originalEvent.clipboardData) {
            $(this).val(e.originalEvent.clipboardData.getData('text/plain'));
          }
        });

    },
    setEnterActionEvents : function () {
      var that = this;
      if ($.fn.mediumInsert.settings.enabled === false) {
        return false;
      }

      var url = $("input.mediumInsert-embedsText").val();
      if (!url) {
        return false;
      }

        function processEmbedTag(embed_tag) {
            if (!embed_tag) {
                alert('Incorrect URL format specified');
                return false;
            } else {
                var returnedTag = embed_tag;
                var tagId = new Date().getTime();
                embed_tag = $('

').append(embed_tag);
                that.currentPlaceholder.append(embed_tag);
                that.currentPlaceholder.closest('[data-medium-element]').trigger('keyup').trigger('input');

                if(returnedTag.indexOf("facebook") != -1) {
                  if (typeof(FB) != 'undefined') {
                    setTimeout(function() { FB.XFBML.parse();}, 2000);
                  }
                }
            }
        }

        if (this.options.oembedProxy) {
          that.getOEmbedHTML(url, function(error, oebmed) {

              var html = !error && oebmed && oebmed.html;

              if (oebmed && !oebmed.html && oebmed.type === 'photo' && oebmed.url) {
                  html = '';
              }

              processEmbedTag(html);
          });
        } else {
            var embed_tag = that.convertUrlToEmbedTag(url);
            return processEmbedTag(embed_tag);
        }

    },

    removeToolbar : function () {
      $(".mediumInsert-embedsWire").remove();
    },

      getOEmbedHTML: function(url, cb) {
          $.ajax({
              url: this.options.oembedProxy,
              dataType: "json",
              data: {
                  url: url
              },
              success: function(data, textStatus, jqXHR) {
                  cb(null, data, jqXHR);
              },
              error: function(jqXHR, textStatus, errorThrown) {
                  var responseJSON = function() {
                      try {
                          return JSON.parse(jqXHR.responseText);
                      } catch(e) {}
                  }();

                  cb((responseJSON && responseJSON.error) || jqXHR.status || errorThrown.message, responseJSON, jqXHR);
              }
          });
      },

      convertUrlToEmbedTag : function (url) {
          // We didn't get something we expect so let's get out of here.
          if (!(new RegExp(['youtube', 'yout.be', 'vimeo', 'facebook', 'instagram'].join("|")).test(url))) return false;

          var embed_tag = url.replace(/\n?/g, '').replace(/^((http(s)?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/(watch\?v=|v\/)?)([a-zA-Z0-9-_]+)(.*)?$/, '

')
              .replace(/^http:\/\/vimeo\.com(\/.+)?\/([0-9]+)$/, '')
              //.replace(/^https:\/\/twitter\.com\/(\w+)\/status\/(\d+)\/?$/, '

')
              .replace(/^https:\/\/www\.facebook\.com\/(video.php|photo.php)\?v=(\d+).+$/, '

Post

')
              .replace(/^http:\/\/instagram\.com\/p\/(.+)\/?$/, '');

          return /<("[^"]*"|'[^']*'|[^'">])*>/.test(embed_tag) ? embed_tag : false;
      }

  });

}(jQuery));
<|endoftext|>


<commit_before>'use strict';

angular.module('Teem')
  .factory('CommunitiesSvc', [
  'swellRT', '$q', '$timeout', 'base64', 'SessionSvc', 'SwellRTCommon', 'ProjectsSvc',
  'User', '$rootScope',
  function(swellRT, $q, $timeout, base64, SessionSvc, SwellRTCommon, ProjectsSvc, User, $rootScope){

    class CommunityReadOnly {

      constructor (val) {
        if (val) {
          for (var k in val.root){
            if (val.root.hasOwnProperty(k)){
              this[k] = val.root[k];
            }
          }

        }
      }

      get urlId () {
        if (! this._urlId) {
          this._urlId = base64.urlencode(this.id);
        }

        return this._urlId;
      }

      myAndPublicProjects () {
        return ProjectsSvc.all({
           publicAndContributor: SessionSvc.users.current(),
           community: this.id
         });
      }

      isParticipant (user = SessionSvc.users.current()) {
        if (! user) {
          return false;
        }

        // Migrating from participants === undefined
        if (this.participants === undefined) {
          this.participants = [];
        }

        return this.participants.indexOf(user) > -1;
      }
    }

    class Community extends CommunityReadOnly {

      addParticipant (user) {
        if (!user){
          if (!SessionSvc.users.loggedIn()) {
            return;
          }

          user = SessionSvc.users.current();
        }

        if (this.isParticipant(user)) {
          return;
        }

        this.participants.push(user);

        if (user === User.currentId()) {
          $rootScope.$broadcast('teem.community.join');
        }
      }

      removeParticipant (user) {
        if (!user){
          if (!SessionSvc.users.loggedIn()) {
            return;
          }

          user = SessionSvc.users.current();
        }

        if (! this.isParticipant(user)) {
          return;
        }

        this.participants.splice(
          this.participants.indexOf(user),
          1);

          if (user === User.currentId()) {
            $rootScope.$broadcast('teem.community.leave');
          }
      }

      toggleParticipant (user) {
        if (!user){
          if (!SessionSvc.users.loggedIn()) {
            return;
          }

          user = SessionSvc.users.current();
        }

        if (this.isParticipant(user)) {
          this.removeParticipant(user);
        } else {
          this.addParticipant(user);
        }
      }

      delete () {
        this.type = 'deleted';

        //TODO remove pointers from project.communities
      }
    }

    // Service functions

    var openedCommunities = {};

    var find = function(id) {
      var comDef = $q.defer();
      var community = comDef.promise;

      if (!openedCommunities[id]) {
        openedCommunities[id] = community;

        SwellRT.openModel(id, function(model){

          $timeout(function(){
            var pr = swellRT.proxy(model, Community);

            comDef.resolve(pr);
          });

        }, function(error){

          console.log(error);

          comDef.reject(error);
        });
      } else {
        openedCommunities[id].then(
          function(r){
            comDef.resolve(r);
          });
      }

      return community;
    };

    function findByUrlId(urlId) {
      return find(base64.urldecode(urlId));
    }

    var create = function(data, callback) {
      var d = $q.defer();
      var id = window.SwellRT.createModel(function(model){
        openedCommunities[id] = d.promise;
        SwellRTCommon.makeModelPublic(model);

        var p;

        $timeout(function(){
          p = swellRT.proxy(model, Community);
        });

        $timeout(function(){
          p.type = 'community';
          p.id = id;
          p.participants = [SessionSvc.users.current()];
          p.projects = [];
          d.resolve(p);
        });
      });

      d.promise.then(callback);

      return d.promise;
    };

    /*
     * Count the projects these communities have
     * FIXME: Use ProjectsSvc for this
     */
    function countProjects (communities, resolve) {
      SwellRT.query(
        {_aggregate:
           [{$match: {
             'root.type': 'project',
             'root.shareMode': 'public',
             'root.communities': { $in: communities.map(c => c.id) }
           }},
            {$unwind: '$root.communities'},
            {$group :
             {_id:'$root.communities',
              number: { $sum : 1 }
             }
            }]},
          function(result){
            var counters = result.result;

            angular.forEach(communities, function (c) {
              angular.forEach(counters, function (cnt) {
                if (c.id === cnt._id) {
                  c.numProjects = cnt.number;
                }
              });

              if (c.numProjects === undefined) {
                c.numProjects = 0;
              }
            });

            resolve(communities);
          },
          function(e){
            console.log(e);

            resolve(communities);
          });
    }

    /*
     * Build options for all query
     */
    function buildAllQuery(options) {
      var query = {
        _aggregate: [
          {
            $match: {
              'root.type': 'community'
            }
          }
        ]
      };

      if (options.ids) {
        query._aggregate[0].$match['root.id'] = { $in: options.ids };
      }

      if (options.participant) {
        query._aggregate[0].$match['root.participants'] = options.participant;
      }

      return query;
    }

    /*
     * Find all the communities that meet some condition
     */
    function all (options = {}) {
      var communities = [],
          query = buildAllQuery(options);

      return $q(function(resolve, reject) {

        SwellRT.query(query, function(result) {
            angular.forEach(result.result, function(c) {
              communities.push(new CommunityReadOnly(c));
            });

            if (options.projectCount) {
              countProjects(communities, resolve);
            } else {
              resolve(communities);
            }
          },
          function(e){
            reject(e);
          }
        );
      });
    }

    // The communities the user is participating in
    var participating = function(options = {}) {
      if (!SessionSvc.users.loggedIn()) {
        return $q(function(resolve) {
          resolve([]);
        });
      }

      options.participant = SessionSvc.users.current();

      return all(options);
    };

    return {
      findByUrlId,
      find,
      create,
      all,
      participating
    };
  }]);
<commit_msg>Return promise in CommunitiesSvc.countProject<commit_after>'use strict';

angular.module('Teem')
  .factory('CommunitiesSvc', [
  'swellRT', '$q', '$timeout', 'base64', 'SessionSvc', 'SwellRTCommon', 'ProjectsSvc',
  'User', '$rootScope',
  function(swellRT, $q, $timeout, base64, SessionSvc, SwellRTCommon, ProjectsSvc, User, $rootScope){

    class CommunityReadOnly {

      constructor (val) {
        if (val) {
          for (var k in val.root){
            if (val.root.hasOwnProperty(k)){
              this[k] = val.root[k];
            }
          }

        }
      }

      get urlId () {
        if (! this._urlId) {
          this._urlId = base64.urlencode(this.id);
        }

        return this._urlId;
      }

      myAndPublicProjects () {
        return ProjectsSvc.all({
           publicAndContributor: SessionSvc.users.current(),
           community: this.id
         });
      }

      isParticipant (user = SessionSvc.users.current()) {
        if (! user) {
          return false;
        }

        // Migrating from participants === undefined
        if (this.participants === undefined) {
          this.participants = [];
        }

        return this.participants.indexOf(user) > -1;
      }
    }

    class Community extends CommunityReadOnly {

      addParticipant (user) {
        if (!user){
          if (!SessionSvc.users.loggedIn()) {
            return;
          }

          user = SessionSvc.users.current();
        }

        if (this.isParticipant(user)) {
          return;
        }

        this.participants.push(user);

        if (user === User.currentId()) {
          $rootScope.$broadcast('teem.community.join');
        }
      }

      removeParticipant (user) {
        if (!user){
          if (!SessionSvc.users.loggedIn()) {
            return;
          }

          user = SessionSvc.users.current();
        }

        if (! this.isParticipant(user)) {
          return;
        }

        this.participants.splice(
          this.participants.indexOf(user),
          1);

          if (user === User.currentId()) {
            $rootScope.$broadcast('teem.community.leave');
          }
      }

      toggleParticipant (user) {
        if (!user){
          if (!SessionSvc.users.loggedIn()) {
            return;
          }

          user = SessionSvc.users.current();
        }

        if (this.isParticipant(user)) {
          this.removeParticipant(user);
        } else {
          this.addParticipant(user);
        }
      }

      delete () {
        this.type = 'deleted';

        //TODO remove pointers from project.communities
      }
    }

    // Service functions

    var openedCommunities = {};

    var find = function(id) {
      var comDef = $q.defer();
      var community = comDef.promise;

      if (!openedCommunities[id]) {
        openedCommunities[id] = community;

        SwellRT.openModel(id, function(model){

          $timeout(function(){
            var pr = swellRT.proxy(model, Community);

            comDef.resolve(pr);
          });

        }, function(error){

          console.log(error);

          comDef.reject(error);
        });
      } else {
        openedCommunities[id].then(
          function(r){
            comDef.resolve(r);
          });
      }

      return community;
    };

    function findByUrlId(urlId) {
      return find(base64.urldecode(urlId));
    }

    var create = function(data, callback) {
      var d = $q.defer();
      var id = window.SwellRT.createModel(function(model){
        openedCommunities[id] = d.promise;
        SwellRTCommon.makeModelPublic(model);

        var p;

        $timeout(function(){
          p = swellRT.proxy(model, Community);
        });

        $timeout(function(){
          p.type = 'community';
          p.id = id;
          p.participants = [SessionSvc.users.current()];
          p.projects = [];
          d.resolve(p);
        });
      });

      d.promise.then(callback);

      return d.promise;
    };

    /*
     * Count the projects these communities have
     * FIXME: Use ProjectsSvc for this
     */
    function countProjects (communities) {

      return $q(function (resolve, reject) {

        SwellRT.query(
          {_aggregate:
             [{$match: {
               'root.type': 'project',
               'root.shareMode': 'public',
               'root.communities': { $in: communities.map(c => c.id) }
             }},
              {$unwind: '$root.communities'},
              {$group :
               {_id:'$root.communities',
                number: { $sum : 1 }
               }
              }]},
            function(result){
              var counters = result.result;

              angular.forEach(communities, function (c) {
                angular.forEach(counters, function (cnt) {
                  if (c.id === cnt._id) {
                    c.numProjects = cnt.number;
                  }
                });

                if (c.numProjects === undefined) {
                  c.numProjects = 0;
                }
              });

              resolve();
            },
            function(e){
              reject(e);
            });
      });
    }

    /*
     * Build options for all query
     */
    function buildAllQuery(options) {
      var query = {
        _aggregate: [
          {
            $match: {
              'root.type': 'community'
            }
          }
        ]
      };

      if (options.ids) {
        query._aggregate[0].$match['root.id'] = { $in: options.ids };
      }

      if (options.participant) {
        query._aggregate[0].$match['root.participants'] = options.participant;
      }

      return query;
    }

    /*
     * Find all the communities that meet some condition
     */
    function all (options = {}) {
      var communities = [],
          query = buildAllQuery(options);

      return $q(function(resolve, reject) {

        SwellRT.query(query, function(result) {
            angular.forEach(result.result, function(c) {
              communities.push(new CommunityReadOnly(c));
            });

            if (options.projectCount) {
              countProjects(communities)
                .then(function () {
                  resolve(communities);
                }, function (error) {
                  reject(error);
                });
            } else {
              resolve(communities);
            }
          },
          function(e){
            reject(e);
          }
        );
      });
    }

    // The communities the user is participating in
    var participating = function(options = {}) {
      if (!SessionSvc.users.loggedIn()) {
        return $q(function(resolve) {
          resolve([]);
        });
      }

      options.participant = SessionSvc.users.current();

      return all(options);
    };

    return {
      findByUrlId,
      find,
      create,
      all,
      participating
    };
  }]);
<|endoftext|>


<commit_before>let Item = require('../Item');
let List = require('../List');
let Tree = require('../Tree');

describe('Tree', function () {

  describe('#constructor', function () {

    it('defaults to an empty array', function () {
      let tree = new Tree();
      expect(tree.getItems()).toEqual([]);
    });

    it('accepts a list of items', function () {
      let tree = new Tree({items: [0, 1, 2]});
      expect(tree.getItems()).toEqual([0, 1, 2]);
    });

    it('accepts a nested items', function () {
      let tree = new Tree({items: [0, {items: [1.1, 1.2, 1.2]}, 2]});
      expect(tree.getItems()[1] instanceof Tree).toEqual(true);
    });

    it('throws when initialized with a non-array argument', function () {
      let fn = function () {
        return new Tree({items: 'foo'});
      };

      expect(fn).toThrow();
    });

  });

  describe('#add', function () {

    it('adds an item', function () {
      let tree = new Tree();
      tree.add(0);
      expect(tree.getItems()).toEqual([0]);
    });

    it('adds two items', function () {
      let tree = new Tree();
      tree.add(0);
      tree.add(1);
      expect(tree.getItems()).toEqual([0, 1]);
    });

    it('adds items to current Tree', function () {
      let tree = new Tree({items: [0]});
      tree.add(1);
      tree.add(2);
      expect(tree.getItems()).toEqual([0, 1, 2]);
    });

    it('adds a Tree', function () {
      let tree = new Tree();
      tree.add(new Tree());
      expect(tree.getItems()[0] instanceof Tree).toBeTruthy();
    });

  });

  describe('#getItems', function () {

    it('returns a list of items', function () {
      let tree = new Tree();
      expect(tree.getItems()).toEqual([]);
    });

    it('returns added items in a list', function () {
      let tree = new Tree();
      tree.add(0);
      tree.add(1);
      expect(tree.getItems()).toEqual([0, 1]);
    });

  });

  describe('#flattenItems', function () {

    beforeEach(function () {
      this.instance = new Tree({items: [
        {name: 'foo'}, {name: 'bar'}, {items: [{name: 'alpha'}, {name: 'beta'}]}
      ]});
    });

    it('returns an instance of List', function () {
      expect(this.instance.flattenItems() instanceof List).toBeTruthy();
    });

    it('returns correct list of items', function () {
      let items = this.instance.flattenItems().getItems();

      expect(items[0].name).toEqual('foo');
      expect(items[1].name).toEqual('bar');
      expect(items[2] instanceof Tree).toBeTruthy();
      expect(items[3].name).toEqual('alpha');
      expect(items[4].name).toEqual('beta');
    });

  });

  describe('#filterItems', function () {

    beforeEach(function () {
      var items = [
        {name: 'foo', description: {value: 'qux'}, tags: ['one', 'two']},
        {name: 'bar', description: {value: 'quux'}, tags: ['two', 'three']},
        {
          items: [
            {
              name: 'alpha',
              description: {value: 'gamma'},
              tags: ['one', 'two']
            },
            {
              name: 'beta',
              description: {value: 'delta'},
              tags: ['one', 'two']
            }
          ]
        }
      ];
      var filterProperties = {
        name: null,
        description: function (item, prop) {
          return item[prop] && item[prop].value;
        },
        tags: function (item, prop) {
          return item[prop] && item[prop].join(' ');
        }
      };

      this.instance = new Tree({items, filterProperties});
    });

    it('should return an instance of Tree', function () {
      let filteredTree = this.instance.filterItems('bar');
      expect(filteredTree instanceof Tree).toEqual(true);
    });

    it('should filter sub trees', function () {
      let filteredSubtree = this.instance.filterItems('alpha').getItems()[0];
      expect(filteredSubtree instanceof Tree).toEqual(true);
      expect(filteredSubtree.getItems()[0].name).toEqual('alpha');
    });

    it('should filter instances of Item', function () {
      var items = [
        new Item({
          name: 'foo',
          description: {value: 'qux'},
          tags: ['one', 'two']
        }),
        new Item({
          name: 'bar',
          description: {value: 'quux'},
          tags: ['two', 'three']}
        )
      ];
      var filterProperties = {
        name: null,
        description: function (item, prop) {
          return item[prop] && item[prop].value;
        },
        tags: function (item, prop) {
          return item[prop] && item[prop].join(' ');
        }
      };

      this.instance = new Tree({items, filterProperties});
      var filteredItems = this.instance.filterItems('bar').getItems();
      expect(filteredItems.length).toEqual(1);
      expect(filteredItems[0] instanceof Item).toEqual(true);
    });

    it('should filter by default getter', function () {
      var filteredItems = this.instance.filterItems('bar').getItems();
      expect(filteredItems.length).toEqual(1);
      expect(filteredItems[0].name).toEqual('bar');
    });

    it('should filter by description', function () {
      var filteredItems = this.instance.filterItems('qux').getItems();
      expect(filteredItems.length).toEqual(1);
      expect(filteredItems[0].description.value).toEqual('qux');
    });

    it('should filter by tags', function () {
      var filteredItems = this.instance.filterItems('two').getItems();
      expect(filteredItems.length).toEqual(3);
      expect(filteredItems[0].tags).toEqual(['one', 'two']);
      expect(filteredItems[1].tags).toEqual(['two', 'three']);
    });

    it('should handle filter by with null elements', function () {
      var items = [
        {name: null, description: {value: null}, tags: [null, 'three']},
        {description: null, tags: null}
      ];
      var filterProperties = {
        name: null,
        description: function (item, prop) {
          return item[prop] && item[prop].value;
        },
        tags: function (item, prop) {
          return item[prop] && item[prop].join(' ');
        }
      };
      var list = new Tree({items, filterProperties});
      expect(list.filterItems.bind(list, 'foo')).not.toThrow();
    });

  });

  describe('#findItem', function () {

    beforeEach(function () {
      this.instance = new Tree({items: [
        {name: 'foo'}, {name: 'bar'}, {items: [{name: 'alpha'}, {name: 'beta'}]}
      ]});
    });

    it('should return undefined if no matching item was found', function () {
      expect(this.instance.findItem(function () {
        return false;
      })).toEqual(undefined);
    });

    it('should return matching item', function () {
      expect(this.instance.findItem(function (item) {
        return item.name === 'beta';
      })).toEqual({name: 'beta'});
    });

  });

  describe('#mapItems', function () {

    beforeEach(function () {
      this.instance = new Tree({
        items: [
          {name: 'foo'}, {name: 'bar'}, {items: [{name: 'alpha'}, {name: 'beta'}]}
        ]
      });
    });

    it('should return an instance of Tree', function () {
      var tree = this.instance.mapItems(function (item) {
        return item;
      });
      expect(tree instanceof Tree).toBeTruthy();
    });

    it('should apply callback to all items', function () {
      var items = this.instance.mapItems(function (item) {
        if (item instanceof Tree) {
          return item;
        }

        return {name: item.name.toUpperCase()};
      }).flattenItems().getItems();

      expect(items[0].name).toEqual('FOO');
      expect(items[1].name).toEqual('BAR');
      expect(items[2] instanceof Tree).toBeTruthy();
      expect(items[3].name).toEqual('ALPHA');
      expect(items[4].name).toEqual('BETA');
    });

  });

});
<commit_msg>Minor stylistic adjustments<commit_after>let Item = require('../Item');
let List = require('../List');
let Tree = require('../Tree');

describe('Tree', function () {

  describe('#constructor', function () {

    it('defaults to an empty array', function () {
      let tree = new Tree();
      expect(tree.getItems()).toEqual([]);
    });

    it('accepts a list of items', function () {
      let tree = new Tree({items: [0, 1, 2]});
      expect(tree.getItems()).toEqual([0, 1, 2]);
    });

    it('accepts a nested items', function () {
      let tree = new Tree({items: [0, {items: [1.1, 1.2, 1.2]}, 2]});
      expect(tree.getItems()[1] instanceof Tree).toEqual(true);
    });

    it('throws when initialized with a non-array argument', function () {
      let fn = function () {
        return new Tree({items: 'foo'});
      };

      expect(fn).toThrow();
    });

  });

  describe('#add', function () {

    it('adds an item', function () {
      let tree = new Tree();
      tree.add(0);
      expect(tree.getItems()).toEqual([0]);
    });

    it('adds two items', function () {
      let tree = new Tree();
      tree.add(0);
      tree.add(1);
      expect(tree.getItems()).toEqual([0, 1]);
    });

    it('adds items to current Tree', function () {
      let tree = new Tree({items: [0]});
      tree.add(1);
      tree.add(2);
      expect(tree.getItems()).toEqual([0, 1, 2]);
    });

    it('adds a Tree', function () {
      let tree = new Tree();
      tree.add(new Tree());
      expect(tree.getItems()[0] instanceof Tree).toBeTruthy();
    });

  });

  describe('#getItems', function () {

    it('returns a list of items', function () {
      let tree = new Tree();
      expect(tree.getItems()).toEqual([]);
    });

    it('returns added items in a list', function () {
      let tree = new Tree();
      tree.add(0);
      tree.add(1);
      expect(tree.getItems()).toEqual([0, 1]);
    });

  });

  describe('#flattenItems', function () {

    beforeEach(function () {
      this.instance = new Tree({items: [
        {name: 'foo'}, {name: 'bar'}, {items: [{name: 'alpha'}, {name: 'beta'}]}
      ]});
    });

    it('returns an instance of List', function () {
      expect(this.instance.flattenItems() instanceof List).toBeTruthy();
    });

    it('returns correct list of items', function () {
      let items = this.instance.flattenItems().getItems();

      expect(items[0].name).toEqual('foo');
      expect(items[1].name).toEqual('bar');
      expect(items[2] instanceof Tree).toBeTruthy();
      expect(items[3].name).toEqual('alpha');
      expect(items[4].name).toEqual('beta');
    });

  });

  describe('#filterItems', function () {

    beforeEach(function () {
      var items = [
        {name: 'foo', description: {value: 'qux'}, tags: ['one', 'two']},
        {name: 'bar', description: {value: 'quux'}, tags: ['two', 'three']},
        {
          items: [
            {
              name: 'alpha',
              description: {value: 'gamma'},
              tags: ['one', 'two']
            },
            {
              name: 'beta',
              description: {value: 'delta'},
              tags: ['one', 'two']
            }
          ]
        }
      ];
      var filterProperties = {
        name: null,
        description: function (item, prop) {
          return item[prop] && item[prop].value;
        },
        tags: function (item, prop) {
          return item[prop] && item[prop].join(' ');
        }
      };

      this.instance = new Tree({items, filterProperties});
    });

    it('should return an instance of Tree', function () {
      let filteredTree = this.instance.filterItems('bar');
      expect(filteredTree instanceof Tree).toEqual(true);
    });

    it('should filter sub trees', function () {
      let filteredSubtree = this.instance.filterItems('alpha').getItems()[0];
      expect(filteredSubtree instanceof Tree).toEqual(true);
      expect(filteredSubtree.getItems()[0].name).toEqual('alpha');
    });

    it('should filter instances of Item', function () {
      var items = [
        new Item({
          name: 'foo',
          description: {value: 'qux'},
          tags: ['one', 'two']
        }),
        new Item({
          name: 'bar',
          description: {value: 'quux'},
          tags: ['two', 'three']
        })
      ];
      var filterProperties = {
        name: null,
        description: function (item, prop) {
          return item[prop] && item[prop].value;
        },
        tags: function (item, prop) {
          return item[prop] && item[prop].join(' ');
        }
      };

      this.instance = new Tree({items, filterProperties});
      var filteredItems = this.instance.filterItems('bar').getItems();
      expect(filteredItems.length).toEqual(1);
      expect(filteredItems[0] instanceof Item).toEqual(true);
    });

    it('should filter by default getter', function () {
      var filteredItems = this.instance.filterItems('bar').getItems();
      expect(filteredItems.length).toEqual(1);
      expect(filteredItems[0].name).toEqual('bar');
    });

    it('should filter by description', function () {
      var filteredItems = this.instance.filterItems('qux').getItems();
      expect(filteredItems.length).toEqual(1);
      expect(filteredItems[0].description.value).toEqual('qux');
    });

    it('should filter by tags', function () {
      var filteredItems = this.instance.filterItems('two').getItems();
      expect(filteredItems.length).toEqual(3);
      expect(filteredItems[0].tags).toEqual(['one', 'two']);
      expect(filteredItems[1].tags).toEqual(['two', 'three']);
    });

    it('should handle filter by with null elements', function () {
      var items = [
        {name: null, description: {value: null}, tags: [null, 'three']},
        {description: null, tags: null}
      ];
      var filterProperties = {
        name: null,
        description: function (item, prop) {
          return item[prop] && item[prop].value;
        },
        tags: function (item, prop) {
          return item[prop] && item[prop].join(' ');
        }
      };
      var list = new Tree({items, filterProperties});
      expect(list.filterItems.bind(list, 'foo')).not.toThrow();
    });

  });

  describe('#findItem', function () {

    beforeEach(function () {
      this.instance = new Tree({items: [
        {name: 'foo'}, {name: 'bar'}, {items: [{name: 'alpha'}, {name: 'beta'}]}
      ]});
    });

    it('should return undefined if no matching item was found', function () {
      expect(this.instance.findItem(function () {
        return false;
      })).toEqual(undefined);
    });

    it('should return matching item', function () {
      expect(this.instance.findItem(function (item) {
        return item.name === 'beta';
      })).toEqual({name: 'beta'});
    });

  });

  describe('#mapItems', function () {

    beforeEach(function () {
      this.instance = new Tree({
        items: [
          {name: 'foo'}, {name: 'bar'}, {items: [{name: 'alpha'}, {name: 'beta'}]}
        ]
      });
    });

    it('should return an instance of Tree', function () {
      var tree = this.instance.mapItems(function (item) {
        return item;
      });
      expect(tree instanceof Tree).toBeTruthy();
    });

    it('should apply callback to all items', function () {
      var items = this.instance.mapItems(function (item) {
        if (item instanceof Tree) {
          return item;
        }

        return {name: item.name.toUpperCase()};
      }).flattenItems().getItems();

      expect(items[0].name).toEqual('FOO');
      expect(items[1].name).toEqual('BAR');
      expect(items[2] instanceof Tree).toBeTruthy();
      expect(items[3].name).toEqual('ALPHA');
      expect(items[4].name).toEqual('BETA');
    });

  });

});
<|endoftext|>


<commit_before>/**
 * A specialised GoldenLayout component that binds GoldenLayout container
 * lifecycle events to react components
 *
 * @constructor
 *
 * @param {lm.container.ItemContainer} container
 * @param {Object} state state is not required for react components
 */
lm.utils.ReactComponentHandler = function( container, state ) {
	this._reactComponent = null;
	this._originalComponentWillUpdate = null;
	this._container = container;
	this._initialState = state;
	this._reactClass = this._getReactClass();
	this._container.on( 'open', this._render, this );
	this._container.on( 'destroy', this._destroy, this );
};

lm.utils.copy( lm.utils.ReactComponentHandler.prototype, {

	/**
	 * Creates the react class and component and hydrates it with
	 * the initial state - if one is present
	 *
	 * By default, react's getInitialState will be used
	 *
	 * @private
	 * @returns {void}
	 */
	_render: function() {
		this._reactComponent = ReactDOM.render( this._getReactComponent(), this._container.getElement()[ 0 ]);
		this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function(){};
		this._reactComponent.componentWillUpdate = this._onUpdate.bind( this );
		if( this._container.getState() ) {
			this._reactComponent.setState( this._container.getState() );
		}
	},

	/**
	 * Removes the component from the DOM and thus invokes React's unmount lifecycle
	 *
	 * @private
	 * @returns {void}
	 */
	_destroy: function() {
		ReactDOM.unmountComponentAtNode( this._container.getElement()[ 0 ]);
		this._container.off( 'open', this._render, this );
		this._container.off( 'destroy', this._destroy, this );
	},

	/**
	 * Hooks into React's state management and applies the componentstate
	 * to GoldenLayout
	 *
	 * @private
	 * @returns {void}
	 */
	_onUpdate: function( nextProps, nextState ) {
		this._container.setState( nextState );
		this._originalComponentWillUpdate( nextProps, nextState );
	},

	/**
	 * Retrieves the react class from GoldenLayout's registry
	 *
	 * @private
	 * @returns {React.Class}
	 */
	_getReactClass: function() {
		var componentName = this._container._config.component;
		var reactClass;

		if( !componentName ) {
			throw new Error( 'No react component name. type: react-component needs a field `component`' );
		}

		reactClass = this._container.layoutManager.getComponent( componentName );

		if( !reactClass ) {
			throw new Error( 'React component "' + componentName + '" not found. ' +
				'Please register all components with GoldenLayout using `registerComponent(name, component)`' );
		}

		return reactClass;
	},

	/**
	 * Copies and extends the properties array and returns the React element
	 *
	 * @private
	 * @returns {React.Element}
	 */
	_getReactComponent: function() {
		var defaultProps = {
			glEventHub: this._container.layoutManager.eventHub,
			glContainer: this._container,
		};
		var props = $.extend( defaultProps, this._container._config.props );
		return React.createElement( this._reactClass, props );
	}
});<commit_msg>React.ComponentWillUpdate() method is called with the wrong 'this' argument.<commit_after>/**
 * A specialised GoldenLayout component that binds GoldenLayout container
 * lifecycle events to react components
 *
 * @constructor
 *
 * @param {lm.container.ItemContainer} container
 * @param {Object} state state is not required for react components
 */
lm.utils.ReactComponentHandler = function( container, state ) {
	this._reactComponent = null;
	this._originalComponentWillUpdate = null;
	this._container = container;
	this._initialState = state;
	this._reactClass = this._getReactClass();
	this._container.on( 'open', this._render, this );
	this._container.on( 'destroy', this._destroy, this );
};

lm.utils.copy( lm.utils.ReactComponentHandler.prototype, {

	/**
	 * Creates the react class and component and hydrates it with
	 * the initial state - if one is present
	 *
	 * By default, react's getInitialState will be used
	 *
	 * @private
	 * @returns {void}
	 */
	_render: function() {
		this._reactComponent = ReactDOM.render( this._getReactComponent(), this._container.getElement()[ 0 ]);
		this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function(){};
		this._reactComponent.componentWillUpdate = this._onUpdate.bind( this );
		if( this._container.getState() ) {
			this._reactComponent.setState( this._container.getState() );
		}
	},

	/**
	 * Removes the component from the DOM and thus invokes React's unmount lifecycle
	 *
	 * @private
	 * @returns {void}
	 */
	_destroy: function() {
		ReactDOM.unmountComponentAtNode( this._container.getElement()[ 0 ]);
		this._container.off( 'open', this._render, this );
		this._container.off( 'destroy', this._destroy, this );
	},

	/**
	 * Hooks into React's state management and applies the componentstate
	 * to GoldenLayout
	 *
	 * @private
	 * @returns {void}
	 */
	_onUpdate: function( nextProps, nextState ) {
		this._container.setState( nextState );
		this._originalComponentWillUpdate.call( this._reactComponent, nextProps, nextState );
	},

	/**
	 * Retrieves the react class from GoldenLayout's registry
	 *
	 * @private
	 * @returns {React.Class}
	 */
	_getReactClass: function() {
		var componentName = this._container._config.component;
		var reactClass;

		if( !componentName ) {
			throw new Error( 'No react component name. type: react-component needs a field `component`' );
		}

		reactClass = this._container.layoutManager.getComponent( componentName );

		if( !reactClass ) {
			throw new Error( 'React component "' + componentName + '" not found. ' +
				'Please register all components with GoldenLayout using `registerComponent(name, component)`' );
		}

		return reactClass;
	},

	/**
	 * Copies and extends the properties array and returns the React element
	 *
	 * @private
	 * @returns {React.Element}
	 */
	_getReactComponent: function() {
		var defaultProps = {
			glEventHub: this._container.layoutManager.eventHub,
			glContainer: this._container,
		};
		var props = $.extend( defaultProps, this._container._config.props );
		return React.createElement( this._reactClass, props );
	}
});
<|endoftext|>