<commit_before>const MBContract       = require('./mb_contract');
const MBDefaults       = require('./mb_defaults');
const MBNotifications  = require('./mb_notifications');
const MBPrice          = require('./mb_price');
const MBSymbols        = require('./mb_symbols');
const MBTick           = require('./mb_tick');
const commonTrading    = require('../trade/common');
const BinaryPjax       = require('../../base/binary_pjax');
const Client           = require('../../base/client');
const BinarySocket     = require('../../base/socket');
const isCryptocurrency = require('../../common/currency').isCryptocurrency;
const getLanguage      = require('../../../_common/language').get;
const localize         = require('../../../_common/localize').localize;
const State            = require('../../../_common/storage').State;
const urlForStatic     = require('../../../_common/url').urlForStatic;
const getPropertyValue = require('../../../_common/utility').getPropertyValue;

const MBProcess = (() => {
    let market_status = '';

    let symbols_timeout,
        contract_timeout;

    const getSymbols = () => {
        BinarySocket.wait('website_status').then((website_status) => {
            const landing_company_obj = State.getResponse('landing_company');
            const allowed_markets     = Client.currentLandingCompany().legal_allowed_markets;
            if (Client.isLoggedIn() && allowed_markets && allowed_markets.indexOf('forex') === -1) {
                BinaryPjax.load('trading');
                return;
            }
            const req = {
                active_symbols: 'brief',
                product_type  : 'multi_barrier',
            };
            if (landing_company_obj) {
                req.landing_company = landing_company_obj.financial_company ? landing_company_obj.financial_company.shortcode : 'japan';
            } else if (website_status.website_status.clients_country === 'jp' || getLanguage() === 'JA') {
                req.landing_company = 'japan';
            }
            BinarySocket.send(req, { msg_type: 'active_symbols' }).then((response) => {
                if (!response.active_symbols || !response.active_symbols.length) {
                    $('#main_loading').replaceWith($('<p/>', { class: 'notice-msg center-text container', text: localize('Sorry, this feature is not available in your jurisdiction.') }));
                    return;
                }
                processActiveSymbols(response);
            });
        });
    };

    /*
     * This function processes the active symbols to get markets
     * and underlying list
     */
    const processActiveSymbols = (data) => {
        if (getPropertyValue(data, 'error')) {
            MBNotifications.show({ text: data.error.message, uid: 'ACTIVE_SYMBOLS' });
            return;
        }

        // populate the Symbols object
        MBSymbols.details(data);

        const is_show_all  = Client.isLoggedIn() && !Client.isJPClient();
        const symbols_list = is_show_all ? MBSymbols.getAllSymbols() : MBSymbols.underlyings().major_pairs;
        let symbol         = MBDefaults.get('underlying');

        if (!symbol || !symbols_list[symbol]) {
            symbol = undefined;
            MBDefaults.remove('underlying');
        }

        // check if all symbols are inactive
        let is_market_closed = true;
        Object.keys(symbols_list).forEach((s) => {
            if (symbols_list[s].is_active) {
                is_market_closed = false;
            }
        });
        clearSymbolTimeout();
        if (is_market_closed) {
            handleMarketClosed();
        } else {
            handleMarketOpen();
            populateUnderlyings(symbol);

            if (symbol && !symbols_list[symbol].is_active) {
                MBNotifications.show({ text: localize('This symbol is not active. Please try another symbol.'), uid: 'SYMBOL_INACTIVE' });
            } else {
                processMarketUnderlying();
            }
        }
    };

    const populateUnderlyings = (selected) => {
        const $underlyings = $('#underlying');
        const all_symbols  = MBSymbols.getAllSymbols();

        const $list = $underlyings.find('.list');
        $list.empty();
        $underlyings.find('.current').html($('<div/>', { class: 'gr-row' })
            .append($('<span/>', { class: 'nav-caret ja-hide' }))
            .append($('<img/>', { class: 'gr-3 gr-no-gutter-m' }))
            .append($('<span/>', { class: 'name gr-6 gr-5-m align-self-center' }))
            .append($('<span/>', { class: 'gr-3 gr-4-m align-self-center still', id: 'spot' })));

        let selected_symbol = selected;
        if (Object.keys(all_symbols).indexOf(selected) === -1) selected_symbol = '';
        Object.keys(all_symbols).forEach((symbol, idx) => {
            if (all_symbols[symbol].is_active) {
                const is_current = (!selected_symbol && idx === 0) || symbol === selected_symbol;
                const $current   = $('<div/>', { value: symbol, class: 'gr-4 gr-4-t gr-4-m' })
                    .append($('<img/>', { src: urlForStatic(`/images/pages/mb_trading/${symbol.toLowerCase()}.svg`), alt: '' }))
                    .append($('<div/>', { text: all_symbols[symbol].display, class: 'name align-self-center' }));
                $list.append($current);
                if (is_current) {
                    MBContract.setCurrentItem($underlyings, symbol, 1);
                }
            }
        });
    };

    const selectors = '.trade_form, .price-table, #trading_bottom_content, .selection_wrapper, #trade_live_chart';

    const handleMarketClosed = () => {
        $(selectors).setVisibility(0);
        hideShowMbTrading('hide');
        MBNotifications.show({ text: localize('Market is closed. Please try again later.'), uid: 'MARKET_CLOSED' });
        symbols_timeout = setTimeout(() => { getSymbols(); }, 30000);
    };

    const handleMarketOpen = () => {
        $(selectors).setVisibility(1);
        hideShowMbTrading('show');
        MBNotifications.hide('MARKET_CLOSED');
    };

    const hideShowMbTrading = (action) => {
        const classes = ['gr-5 ', 'gr-12 ']; // the extra space is so gr-5-m is not replaced
        const show    = action === 'show';
        const $parent = $('#mb_trading').parent();
        $parent.attr('class', $parent.attr('class').replace(classes[+show], classes[+!show]));
    };

    const clearSymbolTimeout = () => {
        clearTimeout(symbols_timeout);
    };

    /*
     * Function to call when underlying has changed
     */
    const processMarketUnderlying = () => {
        const underlying = $('#underlying').attr('value');
        MBDefaults.set('underlying', underlying);

        commonTrading.showFormOverlay();

        // forget the old tick id i.e. close the old tick stream
        processForgetTicks().then(() => {
            // get ticks for current underlying
            MBTick.request(underlying);
            MBTick.clean();
        });

        BinarySocket.clearTimeouts();

        getContracts(underlying);
    };

    const getContracts = (underlying) => {
        const req = {
            contracts_for: (underlying || MBDefaults.get('underlying')),
            currency     : MBContract.getCurrency(),
            product_type : 'multi_barrier',
        };
        BinarySocket.send(req).then((response) => {
            MBNotifications.hide('CONNECTION_ERROR');
            MBContract.setContractsResponse(response);
            // contracts_for is triggered every 15 seconds to check for expired barriers
            // but we don't want to send proposal in that case
            // so getContracts will be called without underlying param to distinguish these two cases
            processContract(response, underlying);
        });
        if (contract_timeout) clearContractTimeout();
        contract_timeout = setTimeout(getContracts, 15000);
    };

    const clearContractTimeout = () => { clearTimeout(contract_timeout); };

    /*
     * Function to display contract form for current underlying
     */
    const processContract = (contracts, should_send_proposal) => {
        if (getPropertyValue(contracts, 'error')) {
            MBNotifications.show({ text: contracts.error.message, uid: contracts.error.code });
            return;
        }

        State.set('is_chart_allowed', !(contracts.contracts_for && contracts.contracts_for.feed_license && contracts.contracts_for.feed_license === 'chartonly'));

        checkMarketStatus(contracts.contracts_for.close);

        MBContract.populateOptions(should_send_proposal);
        if (should_send_proposal) {
            processPriceRequest();
        } else {
            processExpiredBarriers();
        }
    };

    const checkMarketStatus = (close) => {
        const now = window.time.unix();

        // if market is closed, else if market is open
        if (now > close) {
            if (market_status === 'open') {
                handleMarketClosed();
            }
            market_status = 'closed';
        } else {
            if (market_status === 'closed') {
                getSymbols();
                handleMarketOpen();
            }
            market_status = 'open';
        }
    };

    const processPriceRequest = () => {
        MBPrice.increaseReqId();
        MBPrice.showPriceOverlay();
        const durations = MBDefaults.get('period').split('_');
        const is_crypto = isCryptocurrency(MBDefaults.get('currency'));
        const payout    = parseFloat(MBDefaults.get(`payout${is_crypto ? '_crypto' : ''}`));

        const req = {
            proposal_array: 1,
            subscribe     : 1,
            basis         : 'payout',
            amount        : Client.isJPClient() ? (parseInt(payout) || 1) * 1000 : payout,
            currency      : MBContract.getCurrency(),
            symbol        : MBDefaults.get('underlying'),
            passthrough   : { req_id: MBPrice.getReqId() },
            date_expiry   : durations[1],
            contract_type : [],
            barriers      : [],
            product_type  : 'multi_barrier',

            trading_period_start: durations[0],
        };

        const available_contracts = MBContract.getCurrentContracts();
        // contract_type
        available_contracts.forEach(c => req.contract_type.push(c.contract_type));

        // barriers
        let all_expired = true;
        const contract  = available_contracts[0];
        contract.available_barriers.forEach((barrier) => {
            const barrier_item = {};
            if (+contract.barriers === 2) {
                barrier_item.barrier  = barrier[1];
                barrier_item.barrier2 = barrier[0];
            } else {
                barrier_item.barrier = barrier;
            }
            if (!barrierHasExpired(contract.expired_barriers, barrier_item.barrier, barrier_item.barrier2)) {
                all_expired = false;
                req.barriers.push(barrier_item);
            }
        });

        processForgetProposals().then(() => {
            // send request
            if (req.barriers.length) {
                MBPrice.addPriceObj(req);
                BinarySocket.send(req, { callback: processProposal });
            }
        });

        // all barriers expired
        if (all_expired) {
            MBNotifications.show({ text: `${localize('All barriers in this trading window are expired')}.`, uid: 'ALL_EXPIRED' });
            MBPrice.hidePriceOverlay();
        } else {
            MBNotifications.hide('ALL_EXPIRED');
        }
    };

    const processProposal = (response) => {
        const req_id = MBPrice.getReqId();
        if (response.passthrough.req_id === req_id) {
            if (response.error) {
                const error_message = response.error.error ? response.error.error.message : response.error.message;
                MBNotifications.show({ text: error_message, uid: 'PROPOSAL', dismissible: false });
                return;
            }
            MBNotifications.hide('PROPOSAL');
            MBPrice.display(response);
        }
    };

    const processExpiredBarriers = () => {
        const contracts = MBContract.getCurrentContracts();
        let expired_barrier,
            $expired_barrier_element;
        contracts.forEach((c) => {
            const expired_barriers = c.expired_barriers;
            for (let i = 0; i < expired_barriers.length; i++) {
                if (+c.barriers === 2) {
                    expired_barrier = [expired_barriers[i][0], expired_barriers[i][1]].join('_');
                } else {
                    expired_barrier = expired_barriers[i];
                }
                $expired_barrier_element = $(`div [data-barrier="${expired_barrier}"]`);
                if ($expired_barrier_element.length > 0) {
                    processForgetProposal(expired_barrier);
                    $expired_barrier_element.remove();
                }
            }
        });
    };

    const barrierHasExpired = (expired_barriers, barrier, barrier2) => {
        if (barrier2) {
            return containsArray(expired_barriers, [[barrier2, barrier]]);
        }
        return (expired_barriers.indexOf((barrier).toString()) > -1);
    };

    const processForgetProposal = (expired_barrier) => {
        const prices = MBPrice.getPrices();
        Object.keys(prices[expired_barrier]).forEach((c) => {
            if (!getPropertyValue(prices[expired_barrier][c], 'error')) {
                BinarySocket.send({ forget: prices[expired_barrier][c].proposal.id });
            }
        });
    };

    const processForgetProposals = () => {
        MBPrice.showPriceOverlay();
        const forget_proposal = BinarySocket.send({ forget_all: 'proposal_array' });
        forget_proposal.then(() => {
            MBPrice.cleanup();
        });
        return forget_proposal;
    };

    const processForgetTicks = () => BinarySocket.send({ forget_all: 'ticks' });

    const forgetTradingStreams = () => {
        processForgetProposals();
        processForgetTicks();
    };

    const containsArray = (array, val) => {
        const hash = {};
        for (let i = 0; i < array.length; i++) {
            hash[array[i]] = i;
        }
        return getPropertyValue(hash, val);
    };

    const onUnload = () => {
        forgetTradingStreams();
        clearSymbolTimeout();
        clearContractTimeout();
        MBSymbols.clearData();
        MBTick.clean();
    };

    return {
        getSymbols,
        getContracts,
        processPriceRequest,
        processForgetTicks,
        onUnload,
    };
})();

module.exports = MBProcess;
<commit_msg>On contract error: hide trading form but still display the chart<commit_after>const MBContract       = require('./mb_contract');
const MBDefaults       = require('./mb_defaults');
const MBNotifications  = require('./mb_notifications');
const MBPrice          = require('./mb_price');
const MBSymbols        = require('./mb_symbols');
const MBTick           = require('./mb_tick');
const showChart        = require('../trade/charts/webtrader_chart').showChart;
const commonTrading    = require('../trade/common');
const BinaryPjax       = require('../../base/binary_pjax');
const Client           = require('../../base/client');
const BinarySocket     = require('../../base/socket');
const isCryptocurrency = require('../../common/currency').isCryptocurrency;
const getLanguage      = require('../../../_common/language').get;
const localize         = require('../../../_common/localize').localize;
const State            = require('../../../_common/storage').State;
const urlForStatic     = require('../../../_common/url').urlForStatic;
const getPropertyValue = require('../../../_common/utility').getPropertyValue;

const MBProcess = (() => {
    let market_status = '';

    let symbols_timeout,
        contract_timeout;

    const getSymbols = () => {
        BinarySocket.wait('website_status').then((website_status) => {
            const landing_company_obj = State.getResponse('landing_company');
            const allowed_markets     = Client.currentLandingCompany().legal_allowed_markets;
            if (Client.isLoggedIn() && allowed_markets && allowed_markets.indexOf('forex') === -1) {
                BinaryPjax.load('trading');
                return;
            }
            const req = {
                active_symbols: 'brief',
                product_type  : 'multi_barrier',
            };
            if (landing_company_obj) {
                req.landing_company = landing_company_obj.financial_company ? landing_company_obj.financial_company.shortcode : 'japan';
            } else if (website_status.website_status.clients_country === 'jp' || getLanguage() === 'JA') {
                req.landing_company = 'japan';
            }
            BinarySocket.send(req, { msg_type: 'active_symbols' }).then((response) => {
                if (!response.active_symbols || !response.active_symbols.length) {
                    $('#main_loading').replaceWith($('<p/>', { class: 'notice-msg center-text container', text: localize('Sorry, this feature is not available in your jurisdiction.') }));
                    return;
                }
                processActiveSymbols(response);
            });
        });
    };

    /*
     * This function processes the active symbols to get markets
     * and underlying list
     */
    const processActiveSymbols = (data) => {
        if (getPropertyValue(data, 'error')) {
            MBNotifications.show({ text: data.error.message, uid: 'ACTIVE_SYMBOLS' });
            return;
        }

        // populate the Symbols object
        MBSymbols.details(data);

        const is_show_all  = Client.isLoggedIn() && !Client.isJPClient();
        const symbols_list = is_show_all ? MBSymbols.getAllSymbols() : MBSymbols.underlyings().major_pairs;
        let symbol         = MBDefaults.get('underlying');

        if (!symbol || !symbols_list[symbol]) {
            symbol = undefined;
            MBDefaults.remove('underlying');
        }

        // check if all symbols are inactive
        let is_market_closed = true;
        Object.keys(symbols_list).forEach((s) => {
            if (symbols_list[s].is_active) {
                is_market_closed = false;
            }
        });
        clearSymbolTimeout();
        if (is_market_closed) {
            handleMarketClosed();
        } else {
            handleMarketOpen();
            populateUnderlyings(symbol);

            if (symbol && !symbols_list[symbol].is_active) {
                MBNotifications.show({ text: localize('This symbol is not active. Please try another symbol.'), uid: 'SYMBOL_INACTIVE' });
            } else {
                processMarketUnderlying();
            }
        }
    };

    const populateUnderlyings = (selected) => {
        const $underlyings = $('#underlying');
        const all_symbols  = MBSymbols.getAllSymbols();

        const $list = $underlyings.find('.list');
        $list.empty();
        $underlyings.find('.current').html($('<div/>', { class: 'gr-row' })
            .append($('<span/>', { class: 'nav-caret ja-hide' }))
            .append($('<img/>', { class: 'gr-3 gr-no-gutter-m' }))
            .append($('<span/>', { class: 'name gr-6 gr-5-m align-self-center' }))
            .append($('<span/>', { class: 'gr-3 gr-4-m align-self-center still', id: 'spot' })));

        let selected_symbol = selected;
        if (Object.keys(all_symbols).indexOf(selected) === -1) selected_symbol = '';
        Object.keys(all_symbols).forEach((symbol, idx) => {
            if (all_symbols[symbol].is_active) {
                const is_current = (!selected_symbol && idx === 0) || symbol === selected_symbol;
                const $current   = $('<div/>', { value: symbol, class: 'gr-4 gr-4-t gr-4-m' })
                    .append($('<img/>', { src: urlForStatic(`/images/pages/mb_trading/${symbol.toLowerCase()}.svg`), alt: '' }))
                    .append($('<div/>', { text: all_symbols[symbol].display, class: 'name align-self-center' }));
                $list.append($current);
                if (is_current) {
                    MBContract.setCurrentItem($underlyings, symbol, 1);
                }
            }
        });
    };

    const selectors = '.trade_form, .price-table, #trading_bottom_content, .selection_wrapper, #trade_live_chart';

    const handleMarketClosed = () => {
        $(selectors).setVisibility(0);
        hideShowMbTrading('hide');
        MBNotifications.show({ text: localize('Market is closed. Please try again later.'), uid: 'MARKET_CLOSED' });
        symbols_timeout = setTimeout(() => { getSymbols(); }, 30000);
    };

    const handleMarketOpen = () => {
        $(selectors).setVisibility(1);
        hideShowMbTrading('show');
        MBNotifications.hide('MARKET_CLOSED');
    };

    const hideShowMbTrading = (action) => {
        const classes = ['gr-5 ', 'gr-12 ']; // the extra space is so gr-5-m is not replaced
        const show    = action === 'show';
        const $parent = $('#mb_trading').parent();
        $parent.attr('class', $parent.attr('class').replace(classes[+show], classes[+!show]));
    };

    const clearSymbolTimeout = () => {
        clearTimeout(symbols_timeout);
    };

    /*
     * Function to call when underlying has changed
     */
    const processMarketUnderlying = () => {
        const underlying = $('#underlying').attr('value');
        MBDefaults.set('underlying', underlying);

        commonTrading.showFormOverlay();

        // forget the old tick id i.e. close the old tick stream
        processForgetTicks().then(() => {
            // get ticks for current underlying
            MBTick.request(underlying);
            MBTick.clean();
        });

        BinarySocket.clearTimeouts();

        getContracts(underlying);
    };

    const getContracts = (underlying) => {
        const req = {
            contracts_for: (underlying || MBDefaults.get('underlying')),
            currency     : MBContract.getCurrency(),
            product_type : 'multi_barrier',
        };
        BinarySocket.send(req).then((response) => {
            MBNotifications.hide('CONNECTION_ERROR');
            MBContract.setContractsResponse(response);
            // contracts_for is triggered every 15 seconds to check for expired barriers
            // but we don't want to send proposal in that case
            // so getContracts will be called without underlying param to distinguish these two cases
            processContract(response, underlying);
        });
        if (contract_timeout) clearContractTimeout();
        contract_timeout = setTimeout(getContracts, 15000);
    };

    const clearContractTimeout = () => { clearTimeout(contract_timeout); };

    /*
     * Function to display contract form for current underlying
     */
    const processContract = (contracts, should_send_proposal) => {
        if (getPropertyValue(contracts, 'error')) {
            MBNotifications.show({ text: contracts.error.message, uid: contracts.error.code });
            // Hide trading form but still display the chart
            $('#period').parents('.selection_wrapper').setVisibility(0);
            $(`.price-table, ${Client.isLoggedIn() ? '#tab_explanation' : '#trade_analysis'}`).setVisibility(0);
            showChart();
            return;
        }

        State.set('is_chart_allowed', !(contracts.contracts_for && contracts.contracts_for.feed_license && contracts.contracts_for.feed_license === 'chartonly'));

        checkMarketStatus(contracts.contracts_for.close);

        MBContract.populateOptions(should_send_proposal);
        if (should_send_proposal) {
            processPriceRequest();
        } else {
            processExpiredBarriers();
        }
    };

    const checkMarketStatus = (close) => {
        const now = window.time.unix();

        // if market is closed, else if market is open
        if (now > close) {
            if (market_status === 'open') {
                handleMarketClosed();
            }
            market_status = 'closed';
        } else {
            if (market_status === 'closed') {
                getSymbols();
                handleMarketOpen();
            }
            market_status = 'open';
        }
    };

    const processPriceRequest = () => {
        MBPrice.increaseReqId();
        MBPrice.showPriceOverlay();
        const durations = MBDefaults.get('period').split('_');
        const is_crypto = isCryptocurrency(MBDefaults.get('currency'));
        const payout    = parseFloat(MBDefaults.get(`payout${is_crypto ? '_crypto' : ''}`));

        const req = {
            proposal_array: 1,
            subscribe     : 1,
            basis         : 'payout',
            amount        : Client.isJPClient() ? (parseInt(payout) || 1) * 1000 : payout,
            currency      : MBContract.getCurrency(),
            symbol        : MBDefaults.get('underlying'),
            passthrough   : { req_id: MBPrice.getReqId() },
            date_expiry   : durations[1],
            contract_type : [],
            barriers      : [],
            product_type  : 'multi_barrier',

            trading_period_start: durations[0],
        };

        const available_contracts = MBContract.getCurrentContracts();
        // contract_type
        available_contracts.forEach(c => req.contract_type.push(c.contract_type));

        // barriers
        let all_expired = true;
        const contract  = available_contracts[0];
        contract.available_barriers.forEach((barrier) => {
            const barrier_item = {};
            if (+contract.barriers === 2) {
                barrier_item.barrier  = barrier[1];
                barrier_item.barrier2 = barrier[0];
            } else {
                barrier_item.barrier = barrier;
            }
            if (!barrierHasExpired(contract.expired_barriers, barrier_item.barrier, barrier_item.barrier2)) {
                all_expired = false;
                req.barriers.push(barrier_item);
            }
        });

        processForgetProposals().then(() => {
            // send request
            if (req.barriers.length) {
                MBPrice.addPriceObj(req);
                BinarySocket.send(req, { callback: processProposal });
            }
        });

        // all barriers expired
        if (all_expired) {
            MBNotifications.show({ text: `${localize('All barriers in this trading window are expired')}.`, uid: 'ALL_EXPIRED' });
            MBPrice.hidePriceOverlay();
        } else {
            MBNotifications.hide('ALL_EXPIRED');
        }
    };

    const processProposal = (response) => {
        const req_id = MBPrice.getReqId();
        if (response.passthrough.req_id === req_id) {
            if (response.error) {
                const error_message = response.error.error ? response.error.error.message : response.error.message;
                MBNotifications.show({ text: error_message, uid: 'PROPOSAL', dismissible: false });
                return;
            }
            MBNotifications.hide('PROPOSAL');
            MBPrice.display(response);
        }
    };

    const processExpiredBarriers = () => {
        const contracts = MBContract.getCurrentContracts();
        let expired_barrier,
            $expired_barrier_element;
        contracts.forEach((c) => {
            const expired_barriers = c.expired_barriers;
            for (let i = 0; i < expired_barriers.length; i++) {
                if (+c.barriers === 2) {
                    expired_barrier = [expired_barriers[i][0], expired_barriers[i][1]].join('_');
                } else {
                    expired_barrier = expired_barriers[i];
                }
                $expired_barrier_element = $(`div [data-barrier="${expired_barrier}"]`);
                if ($expired_barrier_element.length > 0) {
                    processForgetProposal(expired_barrier);
                    $expired_barrier_element.remove();
                }
            }
        });
    };

    const barrierHasExpired = (expired_barriers, barrier, barrier2) => {
        if (barrier2) {
            return containsArray(expired_barriers, [[barrier2, barrier]]);
        }
        return (expired_barriers.indexOf((barrier).toString()) > -1);
    };

    const processForgetProposal = (expired_barrier) => {
        const prices = MBPrice.getPrices();
        Object.keys(prices[expired_barrier]).forEach((c) => {
            if (!getPropertyValue(prices[expired_barrier][c], 'error')) {
                BinarySocket.send({ forget: prices[expired_barrier][c].proposal.id });
            }
        });
    };

    const processForgetProposals = () => {
        MBPrice.showPriceOverlay();
        const forget_proposal = BinarySocket.send({ forget_all: 'proposal_array' });
        forget_proposal.then(() => {
            MBPrice.cleanup();
        });
        return forget_proposal;
    };

    const processForgetTicks = () => BinarySocket.send({ forget_all: 'ticks' });

    const forgetTradingStreams = () => {
        processForgetProposals();
        processForgetTicks();
    };

    const containsArray = (array, val) => {
        const hash = {};
        for (let i = 0; i < array.length; i++) {
            hash[array[i]] = i;
        }
        return getPropertyValue(hash, val);
    };

    const onUnload = () => {
        forgetTradingStreams();
        clearSymbolTimeout();
        clearContractTimeout();
        MBSymbols.clearData();
        MBTick.clean();
    };

    return {
        getSymbols,
        getContracts,
        processPriceRequest,
        processForgetTicks,
        onUnload,
    };
})();

module.exports = MBProcess;
<|endoftext|>


<commit_before>/**
  Handles routes related to users.

  @class UserRoute
  @extends Discourse.Route
  @namespace Discourse
  @module Discourse
**/
Discourse.UserRoute = Discourse.Route.extend({

  model: function(params) {

    // If we're viewing the currently logged in user, return that object
    // instead.
    var currentUser = Discourse.User.current();
    if (params.username.toLowerCase() === currentUser.get('username_lower')) {
      return currentUser;
    }

    return Discourse.User.create({username: params.username});
  },

  serialize: function(params) {
    if (!params) return {};
    return { username: Em.get(params, 'username').toLowerCase() };
  },

  setupController: function(controller, user) {
    user.findDetails();
    controller.set('model', user);

    // Add a search context
    this.controllerFor('search').set('searchContext', user.get('searchContext'));
  },

  activate: function() {
    this._super();
    var user = this.modelFor('user');
    Discourse.MessageBus.subscribe("/users/" + user.get('username_lower'), function(data) {
      user.loadUserAction(data);
    });
  },

  deactivate: function() {
    this._super();
    Discourse.MessageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower'));

    // Remove the search context
    this.controllerFor('search').set('searchContext', null);
  }

});
<commit_msg>FIX: Error viewing user page when not logged in.<commit_after>/**
  Handles routes related to users.

  @class UserRoute
  @extends Discourse.Route
  @namespace Discourse
  @module Discourse
**/
Discourse.UserRoute = Discourse.Route.extend({

  model: function(params) {

    // If we're viewing the currently logged in user, return that object
    // instead.
    var currentUser = Discourse.User.current();
    if (currentUser && (params.username.toLowerCase() === currentUser.get('username_lower'))) {
      return currentUser;
    }

    return Discourse.User.create({username: params.username});
  },

  serialize: function(params) {
    if (!params) return {};
    return { username: Em.get(params, 'username').toLowerCase() };
  },

  setupController: function(controller, user) {
    user.findDetails();
    controller.set('model', user);

    // Add a search context
    this.controllerFor('search').set('searchContext', user.get('searchContext'));
  },

  activate: function() {
    this._super();
    var user = this.modelFor('user');
    Discourse.MessageBus.subscribe("/users/" + user.get('username_lower'), function(data) {
      user.loadUserAction(data);
    });
  },

  deactivate: function() {
    this._super();
    Discourse.MessageBus.unsubscribe("/users/" + this.modelFor('user').get('username_lower'));

    // Remove the search context
    this.controllerFor('search').set('searchContext', null);
  }

});
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var fold = function(data, initialValue, func) {
  var x = initialValue;
  for (var i=0;i<data.length;++i) {
    x = func(x,data[i]);
  }

  return x;
};

var calcStats = function(data) {

  var listIsOfLength0 = data.length === 0;

  var min = listIsOfLength0 ? undefined : data.reduce(function(x,y) { return Math.min(x,y); });
  var max = listIsOfLength0 ? undefined : fold(data,data[0], Math.max);
  var cnt = listIsOfLength0 ? 0 : fold(data,0, function(x,y) { return x + 1; });

  var sum = listIsOfLength0 ? 0 : fold(data,0, function(x,y) { return x + y; });

  return {
    average: listIsOfLength0 ? undefined : (sum / cnt),
    count: cnt,
    minValue: min,
    maxValue: max
  };
};

var testFold = function() {
  assert.equal('banana', fold([], 'banana', undefined));
  assert.equal('3', fold([1,2,3], '', function(x,y) { return y; }));
};

var testMaximum = function() {
  var emptyList = calcStats([]);
  assert.equal(undefined, emptyList.maxValue);

  var singletonList = calcStats([1]);
  assert.equal(1, singletonList.maxValue);
};

var testMinimum = function() {
  var emptyList = calcStats([]);
  assert.equal(undefined, emptyList.minValue);

  var singletonList = calcStats([1]);
  assert.equal(1, singletonList.minValue);

  var zeroMin = calcStats([0]);
  assert.equal(0, zeroMin.minValue);

  var multiple = calcStats([0,1,2,3,4,5]);
  assert.equal(0, multiple.minValue);
};

var testNumberOfElements = function() {
  assert.equal(0, calcStats([]).count);
  assert.equal(1, calcStats([1]).count);
  assert.equal(5, calcStats([5,6,7,8,9]).count);
};

var testAverage = function() {
  var stats = calcStats([]);
  assert.equal(undefined, stats.average);

  var zero = calcStats([0]);
  assert.equal(0, zero.average);

  var three = calcStats([0,1,2]);
  assert.equal(1, three.average);
};

var test = function() {
  testFold();
  testMinimum();
  testMaximum();
  testNumberOfElements();
  testAverage();

  console.log(calcStats([6,9,15,-2,92,11]));
};

test();

// Retrospectives
// - using fold is nice
// - you can use .reduce
<commit_msg>more reduce<commit_after>"use strict";

var assert = require('assert');

var fold = function(data, initialValue, func) {
  var x = initialValue;
  for (var i=0;i<data.length;++i) {
    x = func(x,data[i]);
  }

  return x;
};

var calcStats = function(data) {

  var listIsOfLength0 = data.length === 0;

  var min = listIsOfLength0 ? undefined : data.reduce(function(x,y) { return Math.min(x,y); });
  var max = listIsOfLength0 ? undefined : data.reduce(function(x,y) { return Math.max(x,y); });
  var cnt = listIsOfLength0 ? 0 : fold(data,0, function(x,y) { return x + 1; });

  var sum = listIsOfLength0 ? 0 : fold(data,0, function(x,y) { return x + y; });

  return {
    average: listIsOfLength0 ? undefined : (sum / cnt),
    count: cnt,
    minValue: min,
    maxValue: max
  };
};

var testFold = function() {
  assert.equal('banana', fold([], 'banana', undefined));
  assert.equal('3', fold([1,2,3], '', function(x,y) { return y; }));
};

var testMaximum = function() {
  var emptyList = calcStats([]);
  assert.equal(undefined, emptyList.maxValue);

  var singletonList = calcStats([1]);
  assert.equal(1, singletonList.maxValue);
};

var testMinimum = function() {
  var emptyList = calcStats([]);
  assert.equal(undefined, emptyList.minValue);

  var singletonList = calcStats([1]);
  assert.equal(1, singletonList.minValue);

  var zeroMin = calcStats([0]);
  assert.equal(0, zeroMin.minValue);

  var multiple = calcStats([0,1,2,3,4,5]);
  assert.equal(0, multiple.minValue);
};

var testNumberOfElements = function() {
  assert.equal(0, calcStats([]).count);
  assert.equal(1, calcStats([1]).count);
  assert.equal(5, calcStats([5,6,7,8,9]).count);
};

var testAverage = function() {
  var stats = calcStats([]);
  assert.equal(undefined, stats.average);

  var zero = calcStats([0]);
  assert.equal(0, zero.average);

  var three = calcStats([0,1,2]);
  assert.equal(1, three.average);
};

var test = function() {
  testFold();
  testMinimum();
  testMaximum();
  testNumberOfElements();
  testAverage();

  console.log(calcStats([6,9,15,-2,92,11]));
};

test();

// Retrospectives
// - using fold is nice
// - you can use .reduce
// - but reduce is weird, takes 4 arguments (not 2)
<|endoftext|>


<commit_before>Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Toggle');

Rickshaw.Graph.Behavior.Series.Toggle = function(args) {

	this.graph = args.graph;
	this.legend = args.legend;

	var self = this;

	this.addAnchor = function(line) {

		var $ = jQuery;

		var anchor = document.createElement('a');
		anchor.innerHTML = '✔';
		anchor.classList.add('action');
		line.element.insertBefore(anchor, line.element.firstChild);

		anchor.onclick = function(e) {
			if (line.series.disabled) {
				line.series.enable();
				line.element.classList.remove('disabled');
			} else {
				if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return;
				line.series.disable();
				line.element.classList.add('disabled');
			}

		}.bind(this);

                var label = line.element.getElementsByTagName('span')[0];
                label.onclick = function(e){

                        var disableAllOtherLines = line.series.disabled;
                        if ( ! disableAllOtherLines ) {
                                for ( var i = 0; i < self.legend.lines.length; i++ ) {
                                        var l = self.legend.lines[i];
                                        if ( line.series === l.series ) {
                                                // noop
                                        } else if ( l.series.disabled ) {
                                                // noop
                                        } else {
                                                disableAllOtherLines = true;
                                                break;
                                        }
                                }
                        }

                        // show all or none
                        if ( disableAllOtherLines ) {

                                // these must happen first or else we try ( and probably fail ) to make a no line graph
                                line.series.enable();
                                line.element.classList.remove('disabled');

                                self.legend.lines.forEach(function(l){
                                        if ( line.series === l.series ) {
                                                // noop
                                        } else {
                                                l.series.disable();
                                                l.element.classList.add('disabled');
                                        }
                                });

                        } else {

                                self.legend.lines.forEach(function(l){
                                        l.series.enable();
                                        l.element.classList.remove('disabled');
                                });

                        }

                };

	};

	if (this.legend) {

		if (typeof $ != 'undefined' && $(this.legend.list).sortable) {

			$(this.legend.list).sortable( {
				start: function(event, ui) {
					ui.item.bind('no.onclick',
						function(event) {
							event.preventDefault();
						}
					);
				},
				stop: function(event, ui) {
					setTimeout(function(){
						ui.item.unbind('no.onclick');
					}, 250);
				}
			});
		}

		this.legend.lines.forEach( function(l) {
			self.addAnchor(l);
		} );
	}

	this._addBehavior = function() {

		this.graph.series.forEach( function(s) {

			s.disable = function() {

				if (self.graph.series.length <= 1) {
					throw('only one series left');
				}

				s.disabled = true;
				self.graph.update();
			};

			s.enable = function() {
				s.disabled = false;
				self.graph.update();
			};
		} );
	};
	this._addBehavior();

	this.updateBehaviour = function () { this._addBehavior() };

};
<commit_msg>Define $ variable in correct scope<commit_after>Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Toggle');

Rickshaw.Graph.Behavior.Series.Toggle = function(args) {

	this.graph = args.graph;
	this.legend = args.legend;

	var self = this;

	this.addAnchor = function(line) {

		var anchor = document.createElement('a');
		anchor.innerHTML = '✔';
		anchor.classList.add('action');
		line.element.insertBefore(anchor, line.element.firstChild);

		anchor.onclick = function(e) {
			if (line.series.disabled) {
				line.series.enable();
				line.element.classList.remove('disabled');
			} else {
				if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return;
				line.series.disable();
				line.element.classList.add('disabled');
			}

		}.bind(this);

                var label = line.element.getElementsByTagName('span')[0];
                label.onclick = function(e){

                        var disableAllOtherLines = line.series.disabled;
                        if ( ! disableAllOtherLines ) {
                                for ( var i = 0; i < self.legend.lines.length; i++ ) {
                                        var l = self.legend.lines[i];
                                        if ( line.series === l.series ) {
                                                // noop
                                        } else if ( l.series.disabled ) {
                                                // noop
                                        } else {
                                                disableAllOtherLines = true;
                                                break;
                                        }
                                }
                        }

                        // show all or none
                        if ( disableAllOtherLines ) {

                                // these must happen first or else we try ( and probably fail ) to make a no line graph
                                line.series.enable();
                                line.element.classList.remove('disabled');

                                self.legend.lines.forEach(function(l){
                                        if ( line.series === l.series ) {
                                                // noop
                                        } else {
                                                l.series.disable();
                                                l.element.classList.add('disabled');
                                        }
                                });

                        } else {

                                self.legend.lines.forEach(function(l){
                                        l.series.enable();
                                        l.element.classList.remove('disabled');
                                });

                        }

                };

	};

	if (this.legend) {

		var $ = jQuery;
		if (typeof $ != 'undefined' && $(this.legend.list).sortable) {

			$(this.legend.list).sortable( {
				start: function(event, ui) {
					ui.item.bind('no.onclick',
						function(event) {
							event.preventDefault();
						}
					);
				},
				stop: function(event, ui) {
					setTimeout(function(){
						ui.item.unbind('no.onclick');
					}, 250);
				}
			});
		}

		this.legend.lines.forEach( function(l) {
			self.addAnchor(l);
		} );
	}

	this._addBehavior = function() {

		this.graph.series.forEach( function(s) {

			s.disable = function() {

				if (self.graph.series.length <= 1) {
					throw('only one series left');
				}

				s.disabled = true;
				self.graph.update();
			};

			s.enable = function() {
				s.disabled = false;
				self.graph.update();
			};
		} );
	};
	this._addBehavior();

	this.updateBehaviour = function () { this._addBehavior() };

};
<|endoftext|>


<commit_before>/*
    ___ usage ___ en_US ___
    usage: prolific test a

    ___ . ___
*/
require('arguable')(module, require('cadence')(function (async, program) {
    return 0
}))
<commit_msg>Reintroduce `compassion.test.js`.<commit_after>/*
    ___ usage ___ en_US ___
    usage: compassion test a

    ___ . ___
*/
require('arguable')(module, require('cadence')(function (async, program) {
    return 0
}))
<|endoftext|>


<commit_before>(function ($) {

/*
*
*  provides feedback form for zammad
*

<button id="feedback-form">Feedback</button>

*/

  var pluginName = 'ZammadForm',
  defaults = {
    lang: undefined,
    debug: false,
    noCSS: false,
    prefixCSS: 'zammad-form-',
    showTitle: false,
    messageTitle: 'Zammad Form',
    messageSubmit: 'Submit',
    messageThankYou: 'Thank you for your inquiry! We\'ll contact you as soon as possible.',
    messageNoConfig: 'Unable to load form config from server. Maybe feature is disabled.',
    attachmentSupport: false,
    attributes: [
      {
        display: 'Name',
        name: 'name',
        tag: 'input',
        type: 'text',
        placeholder: 'Your Name',
      },
      {
        display: 'Email',
        name: 'email',
        tag: 'input',
        type: 'email',
        placeholder: 'Your Email',
      },
      {
        display: 'Message',
        name: 'body',
        tag: 'textarea',
        placeholder: 'Your Message...',
        rows: 7,
      },
    ],
    translations: {
      de: {
        'Name': 'Name',
        'Your Name': 'Ihr Name',
        'Email': 'E-Mail',
        'Your Email': 'Ihre E-Mail',
        'Message': 'Nachricht',
        'Attachments': 'Anhänge',
        'Your Message...': 'Ihre Nachricht...',
      },
      es: {
        'Name': 'Nombre',
        'Your Name': 'tu Nombre',
        'Email': 'correo electrónico',
        'Your Email': 'Tu correo electrónico',
        'Message': 'Mensaje',
        'Attachments': 'archivos adjuntos',
        'Your Message...': 'tu Mensaje...',
      },
      fr: {
        'Name': 'Prénom',
        'Your Name': 'Votre nom',
        'Email': 'Email',
        'Your Email': 'Votre Email',
        'Message': 'Message',
        'Attachments': 'Pièces jointes',
        'Your Message...': 'Votre message...',
      },
    }
  };

  function Plugin(element, options) {
    this.element  = element
    this.$element = $(element)

    this._defaults = defaults;
    this._name     = pluginName;

    this._endpoint_config = '/api/v1/form_config'
    this._endpoint_submit = '/api/v1/form_submit'
    this._script_location = '/assets/form/form.js'
    this._css_location    = '/assets/form/form.css'

    this._src = document.getElementById('zammad_form_script').src
    this.css_location = this._src.replace(this._script_location, this._css_location)
    this.endpoint_config = this._src.replace(this._script_location, this._endpoint_config)
    this.endpoint_submit = this._src.replace(this._script_location, this._endpoint_submit)

    this.options = $.extend({}, defaults, options)
    if (!this.options.lang) {
      this.options.lang = $('html').attr('lang')
    }
    if (this.options.lang) {
      this.options.lang = this.options.lang.replace(/-.+?$/, '')
      this.log('debug', "lang: " + this.options.lang)
    }

    this._config = {}

    this.init()
  }

  Plugin.prototype.init = function () {
    var _this = this,
      params = {}

    _this.log('debug', 'init', this._src)

    if (!_this.options.noCSS) {
      _this.loadCss(_this.css_location)
    }

    if (_this.options.attachmentSupport === true || _this.options.attachmentSupport === 'true') {
      var attachment = {
        display: 'Attachments',
        name: 'file[]',
        tag: 'input',
        type: 'file',
        repeat: 1,
      }
      _this.options.attributes.push(attachment)
    }

    _this.log('debug', 'endpoint_config: ' + _this.endpoint_config)
    _this.log('debug', 'endpoint_submit: ' + _this.endpoint_submit)

    // load config
    if (this.options.test) {
      params.test = true
    }
    $.ajax({
      url: _this.endpoint_config,
      data: params
    }).done(function(data) {
      _this.log('debug', 'config:', data)
      _this._config = data
    }).fail(function(jqXHR, textStatus, errorThrown) {
      if (jqXHR.status == 401) {
        _this.log('error', 'Faild to load form config, feature is disabled!')
      }
      else {
        _this.log('error', 'Faild to load form config!')
      }
      _this.noConfig()
    });

    // show form
    if (!this.options.modal) {
      _this.render()
    }

    // bind form on call
    else {
      this.$element.off('click.zammad-form').on('click.zammad-form', function (e) {
        e.preventDefault()
        _this.render()
        return true
      })
    }
  }

  // load css
  Plugin.prototype.loadCss = function(filename) {
    if (document.createStyleSheet) {
      document.createStyleSheet(filename)
    }
    else {
      $('<link rel="stylesheet" type="text/css" href="' + filename + '" />').appendTo('head')
    }
  }

  // send
  Plugin.prototype.submit = function() {
    var _this = this

    // check min modal open time
    if (_this.modalOpenTime) {
      var currentTime = new Date().getTime()
      var diff = currentTime - _this.modalOpenTime.getTime()
      _this.log('debug', 'currentTime', currentTime)
      _this.log('debug', 'modalOpenTime', _this.modalOpenTime.getTime())
      _this.log('debug', 'diffTime', diff)
      if (diff < 1000*8) {
        alert('Sorry, you look like an robot!')
        return
      }
    }

    // disable form
    _this.$form.find('button').prop('disabled', true)

    $.ajax({
      method: 'post',
      url: _this.endpoint_submit,
      data: _this.getParams(),
      cache: false,
      contentType: false,
      processData: false,
    }).done(function(data) {

      // removed errors
      _this.$form.find('.has-error').removeClass('has-error')

      // set errors
      if (data.errors) {
        $.each(data.errors, function( key, value ) {
          _this.$form.find('[name=' + key + ']').closest('.form-group').addClass('has-error')
        })
        _this.$form.find('button').prop('disabled', false)
        return
      }

      // ticket has been created
      _this.thanks(data)

    }).fail(function() {
      _this.$form.find('button').prop('disabled', false)
      alert('Faild to submit form!')
    });
  }

  // get params
  Plugin.prototype.getParams = function() {
    var _this = this

    var formData = new FormData(_this.$form[0])

    if (!formData.has('title')) {
      formData.append('title', this.options.messageTitle)
    }

    if (this.options.test) {
      formData.append('test', true)
    }
    _this.log('debug', 'formData', formData)
    return formData
  }

  Plugin.prototype.closeModal = function() {
    if (this.$modal) {
      this.$modal.remove()
    }
  }

  // render form
  Plugin.prototype.render = function(e) {
    var _this = this
    _this.closeModal()
    _this.modalOpenTime = new Date()
    _this.log('debug', 'modalOpenTime:', _this.modalOpenTime)

    var element = "
\

\

\
        <form class=\"zammad-form\"></form>\

\

"

    if (!this.options.modal) {
      element = '
<form class="zammad-form"></form>
'
    }

    var $element = $(element)
    var $form = $element.find('form')
    if (this.options.showTitle && this.options.messageTitle != '') {
      $form.append('
' + this.options.messageTitle + '
')
    }
    $.each(this.options.attributes, function(index, value) {
      var item = $('
' + _this.T(value.display) + '
')
      for (var i=0; i < (value.repeat ? value.repeat : 1); i++) {
        if (value.tag == 'input') {
          item.append('<input class="form-control" name="' + value.name + '" type="' + value.type + '" placeholder="' + _this.T(value.placeholder) + '">')
        }
        else if (value.tag == 'textarea') {
          item.append('<textarea class="form-control" name="' + value.name + '" placeholder="' + _this.T(value.placeholder) + '" rows="' + value.rows + '"></textarea>')
        }
      }
      $form.append(item)
    })
    $form.append('<button type="submit" class="btn">' + this.options.messageSubmit + '</button')

    this.$modal = $element
    this.$form  = $form

    // bind on close
    $element.find('.js-close').off('click.zammad-form').on('click.zammad-form', function (e) {
      e.preventDefault()
      _this.closeModal()
      return true
    })

    // bind form submit
    $element.off('submit.zammad-form').on('submit.zammad-form', function (e) {
      e.preventDefault()
      _this.submit()
      return true
    })

    // show form
    if (!this.options.modal) {
      _this.$element.html($element)
    }

    // append modal to body
    else {
      $('body').append($element)
    }

  }

  // thanks
  Plugin.prototype.thanks = function(data) {
    var thankYou = this.options.messageThankYou
    if (data.ticket && data.ticket.number) {
      thankYou = thankYou.replace('%s', data.ticket.number)
    }
    var message = $('
' + thankYou + '
')
    this.$form.html(message)
  }

  // unable to load config
  Plugin.prototype.noConfig = function(e) {
    var message = $('
' + this.options.messageNoConfig + '
')
    if (this.$form) {
      this.$form.html(message)
    }
    this.$element.html(message)
  }

  // log method
  Plugin.prototype.log = function() {
    var args = Array.prototype.slice.call(arguments)
    var level = args.shift()
    if (!this.options.debug && level == 'debug') {
      return
    }
    args.unshift(this._name + '||' + level)
    console.log.apply(console, args)

    var logString = ''
    $.each( args, function(index, item) {
      logString = logString + ' '
      if (typeof item == 'object') {
        logString = logString + JSON.stringify(item)
      }
      else if (item && item.toString) {
        logString = logString + item.toString()
      }
      else {
        logString = logString + item
      }
    })
    $('.js-logDisplay').prepend('
' + logString + '
')
  }

  // translation method
  Plugin.prototype.T = function() {
    var string = arguments[0]
    var items = 2 <= arguments.length ? slice.call(arguments, 1) : []
    if (this.options.lang && this.options.lang !== 'en') {
      if (!this.options.translations[this.options.lang]) {
        this.log('debug', "Translation '" + this.options.lang + "' needed!")
      }
      else {
        translations = this.options.translations[this.options.lang]
        if (!translations[string]) {
          this.log('debug', "Translation needed for '" + this.options.lang + "' " + string + "'")
        }
        string = translations[string] || string
      }
    }
    if (items) {
      for (i = 0, len = items.length; i < len; i++) {
        item = items[i]
        string = string.replace(/%s/, item)
      }
    }
    return string
  }

  $.fn[pluginName] = function (options) {
    return this.each(function () {
      var instance = $.data(this, 'plugin_' + pluginName)
      if (instance) {
        instance.$element.empty()
        $.data(this, 'plugin_' + pluginName, undefined)
      }
      $.data(
        this, 'plugin_' + pluginName,
        new Plugin(this, options)
      );
    });
  }

}(jQuery));
<commit_msg>Fixed issue #685 - Feedback form not working in Safari.<commit_after>(function ($) {

/*
*
*  provides feedback form for zammad
*

<button id="feedback-form">Feedback</button>

*/

  var pluginName = 'ZammadForm',
  defaults = {
    lang: undefined,
    debug: false,
    noCSS: false,
    prefixCSS: 'zammad-form-',
    showTitle: false,
    messageTitle: 'Zammad Form',
    messageSubmit: 'Submit',
    messageThankYou: 'Thank you for your inquiry! We\'ll contact you as soon as possible.',
    messageNoConfig: 'Unable to load form config from server. Maybe feature is disabled.',
    attachmentSupport: false,
    attributes: [
      {
        display: 'Name',
        name: 'name',
        tag: 'input',
        type: 'text',
        placeholder: 'Your Name',
      },
      {
        display: 'Email',
        name: 'email',
        tag: 'input',
        type: 'email',
        placeholder: 'Your Email',
      },
      {
        display: 'Message',
        name: 'body',
        tag: 'textarea',
        placeholder: 'Your Message...',
        rows: 7,
      },
    ],
    translations: {
      de: {
        'Name': 'Name',
        'Your Name': 'Ihr Name',
        'Email': 'E-Mail',
        'Your Email': 'Ihre E-Mail',
        'Message': 'Nachricht',
        'Attachments': 'Anhänge',
        'Your Message...': 'Ihre Nachricht...',
      },
      es: {
        'Name': 'Nombre',
        'Your Name': 'tu Nombre',
        'Email': 'correo electrónico',
        'Your Email': 'Tu correo electrónico',
        'Message': 'Mensaje',
        'Attachments': 'archivos adjuntos',
        'Your Message...': 'tu Mensaje...',
      },
      fr: {
        'Name': 'Prénom',
        'Your Name': 'Votre nom',
        'Email': 'Email',
        'Your Email': 'Votre Email',
        'Message': 'Message',
        'Attachments': 'Pièces jointes',
        'Your Message...': 'Votre message...',
      },
    }
  };

  function Plugin(element, options) {
    this.element  = element
    this.$element = $(element)

    this._defaults = defaults;
    this._name     = pluginName;

    this._endpoint_config = '/api/v1/form_config'
    this._endpoint_submit = '/api/v1/form_submit'
    this._script_location = '/assets/form/form.js'
    this._css_location    = '/assets/form/form.css'

    this._src = document.getElementById('zammad_form_script').src
    this.css_location = this._src.replace(this._script_location, this._css_location)
    this.endpoint_config = this._src.replace(this._script_location, this._endpoint_config)
    this.endpoint_submit = this._src.replace(this._script_location, this._endpoint_submit)

    this.options = $.extend({}, defaults, options)
    if (!this.options.lang) {
      this.options.lang = $('html').attr('lang')
    }
    if (this.options.lang) {
      this.options.lang = this.options.lang.replace(/-.+?$/, '')
      this.log('debug', "lang: " + this.options.lang)
    }

    this._config = {}

    this.init()
  }

  Plugin.prototype.init = function () {
    var _this = this,
      params = {}

    _this.log('debug', 'init', this._src)

    if (!_this.options.noCSS) {
      _this.loadCss(_this.css_location)
    }

    if (_this.options.attachmentSupport === true || _this.options.attachmentSupport === 'true') {
      var attachment = {
        display: 'Attachments',
        name: 'file[]',
        tag: 'input',
        type: 'file',
        repeat: 1,
      }
      _this.options.attributes.push(attachment)
    }

    _this.log('debug', 'endpoint_config: ' + _this.endpoint_config)
    _this.log('debug', 'endpoint_submit: ' + _this.endpoint_submit)

    // load config
    if (this.options.test) {
      params.test = true
    }
    $.ajax({
      url: _this.endpoint_config,
      data: params
    }).done(function(data) {
      _this.log('debug', 'config:', data)
      _this._config = data
    }).fail(function(jqXHR, textStatus, errorThrown) {
      if (jqXHR.status == 401) {
        _this.log('error', 'Faild to load form config, feature is disabled!')
      }
      else {
        _this.log('error', 'Faild to load form config!')
      }
      _this.noConfig()
    });

    // show form
    if (!this.options.modal) {
      _this.render()
    }

    // bind form on call
    else {
      this.$element.off('click.zammad-form').on('click.zammad-form', function (e) {
        e.preventDefault()
        _this.render()
        return true
      })
    }
  }

  // load css
  Plugin.prototype.loadCss = function(filename) {
    if (document.createStyleSheet) {
      document.createStyleSheet(filename)
    }
    else {
      $('<link rel="stylesheet" type="text/css" href="' + filename + '" />').appendTo('head')
    }
  }

  // send
  Plugin.prototype.submit = function() {
    var _this = this

    // check min modal open time
    if (_this.modalOpenTime) {
      var currentTime = new Date().getTime()
      var diff = currentTime - _this.modalOpenTime.getTime()
      _this.log('debug', 'currentTime', currentTime)
      _this.log('debug', 'modalOpenTime', _this.modalOpenTime.getTime())
      _this.log('debug', 'diffTime', diff)
      if (diff < 1000*8) {
        alert('Sorry, you look like an robot!')
        return
      }
    }

    // disable form
    _this.$form.find('button').prop('disabled', true)

    $.ajax({
      method: 'post',
      url: _this.endpoint_submit,
      data: _this.getParams(),
      cache: false,
      contentType: false,
      processData: false,
    }).done(function(data) {

      // removed errors
      _this.$form.find('.has-error').removeClass('has-error')

      // set errors
      if (data.errors) {
        $.each(data.errors, function( key, value ) {
          _this.$form.find('[name=' + key + ']').closest('.form-group').addClass('has-error')
        })
        _this.$form.find('button').prop('disabled', false)
        return
      }

      // ticket has been created
      _this.thanks(data)

    }).fail(function() {
      _this.$form.find('button').prop('disabled', false)
      alert('Faild to submit form!')
    });
  }

  // get params
  Plugin.prototype.getParams = function() {
    var _this = this

    var formData = new FormData(_this.$form[0])

    /* unfortunaly not working in safari and some IEs - https://developer.mozilla.org/en-US/docs/Web/API/FormData
    if (!formData.has('title')) {
      formData.append('title', this.options.messageTitle)
    }
    */
    if (!_this.$form.find('[name=title]').val()) {
      formData.append('title', this.options.messageTitle)
    }

    if (this.options.test) {
      formData.append('test', true)
    }
    _this.log('debug', 'formData', formData)
    return formData
  }

  Plugin.prototype.closeModal = function() {
    if (this.$modal) {
      this.$modal.remove()
    }
  }

  // render form
  Plugin.prototype.render = function(e) {
    var _this = this
    _this.closeModal()
    _this.modalOpenTime = new Date()
    _this.log('debug', 'modalOpenTime:', _this.modalOpenTime)

    var element = "
\

\

\
        <form class=\"zammad-form\"></form>\

\

"

    if (!this.options.modal) {
      element = '
<form class="zammad-form"></form>
'
    }

    var $element = $(element)
    var $form = $element.find('form')
    if (this.options.showTitle && this.options.messageTitle != '') {
      $form.append('
' + this.options.messageTitle + '
')
    }
    $.each(this.options.attributes, function(index, value) {
      var item = $('
' + _this.T(value.display) + '
')
      for (var i=0; i < (value.repeat ? value.repeat : 1); i++) {
        if (value.tag == 'input') {
          item.append('<input class="form-control" name="' + value.name + '" type="' + value.type + '" placeholder="' + _this.T(value.placeholder) + '">')
        }
        else if (value.tag == 'textarea') {
          item.append('<textarea class="form-control" name="' + value.name + '" placeholder="' + _this.T(value.placeholder) + '" rows="' + value.rows + '"></textarea>')
        }
      }
      $form.append(item)
    })
    $form.append('<button type="submit" class="btn">' + this.options.messageSubmit + '</button')

    this.$modal = $element
    this.$form  = $form

    // bind on close
    $element.find('.js-close').off('click.zammad-form').on('click.zammad-form', function (e) {
      e.preventDefault()
      _this.closeModal()
      return true
    })

    // bind form submit
    $element.off('submit.zammad-form').on('submit.zammad-form', function (e) {
      e.preventDefault()
      _this.submit()
      return true
    })

    // show form
    if (!this.options.modal) {
      _this.$element.html($element)
    }

    // append modal to body
    else {
      $('body').append($element)
    }

  }

  // thanks
  Plugin.prototype.thanks = function(data) {
    var thankYou = this.options.messageThankYou
    if (data.ticket && data.ticket.number) {
      thankYou = thankYou.replace('%s', data.ticket.number)
    }
    var message = $('
' + thankYou + '
')
    this.$form.html(message)
  }

  // unable to load config
  Plugin.prototype.noConfig = function(e) {
    var message = $('
' + this.options.messageNoConfig + '
')
    if (this.$form) {
      this.$form.html(message)
    }
    this.$element.html(message)
  }

  // log method
  Plugin.prototype.log = function() {
    var args = Array.prototype.slice.call(arguments)
    var level = args.shift()
    if (!this.options.debug && level == 'debug') {
      return
    }
    args.unshift(this._name + '||' + level)
    console.log.apply(console, args)

    var logString = ''
    $.each( args, function(index, item) {
      logString = logString + ' '
      if (typeof item == 'object') {
        logString = logString + JSON.stringify(item)
      }
      else if (item && item.toString) {
        logString = logString + item.toString()
      }
      else {
        logString = logString + item
      }
    })
    $('.js-logDisplay').prepend('
' + logString + '
')
  }

  // translation method
  Plugin.prototype.T = function() {
    var string = arguments[0]
    var items = 2 <= arguments.length ? slice.call(arguments, 1) : []
    if (this.options.lang && this.options.lang !== 'en') {
      if (!this.options.translations[this.options.lang]) {
        this.log('debug', "Translation '" + this.options.lang + "' needed!")
      }
      else {
        translations = this.options.translations[this.options.lang]
        if (!translations[string]) {
          this.log('debug', "Translation needed for '" + this.options.lang + "' " + string + "'")
        }
        string = translations[string] || string
      }
    }
    if (items) {
      for (i = 0, len = items.length; i < len; i++) {
        item = items[i]
        string = string.replace(/%s/, item)
      }
    }
    return string
  }

  $.fn[pluginName] = function (options) {
    return this.each(function () {
      var instance = $.data(this, 'plugin_' + pluginName)
      if (instance) {
        instance.$element.empty()
        $.data(this, 'plugin_' + pluginName, undefined)
      }
      $.data(
        this, 'plugin_' + pluginName,
        new Plugin(this, options)
      );
    });
  }

}(jQuery));
<|endoftext|>


<commit_before>var request = require('request');
var EventEmitter = require("events").EventEmitter;
var util = require("util");

/**
 * 物件TGBOT
 *
 * @class TGBOT
 * @constructor
 * @param {Object} options 選項
 */
function TGBOT (options) {
    EventEmitter.call(this);

    this.token = options.token;
    this.pollingTimeout = options.pollingTimeout || 40;
    this.help = options.help;

    this.cmdList = {};

    this.lastOffset = null;
    this.username = null;
    this.cmdRegex = null;
}
util.inherits(TGBOT, EventEmitter);
/**
 * 啟動bot
 * @method start
 */
TGBOT.prototype.start = function(){
    var self=this;
    this.getUpdates(self.pollingTimeout,null);
    this.getMe(function(error,result){
        if(error){console.log(error)}
        self.username = result.username;
        self.cmdRegex = new RegExp("^\/(\\w+)(?:@"+self.username+")?(?: (.*))?$","i");
    });
    if(self.help){
        self.addCmd('help',function(toolBox,args){
            if(args[0]){
                toolBox.replyMsg(self.cmdList[args[0]].helpMsg || (self.cmdList[args[0]].desc || "Command " + args[0] + " not found or nothing to display :("));
            }else{
                toolBox.replyMsg(self.genHelp());
            }
        },"Show help","/help command\nShow how to use the command\nIf no argument,show the commnad list");
    }
};

TGBOT.prototype.getUpdates = function(timeout,offset){
    var self = this;

    var params = {
        timeout: timeout
    };
    if (offset != null) {
        params.offset = offset;
    }

    self._invoke('getUpdates',params,function(error,result){
        if(error!=null){
            self.lastOffset = null;
            console.log(error.toString());
        }else{
            result.forEach(function(update){
                if (update.update_id >= self.lastOffset) {
                        self.lastOffset = update.update_id;
                    }
                if (update.message) {
                    self.emit('message', update.message);
                    if(update.message.text){self.execCmd(update.message)}
                    //console.log(update.message);
                } else if (update.inline_query) {
                    self.emit('inline_query', update.inline_query);
                } else if (update.chosen_inline_result) {
                    self.emit('chosen_inline_result', update.chosen_inline_result);
                }

            });
        }
        self.getUpdates(timeout,self.lastOffset+1);
    },timeout*1000+15000);

};

TGBOT.prototype._invoke = function(apiName,params,cb,timeout,multiPart){
    cb = cb || function(){};
    timeout = timeout || 15000;
    console.log("[INVOKE]",apiName,params);
    var targetURL = 'https://api.telegram.org/bot' + this.token + '/' + apiName;

    var requestData = {
        url: targetURL,
        timeout: timeout // 15 sec
    };

    if (!multiPart || !params) {
        params = params || {};
        requestData.form = params;
    } else {
        params = params || {};
        requestData.formData = params;
    }
    //console.log(requestData);
    request.post(requestData, function (err, response, body) {
        // console.log(response);
        if (err || response.statusCode !== 200) {
            return cb(err || new Error('unexpect response code: ' + response.statusCode + ' ' + body));
        }
        try {
            body = JSON.parse(body);
        } catch (e) {
            return cb(e);
        }
        if (body.ok !== true) {
            return cb (new Error('response is not ok'));
        }
        cb(null, body.result);
    });
};
/**
 * 獲取bot資訊
 * @method getMe
 * @param {Function} cb callback
 */
TGBOT.prototype.getMe = function getMe(cb) {
    return this._invoke('getMe', null , cb);
};
/**
 * 發送訊息
 * @method sendMessage
 * @param chat_id 要送到哪
 * @param {String} text 要發送的文字
 * @param {Object} [datas] 其他資料
 * @cb {Function} [cb] callback
 */
TGBOT.prototype.sendMessage = function sendMessage(chat_id, text, datas, cb) {
    datas = typeof datas === "object" ? datas : {};
    datas.chat_id = chat_id;
    datas.text = text;
    return this._invoke('sendMessage', datas , cb);
};
/**
 * 添加指令
 * @method addCmd
 * @param {String} cmd 指令名稱
 * @param {Function} script 指令的內容
 * @param {String} desc 簡短描述
 * @param {String} helpMsg 說明文字
 */
TGBOT.prototype.addCmd = function(cmd,script,desc,helpMsg){
    this.cmdList[cmd] = {cmd:cmd,script:script,desc:desc,helpMsg:helpMsg};
    console.log("Added Command : "+cmd);
};

TGBOT.prototype.execCmd = function(message){
    var self= this;
    var result = message.text.match(this.cmdRegex);
    var cmd,args;
    if(result){
        cmd=result[1];
        args = result[2] ? result[2].split(' ') : [];
        if (this.cmdList[cmd]) {
            this.cmdList[cmd].script(self.createToolBox(message),args,message);
            console.log("[COMMAND]",cmd,args);
        }
    }
};
/**
 * 建構toolBox物件
 * @method createToolBox
 * @param {Object} message 訊息
 * @return {Object} toolBox 工具盒
 *  sendToChat(text) 發送文字到訊息的來源
 *  replyMsg(text) 以文字回復訊息
 *  sendToUser(text) 發送文字給發送訊息的使用者

 */
TGBOT.prototype.createToolBox = function(message) {
    var self = this;

    var toolBox = {};
    toolBox.sendToChat = text => self.sendMessage(message.chat.id,text);
    toolBox.replyMsg = text => self.sendMessage(message.chat.id,text,{reply_to_message_id:message.message_id});
    toolBox.sendToUser = text => self.sendMessage(message.from.id,text);
    return toolBox;
};
/**
 * 產生Help
 * @method genHelp
 * @param {Function} Help的格式
 * @return {String} help help
 */
TGBOT.prototype.genHelp = function(format){
    var self = this;
    var help = "";
    format = format || (command => "/" + command.cmd + " : " + command.desc + "\n");
    for(var command in self.cmdList){
        help+=format(self.cmdList[command]);
    }
    return help;
};

module.exports = TGBOT;<commit_msg>args變成從一開始<commit_after>var request = require('request');
var EventEmitter = require("events").EventEmitter;
var util = require("util");

/**
 * 物件TGBOT
 *
 * @class TGBOT
 * @constructor
 * @param {Object} options 選項
 */
function TGBOT (options) {
    EventEmitter.call(this);

    this.token = options.token;
    this.pollingTimeout = options.pollingTimeout || 40;
    this.help = options.help;

    this.cmdList = {};

    this.lastOffset = null;
    this.username = null;
    this.cmdRegex = null;
}
util.inherits(TGBOT, EventEmitter);
/**
 * 啟動bot
 * @method start
 */
TGBOT.prototype.start = function(){
    var self=this;
    this.getUpdates(self.pollingTimeout,null);
    this.getMe(function(error,result){
        if(error){console.log(error)}
        self.username = result.username;
        self.cmdRegex = new RegExp("^\/(\\w+)(?:@"+self.username+")?(?: (.*))?$","i");
    });
    if(self.help){
        self.addCmd('help',function(toolBox,args){
            if(args[0]){
                toolBox.replyMsg(self.cmdList[args[1]].helpMsg || (self.cmdList[args[1]].desc || "Command " + args[1] + " not found or nothing to display :("));
            }else{
                toolBox.replyMsg(self.genHelp());
            }
        },"Show help","/help command\nShow how to use the command\nIf no argument,show the commnad list");
    }
};

TGBOT.prototype.getUpdates = function(timeout,offset){
    var self = this;

    var params = {
        timeout: timeout
    };
    if (offset != null) {
        params.offset = offset;
    }

    self._invoke('getUpdates',params,function(error,result){
        if(error!=null){
            self.lastOffset = null;
            console.log(error.toString());
        }else{
            result.forEach(function(update){
                if (update.update_id >= self.lastOffset) {
                        self.lastOffset = update.update_id;
                    }
                if (update.message) {
                    self.emit('message', update.message);
                    if(update.message.text){self.execCmd(update.message)}
                    //console.log(update.message);
                } else if (update.inline_query) {
                    self.emit('inline_query', update.inline_query);
                } else if (update.chosen_inline_result) {
                    self.emit('chosen_inline_result', update.chosen_inline_result);
                }

            });
        }
        self.getUpdates(timeout,self.lastOffset+1);
    },timeout*1000+15000);

};

TGBOT.prototype._invoke = function(apiName,params,cb,timeout,multiPart){
    cb = cb || function(){};
    timeout = timeout || 15000;
    console.log("[INVOKE]",apiName,params);
    var targetURL = 'https://api.telegram.org/bot' + this.token + '/' + apiName;

    var requestData = {
        url: targetURL,
        timeout: timeout // 15 sec
    };

    if (!multiPart || !params) {
        params = params || {};
        requestData.form = params;
    } else {
        params = params || {};
        requestData.formData = params;
    }
    //console.log(requestData);
    request.post(requestData, function (err, response, body) {
        // console.log(response);
        if (err || response.statusCode !== 200) {
            return cb(err || new Error('unexpect response code: ' + response.statusCode + ' ' + body));
        }
        try {
            body = JSON.parse(body);
        } catch (e) {
            return cb(e);
        }
        if (body.ok !== true) {
            return cb (new Error('response is not ok'));
        }
        cb(null, body.result);
    });
};
/**
 * 獲取bot資訊
 * @method getMe
 * @param {Function} cb callback
 */
TGBOT.prototype.getMe = function getMe(cb) {
    return this._invoke('getMe', null , cb);
};
/**
 * 發送訊息
 * @method sendMessage
 * @param chat_id 要送到哪
 * @param {String} text 要發送的文字
 * @param {Object} [datas] 其他資料
 * @cb {Function} [cb] callback
 */
TGBOT.prototype.sendMessage = function sendMessage(chat_id, text, datas, cb) {
    datas = typeof datas === "object" ? datas : {};
    datas.chat_id = chat_id;
    datas.text = text;
    return this._invoke('sendMessage', datas , cb);
};
/**
 * 添加指令
 * @method addCmd
 * @param {String} cmd 指令名稱
 * @param {Function} script 指令的內容
 * @param {String} desc 簡短描述
 * @param {String} helpMsg 說明文字
 */
TGBOT.prototype.addCmd = function(cmd,script,desc,helpMsg){
    this.cmdList[cmd] = {cmd:cmd,script:script,desc:desc,helpMsg:helpMsg};
    console.log("Added Command : "+cmd);
};

TGBOT.prototype.execCmd = function(message){
    var self= this;
    var result = message.text.match(this.cmdRegex);
    var cmd;
    var args = [];
    if(result){
        cmd=result[1];
        args[0] = result[2];
        args = args.concat(result[2] ? result[2].split(' ') : []);
        if (this.cmdList[cmd]) {
            this.cmdList[cmd].script(self.createToolBox(message),args,message);
            console.log("[COMMAND]",cmd,args);
        }
    }
};
/**
 * 建構toolBox物件
 * @method createToolBox
 * @param {Object} message 訊息
 * @return {Object} toolBox 工具盒
 *  sendToChat(text) 發送文字到訊息的來源
 *  replyMsg(text) 以文字回復訊息
 *  sendToUser(text) 發送文字給發送訊息的使用者

 */
TGBOT.prototype.createToolBox = function(message) {
    var self = this;

    var toolBox = {};
    toolBox.sendToChat = text => self.sendMessage(message.chat.id,text);
    toolBox.replyMsg = text => self.sendMessage(message.chat.id,text,{reply_to_message_id:message.message_id});
    toolBox.sendToUser = text => self.sendMessage(message.from.id,text);
    return toolBox;
};
/**
 * 產生Help
 * @method genHelp
 * @param {Function} Help的格式
 * @return {String} help help
 */
TGBOT.prototype.genHelp = function(format){
    var self = this;
    var help = "";
    format = format || (command => "/" + command.cmd + " : " + command.desc + "\n");
    for(var command in self.cmdList){
        help+=format(self.cmdList[command]);
    }
    return help;
};

module.exports = TGBOT;<|endoftext|>


<commit_before>function FranchiseCharacterDisplay(franchise, container){
  this.franchise = franchise;
  this.container = container;
}

FranchiseCharacterDisplay.prototype.getBox = function(){
  this.box = $("<div>").attr({
    class: "franchise-container"
  });
  return this.box;
}
FranchiseCharacterDisplay.prototype.render = function(){
  this.box.empty();
  this.box.append($("<p>").append(this.franchise.name));
  var list = $("<ul>");
  var contained = this.container.characters.filter(function(char){
    if(char.franchise_id == this.franchise.id){
      return true
    }
    else{
      return false;
    }
  }, this);
  contained.forEach(function(member){
    var that = this;
    var d = new CharacterListItem(member, this.container);
    list.append(d.getItem(false, function(){
      that.render();
    }));
  }, this);
  this.box.append(list);
  this.box.append(this.newCharacterButton());
}

FranchiseCharacterDisplay.prototype.newCharacterButton = function(){
  console.log("Getting the new character button");
  var that = this;
  var btn = $("<button>").append("Add a character");
  btn.click(function(){
    btn.replaceWith(that.newCharacterList());
  });
  return btn;
}
FranchiseCharacterDisplay.prototype.newCharacterList = function(){
  var list = $("<ul>").attr({class: "new-character-list"});
  this.charactersNotInContainer().forEach(function(char){
    var that = this;
    var item = $("<li>");
    item.append(char.name);
    item.click(function(){
      that.container.addCharacter(char);
      that.render();
    });
    list.append(item);
  },this);
  return list;
}
FranchiseCharacterDisplay.prototype.charactersNotInContainer = function(){
var newChars = this.franchise.characters.filter(function(char){
    return this.container.characters.indexOf(char) == -1;
  }, this);
  console.log("Characters not in story:");
  console.log(newChars);
  return newChars;
}
<commit_msg>Semicolons<commit_after>function FranchiseCharacterDisplay(franchise, container){
  this.franchise = franchise;
  this.container = container;
}

FranchiseCharacterDisplay.prototype.getBox = function(){
  this.box = $("<div>").attr({
    class: "franchise-container"
  });
  return this.box;
};

FranchiseCharacterDisplay.prototype.render = function(){
  this.box.empty();
  this.box.append($("<p>").append(this.franchise.name));
  var list = $("<ul>");
  var contained = this.container.characters.filter(function(char){
    if(char.franchise_id == this.franchise.id){
      return true
    }
    else{
      return false;
    }
  }, this);
  contained.forEach(function(member){
    var that = this;
    var d = new CharacterListItem(member, this.container);
    list.append(d.getItem(false, function(){
      that.render();
    }));
  }, this);
  this.box.append(list);
  this.box.append(this.newCharacterButton());
}

FranchiseCharacterDisplay.prototype.newCharacterButton = function(){
  console.log("Getting the new character button");
  var that = this;
  var btn = $("<button>").append("Add a character");
  btn.click(function(){
    btn.replaceWith(that.newCharacterList());
  });
  return btn;
};

FranchiseCharacterDisplay.prototype.newCharacterList = function(){
  var list = $("<ul>").attr({class: "new-character-list"});
  this.charactersNotInContainer().forEach(function(char){
    var that = this;
    var item = $("<li>");
    item.append(char.name);
    item.click(function(){
      that.container.addCharacter(char);
      that.render();
    });
    list.append(item);
  },this);
  return list;
};

FranchiseCharacterDisplay.prototype.charactersNotInContainer = function(){
  var newChars = this.franchise.characters.filter(function(char){
    return this.container.characters.indexOf(char) == -1;
  }, this);

  console.log("Characters not in story:");
  console.log(newChars);
  return newChars;
};
<|endoftext|>


<commit_before>/// <binding BeforeBuild='default-vs' />
var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();
var runSequence = require('run-sequence');
var mainBowerFiles = require('main-bower-files');
var tsProject = plugins.typescript.createProject('scripts/tsconfig.json');

var files = {
    css: ['css/**/*.css'],
    index: 'index.html',
    indexBkp: 'index.html.bkp',
    js: ['scripts/lib/**/*.js', 'scripts/appbundle.js'],
    maps: ['scripts/appbundle.js.map'],
    scss: 'scss/*.scss',
    ts: 'scripts/**/*.ts'
};

var paths = {
    build: 'build',
    css: 'css',
    project: '',
    scss: 'scss',
    ts: 'scripts',
    js: 'scripts',
    www: 'www'
};

function getCorrectPaths(folder, filesa, filesb) {
    var cfiles = [];
    for (var i = 0; i < filesa.length; i++) {
        cfiles.push(folder + filesa[i]);
    }
    if (filesb) {
        for (var j = 0; j < filesb.length; j++) {
            cfiles.push(folder + filesb[j]);
        }
    }
    return cfiles;
}

// Working without Visual Studio
    // 1.- npm install
    // 2.- gulp initialize
    // 3.- gulp watch

// Working with Visual Studio
    // 1.- npm install
    // 2.- gulp initialize

// Tasks definition
    gulp.task('default', function () {
        // Compile Sass, TypeScript and inject files into index.html
        runSequence('default:scss', 'default:ts', 'default:inject');
    });

    gulp.task('default-vs', function () {
        // Compile Sass, inject files into index.html (for Visual Studio)
        runSequence('default:scss', 'default:inject');
    });

    gulp.task('initialize', function () {
        // Download and install bower packages
        runSequence('initialize:tsd', 'initialize:bower');
    });

    gulp.task('build', function () {
        // Build project
        runSequence('build:js', 'build:clean');
    });

    gulp.task('zip', function () {
        // Zip template (only for dev purposes)
        runSequence('zip:copy', 'zip:compress', 'zip:clean');
    });

    gulp.task('watch', function () {
        runSequence('default');
        gulp.watch(paths.project + files.scss, ['default:scss']);
        return gulp.watch(paths.project + files.ts, ['default:ts']);
    });

// Tasks implementation
    // Download bower dependencies
    gulp.task('initialize:bower:install', function () {
        return plugins.bower();
    });

    gulp.task('initialize:tsd', function () {
        return gulp.src('tsd.json').pipe(plugins.tsd());
    });

    // Move bower packages to each folder
    gulp.task('initialize:bower', ['initialize:bower:install'], function () {
        gulp.src(mainBowerFiles('**/*.scss'), { base: 'bower_components' })
            .pipe(gulp.dest(paths.project + paths.scss + '/lib'));

        gulp.src(mainBowerFiles('**/*.js'), { base: 'bower_components' })
            .pipe(gulp.dest(paths.project + paths.www + '/' + paths.js + '/lib'));

        return gulp.src(mainBowerFiles('**/fonts/**.*'), { base: 'bower_components' })
            .pipe(gulp.dest(paths.project + paths.www + '/fonts/lib'));

        //return gulp.src(mainBowerFiles(['**/*.css', '**/*.scss']), { base: 'bower_components' })
            //.pipe(plugins.minifyCss({ keepSpecialComments: 0 }))// Because sass import fails with variables in comments
            //.pipe(plugins.rename(function (path) {
        //    path.extname = ".scss"
        //}))
            //.pipe(gulp.dest(paths.project + '/' + paths.scss + '/lib'));
    });

    // Inject files into index
    gulp.task('default:inject', function () {
        return gulp.src(paths.project + paths.www + '/' + files.index)
            .pipe(plugins.inject(
            gulp.src(getCorrectPaths(paths.project + paths.www + '/', files.css, files.js), { read: false }),
                {
                transform: function (filepath) {
                    if (filepath.indexOf('.js') > -1) {
                        return '<script src="' + filepath.replace(paths.project + paths.www + '/', '').substring(1) + '"></script>'
                    }
                    // Css
                    return ' <link rel="stylesheet" href="' + filepath.replace(paths.project + paths.www + '/', '').substring(1) + '">'
                }
            }
        ))
            .pipe(gulp.dest(paths.project + paths.www));
    });

    // Compile Sass
    gulp.task('default:scss', function () {
        return gulp.src(paths.project + files.scss)
            .pipe(plugins.sass())
            .pipe(plugins.autoprefixer({
            browsers: ['last 2 version', 'android 4']
        }))
            .pipe(gulp.dest(paths.project + paths.www + '/' + paths.css));
    });

    // Compile Typescript
    gulp.task('default:ts', function () {
        return tsProject.src()
            .pipe(plugins.typescript(tsProject))
            .pipe(gulp.dest(paths.project));
    });

    // Build JavaScript
    gulp.task('build:js', function () {
        return gulp.src(paths.project + paths.www + '/' + files.index)
            .pipe(plugins.inject(
            gulp.src(getCorrectPaths(paths.project + paths.www + '/', files.js))
                    .pipe(plugins.concat('scripts/build.min.js'))
                    .pipe(plugins.uglify())
                    .pipe(gulp.dest(paths.project + paths.www)),
                {
                transform: function (filepath) {
                    return '<script src="' + filepath.replace(paths.project + paths.www + '/', '').substring(1) + '"></script>'
                }
            }
        ))
        .pipe(gulp.dest(paths.project + paths.www))
    });

    // Build Clean
    gulp.task('build:clean', function () {
        return gulp.src(getCorrectPaths(paths.project + paths.www + '/', files.js, files.maps), { read: false })
            .pipe(plugins.clean({ force: true }));
    });

    // Create a Zip template for VS
    gulp.task('zip:copy', function () {
        var dest = '../zipCreatorTemp';
        gulp.src(['../__TemplateIcon.ico', '../LICENSE', '../*.md', '../*.sln', '../*.vstemplate'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest));

        gulp.src('*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App'));

        gulp.src('merges/**/*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/merges'));

        gulp.src('plugins/**/*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/plugins'));

        gulp.src('res/**/*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/res'));

        gulp.src(['scss/**/*.*', '!scss/lib/**/*.*'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/scss'));

        gulp.src(['scripts/**/*.*', '!scripts/lib/**/*.*'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/scripts'));

        return gulp.src(['www/**/*.*', '!www/fonts/lib/**/*.*', '!www/css/**/*.*', '!www/scripts/appbundle.*'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/www'));
    });

    var zip;
    gulp.task('zip:compress', function () {
        var dest = '../zipCreatorTemp';
        var d = new Date(Date.now());
        zip = 'Cordova-' + d.getDate() + '_' + (d.getMonth()+1) + '_' + d.getFullYear() + '-' + d.getHours() + '_' + d.getMinutes() + '_' + d.getSeconds();
        return gulp.src(dest + '/**/*')
            .pipe(plugins.zip(zip + '.zip'))
            .pipe(gulp.dest('../'));
    });

    gulp.task('zip:clean', function () {
        var dest = '../zipCreatorTemp';
        return gulp.src(dest, {read: false})
            .pipe(plugins.clean({force: true}))
    });<commit_msg>Update gulpfile<commit_after>/// <binding BeforeBuild='default-vs' />
var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();
var runSequence = require('run-sequence');
var mainBowerFiles = require('main-bower-files');
var tsProject = plugins.typescript.createProject('scripts/tsconfig.json');

var files = {
    css: ['css/**/*.css'],
    index: 'index.html',
    indexBkp: 'index.html.bkp',
    js: ['scripts/lib/**/*.js', 'scripts/appbundle.js'],
    maps: ['scripts/appbundle.js.map'],
    scss: 'scss/*.scss',
    ts: 'scripts/**/*.ts'
};

var paths = {
    build: 'build',
    css: 'css',
    project: '',
    scss: 'scss',
    ts: 'scripts',
    js: 'scripts',
    www: 'www'
};

// Working without Visual Studio
    // 1.- npm install
    // 2.- gulp initialize
    // 3.- gulp watch
    // 4.- ionic serve

// Working with Visual Studio
    // 1.- npm install
    // 2.- gulp initialize

// Tasks definition
    gulp.task('default', function () {
        // Compile Sass, TypeScript and inject files into index.html
        runSequence('default:scss', 'default:ts', 'default:inject');
    });

    gulp.task('default-vs', function () {
        // Compile Sass, inject files into index.html (for Visual Studio)
        runSequence('default:scss', 'default:inject');
    });

    gulp.task('initialize', function () {
        // Download and install bower packages
        runSequence('initialize:tsd', 'initialize:bower');
    });

    gulp.task('build', function () {
        // Build project
        runSequence('build:js', 'build:clean');
    });

    gulp.task('zip', function () {
        // Zip template (only for dev purposes)
        runSequence('zip:copy', 'zip:compress', 'zip:clean');
    });

    gulp.task('watch', function () {
        runSequence('default');
        gulp.watch(paths.project + files.scss, ['default:scss']);
        return gulp.watch(paths.project + files.ts, ['default:ts']);
    });

// Tasks implementation
    // Download bower dependencies
    gulp.task('initialize:bower:install', function () {
        return plugins.bower();
    });

    gulp.task('initialize:tsd', function () {
        return gulp.src('tsd.json').pipe(plugins.tsd());
    });

    // Move bower packages to each folder
    gulp.task('initialize:bower', ['initialize:bower:install'], function () {
        gulp.src(mainBowerFiles('**/*.scss'), { base: 'bower_components' })
            .pipe(gulp.dest(paths.project + paths.scss + '/lib'));

        gulp.src(mainBowerFiles('**/*.js'), { base: 'bower_components' })
            .pipe(gulp.dest(paths.project + paths.www + '/' + paths.js + '/lib'));

        return gulp.src(mainBowerFiles('**/fonts/**.*'), { base: 'bower_components' })
            .pipe(gulp.dest(paths.project + paths.www + '/fonts/lib'));

        //return gulp.src(mainBowerFiles(['**/*.css', '**/*.scss']), { base: 'bower_components' })
            //.pipe(plugins.minifyCss({ keepSpecialComments: 0 }))// Because sass import fails with variables in comments
            //.pipe(plugins.rename(function (path) {
        //    path.extname = ".scss"
        //}))
            //.pipe(gulp.dest(paths.project + '/' + paths.scss + '/lib'));
    });

    // Inject files into index
    gulp.task('default:inject', function () {
        return gulp.src(paths.project + paths.www + '/' + files.index)
            .pipe(plugins.inject(
            gulp.src(getCorrectPaths(paths.project + paths.www + '/', files.css, files.js), { read: false }),
                {
                transform: function (filepath) {
                    if (filepath.indexOf('.js') > -1) {
                        return '<script src="' + filepath.replace(paths.project + paths.www + '/', '').substring(1) + '"></script>'
                    }
                    // Css
                    return ' <link rel="stylesheet" href="' + filepath.replace(paths.project + paths.www + '/', '').substring(1) + '">'
                }
            }
        ))
            .pipe(gulp.dest(paths.project + paths.www));
    });

    // Compile Sass
    gulp.task('default:scss', function () {
        return gulp.src(paths.project + files.scss)
            .pipe(plugins.sass())
            .pipe(plugins.autoprefixer({
            browsers: ['last 2 version', 'android 4']
        }))
            .pipe(gulp.dest(paths.project + paths.www + '/' + paths.css));
    });

    // Compile Typescript
    gulp.task('default:ts', function () {
        return tsProject.src()
            .pipe(plugins.typescript(tsProject))
            .pipe(gulp.dest(paths.project));
    });

    // Build JavaScript
    gulp.task('build:js', function () {
        return gulp.src(paths.project + paths.www + '/' + files.index)
            .pipe(plugins.inject(
            gulp.src(getCorrectPaths(paths.project + paths.www + '/', files.js))
                    .pipe(plugins.concat('scripts/build.min.js'))
                    .pipe(plugins.uglify())
                    .pipe(gulp.dest(paths.project + paths.www)),
                {
                transform: function (filepath) {
                    return '<script src="' + filepath.replace(paths.project + paths.www + '/', '').substring(1) + '"></script>'
                }
            }
        ))
        .pipe(gulp.dest(paths.project + paths.www))
    });

    // Build Clean
    gulp.task('build:clean', function () {
        return gulp.src(getCorrectPaths(paths.project + paths.www + '/', files.js, files.maps), { read: false })
            .pipe(plugins.clean({ force: true }));
    });

    // Create a Zip template for VS
    gulp.task('zip:copy', function () {
        var dest = '../zipCreatorTemp';
        gulp.src(['../__TemplateIcon.ico', '../LICENSE', '../*.md', '../*.sln', '../*.vstemplate'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest));

        gulp.src('*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App'));

        gulp.src('merges/**/*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/merges'));

        gulp.src('plugins/**/*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/plugins'));

        gulp.src('res/**/*.*')
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/res'));

        gulp.src(['scss/**/*.*', '!scss/lib/**/*.*'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/scss'));

        gulp.src(['scripts/**/*.*', '!scripts/lib/**/*.*'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/scripts'));

        return gulp.src(['www/**/*.*', '!www/fonts/lib/**/*.*', '!www/css/**/*.*', '!www/scripts/appbundle.*'])
            .pipe(plugins.clone())
            .pipe(gulp.dest(dest + '/App/www'));
    });

    var zip;
    gulp.task('zip:compress', function () {
        var dest = '../zipCreatorTemp';
        var d = new Date(Date.now());
        zip = 'Cordova-' + d.getDate() + '_' + (d.getMonth()+1) + '_' + d.getFullYear() + '-' + d.getHours() + '_' + d.getMinutes() + '_' + d.getSeconds();
        return gulp.src(dest + '/**/*')
            .pipe(plugins.zip(zip + '.zip'))
            .pipe(gulp.dest('../'));
    });

    gulp.task('zip:clean', function () {
        var dest = '../zipCreatorTemp';
        return gulp.src(dest, {read: false})
            .pipe(plugins.clean({force: true}))
    });

    function getCorrectPaths(folder, filesa, filesb) {
        var cfiles = [];
        for (var i = 0; i < filesa.length; i++) {
            cfiles.push(folder + filesa[i]);
        }
        if (filesb) {
            for (var j = 0; j < filesb.length; j++) {
                cfiles.push(folder + filesb[j]);
            }
        }
        return cfiles;
    }<|endoftext|>