<commit_before>'use strict';

// ----------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, AuthenticationError, DDoSProtection } = require ('./base/errors');

// ----------------------------------------------------------------------------

module.exports = class coinbase extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'coinbase',
            'name': 'coinbase',
            'countries': [ 'US' ],
            'rateLimit': 400, // 10k calls per hour
            'version': 'v2',
            'userAgent': this.userAgents['chrome'],
            'headers': {
                'CB-VERSION': '2018-05-30',
            },
            'has': {
                'CORS': true,
                'cancelOrder': false,
                'createDepositAddress': false,
                'createOrder': false,
                'deposit': false,
                'fetchBalance': true,
                'fetchClosedOrders': false,
                'fetchCurrencies': true,
                'fetchDepositAddress': false,
                'fetchMarkets': false,
                'fetchMyTrades': true,
                'fetchOHLCV': false,
                'fetchOpenOrders': false,
                'fetchOrder': false,
                'fetchOrderBook': false,
                'fetchOrders': false,
                'fetchTicker': true,
                'fetchTickers': false,
                'fetchBidsAsks': false,
                'fetchTrades': false,
                'withdraw': false,
                'fetchTransactions': false,
                'fetchDeposits': false,
                'fetchWithdrawals': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg',
                'api': 'https://api.coinbase.com',
                'www': 'https://www.coinbase.com',
                'doc': 'https://developers.coinbase.com/api/v2',
                'fees': 'https://support.coinbase.com/customer/portal/articles/2109597-buy-sell-bank-transfer-fees',
                'referral': 'https://www.coinbase.com/join/58cbe25a355148797479dbd2',
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
            },
            'api': {
                'public': {
                    'get': [
                        'currencies',
                        'time',
                        'exchange-rates',
                        'users/{user_id}',
                        'prices/{symbol}/buy',
                        'prices/{symbol}/sell',
                        'prices/{symbol}/spot',
                    ],
                },
                'private': {
                    'get': [
                        'accounts',
                        'accounts/{account_id}',
                        'accounts/{account_id}/addresses',
                        'accounts/{account_id}/addresses/{address_id}',
                        'accounts/{account_id}/addresses/{address_id}/transactions',
                        'accounts/{account_id}/transactions',
                        'accounts/{account_id}/transactions/{transaction_id}',
                        'accounts/{account_id}/buys',
                        'accounts/{account_id}/buys/{buy_id}',
                        'accounts/{account_id}/sells',
                        'accounts/{account_id}/sells/{sell_id}',
                        'accounts/{account_id}/deposits',
                        'accounts/{account_id}/deposits/{deposit_id}',
                        'accounts/{account_id}/withdrawals',
                        'accounts/{account_id}/withdrawals/{withdrawal_id}',
                        'payment-methods',
                        'payment-methods/{payment_method_id}',
                        'user',
                        'user/auth',
                    ],
                    'post': [
                        'accounts',
                        'accounts/{account_id}/primary',
                        'accounts/{account_id}/addresses',
                        'accounts/{account_id}/transactions',
                        'accounts/{account_id}/transactions/{transaction_id}/complete',
                        'accounts/{account_id}/transactions/{transaction_id}/resend',
                        'accounts/{account_id}/buys',
                        'accounts/{account_id}/buys/{buy_id}/commit',
                        'accounts/{account_id}/sells',
                        'accounts/{account_id}/sells/{sell_id}/commit',
                        'accounts/{account_id}/deposists',
                        'accounts/{account_id}/deposists/{deposit_id}/commit',
                        'accounts/{account_id}/withdrawals',
                        'accounts/{account_id}/withdrawals/{withdrawal_id}/commit',
                    ],
                    'put': [
                        'accounts/{account_id}',
                        'user',
                    ],
                    'delete': [
                        'accounts/{id}',
                        'accounts/{account_id}/transactions/{transaction_id}',
                    ],
                },
            },
            'exceptions': {
                'two_factor_required': AuthenticationError, // 402 When sending money over 2fa limit
                'param_required': ExchangeError, // 400 Missing parameter
                'validation_error': ExchangeError, // 400 Unable to validate POST/PUT
                'invalid_request': ExchangeError, // 400 Invalid request
                'personal_details_required': AuthenticationError, // 400 User’s personal detail required to complete this request
                'identity_verification_required': AuthenticationError, // 400 Identity verification is required to complete this request
                'jumio_verification_required': AuthenticationError, // 400 Document verification is required to complete this request
                'jumio_face_match_verification_required': AuthenticationError, // 400 Document verification including face match is required to complete this request
                'unverified_email': AuthenticationError, // 400 User has not verified their email
                'authentication_error': AuthenticationError, // 401 Invalid auth (generic)
                'invalid_token': AuthenticationError, // 401 Invalid Oauth token
                'revoked_token': AuthenticationError, // 401 Revoked Oauth token
                'expired_token': AuthenticationError, // 401 Expired Oauth token
                'invalid_scope': AuthenticationError, // 403 User hasn’t authenticated necessary scope
                'not_found': ExchangeError, // 404 Resource not found
                'rate_limit_exceeded': DDoSProtection, // 429 Rate limit exceeded
                'internal_server_error': ExchangeError, // 500 Internal server error
            },
            'markets': {
                'BTC/USD': { 'id': 'btc-usd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD' },
                'LTC/USD': { 'id': 'ltc-usd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD' },
                'ETH/USD': { 'id': 'eth-usd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD' },
                'BCH/USD': { 'id': 'bch-usd', 'symbol': 'BCH/USD', 'base': 'BCH', 'quote': 'USD' },
            },
            'options': {
                'accounts': [
                    'wallet',
                    'fiat',
                    // 'vault',
                ],
            },
        });
    }

    async fetchTime () {
        let response = await this.publicGetTime ();
        let data = response['data'];
        return this.parse8601 (data['iso']);
    }

    async loadAccounts (reload = false) {
        if (reload) {
            this.accounts = await this.fetchAccounts ();
        } else {
            if (this.accounts) {
                return this.accounts;
            } else {
                this.accounts = await this.fetchAccounts ();
                this.accountsById = this.indexBy (this.accounts, 'id');
            }
        }
        return this.accounts;
    }

    async fetchAccounts () {
        await this.loadMarkets ();
        let response = await this.privateGetAccounts ();
        return response['data'];
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        const accountId = this.safeString (params, 'account_id');
        if (accountId === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchMyTrades requires an account_id or accountId extra parameter, u');
        }
        const query = this.omit (params, [ 'account_id', 'accountId' ]);
        const buys = await this.privateGetAccountsAccountIdBuys (this.extend ({
            'account_id': accountId,
        }, query));
        const sells = await this.privateGetAccountsAccountIdSells (this.extend ({
            'account_id': accountId,
        }, query));
        const parsedBuys = this.parseTrades (buys['data'], undefined, since, limit);
        const parsedSells = this.parseTrades (sells['data'], undefined, since, limit);
        let result = this.arrayConcat (parsedBuys, parsedSells);
        let sortedResult = this.sortBy (result, 'timestamp');
        return this.filterBySinceLimit (sortedResult, since, limit);
    }

    async fetchCurrencies (params = {}) {
        let response = await this.publicGetCurrencies (params);
        let currencies = response['data'];
        let result = {};
        for (let c = 0; c < currencies.length; c++) {
            let currency = currencies[c];
            let id = currency['id'];
            let name = currency['name'];
            let code = this.commonCurrencyCode (id);
            let minimum = this.safeFloat (currency, 'min_size');
            result[code] = {
                'id': id,
                'code': code,
                'info': currency, // the original payload
                'name': name,
                'active': true,
                'fee': undefined,
                'precision': undefined,
                'limits': {
                    'amount': {
                        'min': minimum,
                        'max': undefined,
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            };
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let timestamp = this.seconds ();
        let market = this.market (symbol);
        let request = this.extend ({
            'symbol': market['id'],
        }, params);
        let buy = await this.publicGetPricesSymbolBuy (request);
        let sell = await this.publicGetPricesSymbolSell (request);
        let spot = await this.publicGetPricesSymbolSpot (request);
        let ask = this.safeFloat (buy['data'], 'amount');
        let bid = this.safeFloat (sell['data'], 'amount');
        let last = this.safeFloat (spot['data'], 'amount');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'bid': bid,
            'ask': ask,
            'last': last,
            'high': undefined,
            'low': undefined,
            'bidVolume': undefined,
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': undefined,
            'quoteVolume': undefined,
            'info': {
                'buy': buy,
                'sell': sell,
                'spot': spot,
            },
        };
    }

    async fetchBalance (params = {}) {
        let response = await this.privateGetAccounts ();
        let balances = response['data'];
        let accounts = this.safeValue (params, 'type', this.options['accounts']);
        let result = { 'info': response };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            if (this.inArray (balance['type'], accounts)) {
                let currencyId = balance['balance']['currency'];
                let code = currencyId;
                if (currencyId in this.currencies_by_id)
                    code = this.currencies_by_id[currencyId]['code'];
                let total = this.safeFloat (balance['balance'], 'amount');
                let free = total;
                let used = undefined;
                if (code in result) {
                    result[code]['free'] += total;
                    result[code]['total'] += total;
                } else {
                    let account = {
                        'free': free,
                        'used': used,
                        'total': total,
                    };
                    result[code] = account;
                }
            }
        }
        return this.parseBalance (result);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let request = '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (method === 'GET') {
            if (Object.keys (query).length)
                request += '?' + this.urlencode (query);
        }
        let url = this.urls['api'] + '/' + this.version + request;
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let payload = '';
            if (method !== 'GET') {
                if (Object.keys (query).length) {
                    body = this.json (query);
                    payload = body;
                }
            }
            let what = nonce + method + '/' + this.version + request + payload;
            let signature = this.hmac (this.encode (what), this.encode (this.secret));
            headers = {
                'CB-ACCESS-KEY': this.apiKey,
                'CB-ACCESS-SIGN': signature,
                'CB-ACCESS-TIMESTAMP': nonce,
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (code, reason, url, method, headers, body) {
        if (typeof body !== 'string')
            return; // fallback to default error handler
        if (body.length < 2)
            return; // fallback to default error handler
        if ((body[0] === '{') || (body[0] === '[')) {
            let response = JSON.parse (body);
            let feedback = this.id + ' ' + body;
            //
            //    {"error": "invalid_request", "error_description": "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."}
            //
            // or
            //
            //    {
            //      "errors": [
            //        {
            //          "id": "not_found",
            //          "message": "Not found"
            //        }
            //      ]
            //    }
            //
            let exceptions = this.exceptions;
            let errorCode = this.safeString (response, 'error');
            if (errorCode !== undefined) {
                if (errorCode in exceptions) {
                    throw new exceptions[errorCode] (feedback);
                } else {
                    throw new ExchangeError (feedback);
                }
            }
            let errors = this.safeValue (response, 'errors');
            if (errors !== undefined) {
                if (Array.isArray (errors)) {
                    let numErrors = errors.length;
                    if (numErrors > 0) {
                        errorCode = this.safeString (errors[0], 'id');
                        if (errorCode !== undefined) {
                            if (errorCode in exceptions) {
                                throw new exceptions[errorCode] (feedback);
                            } else {
                                throw new ExchangeError (feedback);
                            }
                        }
                    }
                }
            }
            let data = this.safeValue (response, 'data');
            if (data === undefined)
                throw new ExchangeError (this.id + ' failed due to a malformed response ' + this.json (response));
        }
    }
};

<commit_msg>coinbase fetchMyTrades exception text edited<commit_after>'use strict';

// ----------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, AuthenticationError, DDoSProtection } = require ('./base/errors');

// ----------------------------------------------------------------------------

module.exports = class coinbase extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'coinbase',
            'name': 'coinbase',
            'countries': [ 'US' ],
            'rateLimit': 400, // 10k calls per hour
            'version': 'v2',
            'userAgent': this.userAgents['chrome'],
            'headers': {
                'CB-VERSION': '2018-05-30',
            },
            'has': {
                'CORS': true,
                'cancelOrder': false,
                'createDepositAddress': false,
                'createOrder': false,
                'deposit': false,
                'fetchBalance': true,
                'fetchClosedOrders': false,
                'fetchCurrencies': true,
                'fetchDepositAddress': false,
                'fetchMarkets': false,
                'fetchMyTrades': true,
                'fetchOHLCV': false,
                'fetchOpenOrders': false,
                'fetchOrder': false,
                'fetchOrderBook': false,
                'fetchOrders': false,
                'fetchTicker': true,
                'fetchTickers': false,
                'fetchBidsAsks': false,
                'fetchTrades': false,
                'withdraw': false,
                'fetchTransactions': false,
                'fetchDeposits': false,
                'fetchWithdrawals': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/40811661-b6eceae2-653a-11e8-829e-10bfadb078cf.jpg',
                'api': 'https://api.coinbase.com',
                'www': 'https://www.coinbase.com',
                'doc': 'https://developers.coinbase.com/api/v2',
                'fees': 'https://support.coinbase.com/customer/portal/articles/2109597-buy-sell-bank-transfer-fees',
                'referral': 'https://www.coinbase.com/join/58cbe25a355148797479dbd2',
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
            },
            'api': {
                'public': {
                    'get': [
                        'currencies',
                        'time',
                        'exchange-rates',
                        'users/{user_id}',
                        'prices/{symbol}/buy',
                        'prices/{symbol}/sell',
                        'prices/{symbol}/spot',
                    ],
                },
                'private': {
                    'get': [
                        'accounts',
                        'accounts/{account_id}',
                        'accounts/{account_id}/addresses',
                        'accounts/{account_id}/addresses/{address_id}',
                        'accounts/{account_id}/addresses/{address_id}/transactions',
                        'accounts/{account_id}/transactions',
                        'accounts/{account_id}/transactions/{transaction_id}',
                        'accounts/{account_id}/buys',
                        'accounts/{account_id}/buys/{buy_id}',
                        'accounts/{account_id}/sells',
                        'accounts/{account_id}/sells/{sell_id}',
                        'accounts/{account_id}/deposits',
                        'accounts/{account_id}/deposits/{deposit_id}',
                        'accounts/{account_id}/withdrawals',
                        'accounts/{account_id}/withdrawals/{withdrawal_id}',
                        'payment-methods',
                        'payment-methods/{payment_method_id}',
                        'user',
                        'user/auth',
                    ],
                    'post': [
                        'accounts',
                        'accounts/{account_id}/primary',
                        'accounts/{account_id}/addresses',
                        'accounts/{account_id}/transactions',
                        'accounts/{account_id}/transactions/{transaction_id}/complete',
                        'accounts/{account_id}/transactions/{transaction_id}/resend',
                        'accounts/{account_id}/buys',
                        'accounts/{account_id}/buys/{buy_id}/commit',
                        'accounts/{account_id}/sells',
                        'accounts/{account_id}/sells/{sell_id}/commit',
                        'accounts/{account_id}/deposists',
                        'accounts/{account_id}/deposists/{deposit_id}/commit',
                        'accounts/{account_id}/withdrawals',
                        'accounts/{account_id}/withdrawals/{withdrawal_id}/commit',
                    ],
                    'put': [
                        'accounts/{account_id}',
                        'user',
                    ],
                    'delete': [
                        'accounts/{id}',
                        'accounts/{account_id}/transactions/{transaction_id}',
                    ],
                },
            },
            'exceptions': {
                'two_factor_required': AuthenticationError, // 402 When sending money over 2fa limit
                'param_required': ExchangeError, // 400 Missing parameter
                'validation_error': ExchangeError, // 400 Unable to validate POST/PUT
                'invalid_request': ExchangeError, // 400 Invalid request
                'personal_details_required': AuthenticationError, // 400 User’s personal detail required to complete this request
                'identity_verification_required': AuthenticationError, // 400 Identity verification is required to complete this request
                'jumio_verification_required': AuthenticationError, // 400 Document verification is required to complete this request
                'jumio_face_match_verification_required': AuthenticationError, // 400 Document verification including face match is required to complete this request
                'unverified_email': AuthenticationError, // 400 User has not verified their email
                'authentication_error': AuthenticationError, // 401 Invalid auth (generic)
                'invalid_token': AuthenticationError, // 401 Invalid Oauth token
                'revoked_token': AuthenticationError, // 401 Revoked Oauth token
                'expired_token': AuthenticationError, // 401 Expired Oauth token
                'invalid_scope': AuthenticationError, // 403 User hasn’t authenticated necessary scope
                'not_found': ExchangeError, // 404 Resource not found
                'rate_limit_exceeded': DDoSProtection, // 429 Rate limit exceeded
                'internal_server_error': ExchangeError, // 500 Internal server error
            },
            'markets': {
                'BTC/USD': { 'id': 'btc-usd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD' },
                'LTC/USD': { 'id': 'ltc-usd', 'symbol': 'LTC/USD', 'base': 'LTC', 'quote': 'USD' },
                'ETH/USD': { 'id': 'eth-usd', 'symbol': 'ETH/USD', 'base': 'ETH', 'quote': 'USD' },
                'BCH/USD': { 'id': 'bch-usd', 'symbol': 'BCH/USD', 'base': 'BCH', 'quote': 'USD' },
            },
            'options': {
                'accounts': [
                    'wallet',
                    'fiat',
                    // 'vault',
                ],
            },
        });
    }

    async fetchTime () {
        let response = await this.publicGetTime ();
        let data = response['data'];
        return this.parse8601 (data['iso']);
    }

    async loadAccounts (reload = false) {
        if (reload) {
            this.accounts = await this.fetchAccounts ();
        } else {
            if (this.accounts) {
                return this.accounts;
            } else {
                this.accounts = await this.fetchAccounts ();
                this.accountsById = this.indexBy (this.accounts, 'id');
            }
        }
        return this.accounts;
    }

    async fetchAccounts () {
        await this.loadMarkets ();
        let response = await this.privateGetAccounts ();
        return response['data'];
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        const accountId = this.safeString (params, 'account_id');
        if (accountId === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchMyTrades requires an account_id or accountId extra parameter, use fetchAccounts or loadAccounts to get ids of all your accounts.');
        }
        const query = this.omit (params, [ 'account_id', 'accountId' ]);
        const buys = await this.privateGetAccountsAccountIdBuys (this.extend ({
            'account_id': accountId,
        }, query));
        const sells = await this.privateGetAccountsAccountIdSells (this.extend ({
            'account_id': accountId,
        }, query));
        const parsedBuys = this.parseTrades (buys['data'], undefined, since, limit);
        const parsedSells = this.parseTrades (sells['data'], undefined, since, limit);
        let result = this.arrayConcat (parsedBuys, parsedSells);
        let sortedResult = this.sortBy (result, 'timestamp');
        return this.filterBySinceLimit (sortedResult, since, limit);
    }

    async fetchCurrencies (params = {}) {
        let response = await this.publicGetCurrencies (params);
        let currencies = response['data'];
        let result = {};
        for (let c = 0; c < currencies.length; c++) {
            let currency = currencies[c];
            let id = currency['id'];
            let name = currency['name'];
            let code = this.commonCurrencyCode (id);
            let minimum = this.safeFloat (currency, 'min_size');
            result[code] = {
                'id': id,
                'code': code,
                'info': currency, // the original payload
                'name': name,
                'active': true,
                'fee': undefined,
                'precision': undefined,
                'limits': {
                    'amount': {
                        'min': minimum,
                        'max': undefined,
                    },
                    'price': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            };
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let timestamp = this.seconds ();
        let market = this.market (symbol);
        let request = this.extend ({
            'symbol': market['id'],
        }, params);
        let buy = await this.publicGetPricesSymbolBuy (request);
        let sell = await this.publicGetPricesSymbolSell (request);
        let spot = await this.publicGetPricesSymbolSpot (request);
        let ask = this.safeFloat (buy['data'], 'amount');
        let bid = this.safeFloat (sell['data'], 'amount');
        let last = this.safeFloat (spot['data'], 'amount');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'bid': bid,
            'ask': ask,
            'last': last,
            'high': undefined,
            'low': undefined,
            'bidVolume': undefined,
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'previousClose': undefined,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': undefined,
            'quoteVolume': undefined,
            'info': {
                'buy': buy,
                'sell': sell,
                'spot': spot,
            },
        };
    }

    async fetchBalance (params = {}) {
        let response = await this.privateGetAccounts ();
        let balances = response['data'];
        let accounts = this.safeValue (params, 'type', this.options['accounts']);
        let result = { 'info': response };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            if (this.inArray (balance['type'], accounts)) {
                let currencyId = balance['balance']['currency'];
                let code = currencyId;
                if (currencyId in this.currencies_by_id)
                    code = this.currencies_by_id[currencyId]['code'];
                let total = this.safeFloat (balance['balance'], 'amount');
                let free = total;
                let used = undefined;
                if (code in result) {
                    result[code]['free'] += total;
                    result[code]['total'] += total;
                } else {
                    let account = {
                        'free': free,
                        'used': used,
                        'total': total,
                    };
                    result[code] = account;
                }
            }
        }
        return this.parseBalance (result);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let request = '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (method === 'GET') {
            if (Object.keys (query).length)
                request += '?' + this.urlencode (query);
        }
        let url = this.urls['api'] + '/' + this.version + request;
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let payload = '';
            if (method !== 'GET') {
                if (Object.keys (query).length) {
                    body = this.json (query);
                    payload = body;
                }
            }
            let what = nonce + method + '/' + this.version + request + payload;
            let signature = this.hmac (this.encode (what), this.encode (this.secret));
            headers = {
                'CB-ACCESS-KEY': this.apiKey,
                'CB-ACCESS-SIGN': signature,
                'CB-ACCESS-TIMESTAMP': nonce,
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (code, reason, url, method, headers, body) {
        if (typeof body !== 'string')
            return; // fallback to default error handler
        if (body.length < 2)
            return; // fallback to default error handler
        if ((body[0] === '{') || (body[0] === '[')) {
            let response = JSON.parse (body);
            let feedback = this.id + ' ' + body;
            //
            //    {"error": "invalid_request", "error_description": "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."}
            //
            // or
            //
            //    {
            //      "errors": [
            //        {
            //          "id": "not_found",
            //          "message": "Not found"
            //        }
            //      ]
            //    }
            //
            let exceptions = this.exceptions;
            let errorCode = this.safeString (response, 'error');
            if (errorCode !== undefined) {
                if (errorCode in exceptions) {
                    throw new exceptions[errorCode] (feedback);
                } else {
                    throw new ExchangeError (feedback);
                }
            }
            let errors = this.safeValue (response, 'errors');
            if (errors !== undefined) {
                if (Array.isArray (errors)) {
                    let numErrors = errors.length;
                    if (numErrors > 0) {
                        errorCode = this.safeString (errors[0], 'id');
                        if (errorCode !== undefined) {
                            if (errorCode in exceptions) {
                                throw new exceptions[errorCode] (feedback);
                            } else {
                                throw new ExchangeError (feedback);
                            }
                        }
                    }
                }
            }
            let data = this.safeValue (response, 'data');
            if (data === undefined)
                throw new ExchangeError (this.id + ' failed due to a malformed response ' + this.json (response));
        }
    }
};

<|endoftext|>


<commit_before>/**
 *  creature object responsible for
 *
 *  1. init
 *  2. hilight
 *  3. de-light
 *  4. enter typing mode
 *  5. leave typing mode
 */
define([], function(){
var MovingStyle = {"scale":1, "static":2};

//殭屍答案打字區 prefix
var ZOMBIE_ANSWER_TYPING_AEAR_PREFIX = "Ans:　";

//boot場景，用來做一些遊戲啟動前的準備
var zombieInit = function(game){
var zombieFunc = function(zombie) {

    var ansAreaBackgroundColor =  '#000000';

    zombie.inputEnabled = true;
    zombie.tint = 0xFFFFFF;
    zombie.movingStyle = MovingStyle.scale;

    //zombie.events.onInputDown.add(zombieClick, this);
    //zombie.events.onInputOver.add(zombieMouseHover, this);
    //zombie.events.onInputOut.add(zombieMouseHoverOut, this);

    var dialogArea = game.make.text(0,
                                     -zombie.height*zombie.anchor.y + 0.3*zombie.height,
                                     "",
                                     { font: "30px Arial",
                                        fill: "#FFFFFF",
                                        wordWrap: true,
                                        align: "left",
                                        //backgroundColor: "#1C1C1C"
                                     });
    dialogArea.anchor.set(0.4, 1.0);
    dialogArea.wordWrapWidth = 200.0;

    var ansTextArea = game.make.text(0,
                                     0,
                                     "function hello() {\n    return('_____');        \n}",
                                     { font: "22px Arial",
                                       fill: "#40FF00",
                                       wordWrap: false,
                                       align: "left",
                                       backgroundColor: ansAreaBackgroundColor
                                     });
    ansTextArea.anchor.set(0.5, 0.5);

    var timeCountdownArea = game.make.text(ansTextArea.left, 50,
                                              "",
                                              { font: "15px Arial",
                                                fill: "#FFFFFF",
                                                wordWrap: false,
                                                align: "left",
                                                backgroundColor: "#1C1C1C"
                                              });
    //timeCountdownArea.anchor.set(0.5, 0.5);
    var ansTypeArea = game.make.text(timeCountdownArea.right + 22, 50,
                                     ZOMBIE_ANSWER_TYPING_AEAR_PREFIX,
                                     { font: "22px Arial",
                                       fill: "#F7FE2E",
                                       wordWrap: false,
                                       align: "left",
                                       backgroundColor: ansAreaBackgroundColor
                                     });
    //ansTypeArea.anchor.set(0.5, 0.5);
    // hide all dialogs
    ansTypeArea.alpha = 0.0;
    ansTextArea.alpha = 0.0;
    dialogArea.alpha = 0.0;
    timeCountdownArea.alpha = 0;
    zombie.addChild(ansTextArea);
    zombie.addChild(ansTypeArea)
    zombie.addChild(dialogArea)
    zombie.addChild(timeCountdownArea);
    zombie.ansTextArea = ansTextArea;
    zombie.ansTypeArea = ansTypeArea;
    zombie.dialogArea = dialogArea;
    zombie.timeCountdownArea = timeCountdownArea;

    zombie.ansTypeArea.x = zombie.ansTextArea.left;
    zombie.ansTypeArea.y = zombie.ansTextArea.bottom;

    updateTextArea(zombie);
    zombie.textArea.alpha = 0.0;
    // zombie's short quiz
    zombie.shortQuiz // ShortQuiz object

    // signal區
    zombie.onAttackSignal = new Phaser.Signal();
    zombie.onDeathSignal = new Phaser.Signal(); // signal before zombie is killed

    /**
      * reset all variables and events
      */
    zombie.refreshingUp =  function() {
      zombie.onAttackSignal.removeAll();
      zombie.onDeathSignal.removeAll();
      zombie.ansTypeArea.alpha = 0.0;
      zombie.ansTextArea.alpha = 0.0;
      zombie.textArea.alpha = 0.0;
      zombie.dialogArea.alpha = 0.0;
      zombie.timeCountdownArea.alpha = 0;
    }

    /**
      *  reset all variables and events , then kill
      */
    zombie.killThisZombie = function () {
      zombie.refreshingUp();
      zombie.onDeathSignal.dispatch(this);
      zombie.kill();
    }

    zombie.setAnsText = function (text){
        var oldText = zombie.ansTypeArea.text;
        zombie.ansTypeArea.text = text;

        if (zombie.ansTypeArea.width > zombie.ansTextArea.width){
            zombie.ansTypeArea.text = oldText;
        }
        zombie.updateAnsText();
    }

    zombie.clearAnsText = function (){
       zombie.ansTypeArea.text = ZOMBIE_ANSWER_TYPING_AEAR_PREFIX;
       zombie.updateTextArea();
    }

    zombie.updateAnsText = function () {
      zombie.ansTypeArea.x = zombie.ansTextArea.left;
      zombie.ansTypeArea.y = zombie.ansTextArea.bottom;
      zombie.updateTextArea();
    }

    /**
      *  @param shortQuiz : ShortQuiz -
      */
    zombie.setShortQuiz = function (shortQuiz) {
      zombie.shortQuiz = shortQuiz;
      zombie.ansTextArea.text = shortQuiz.quizAnswerWithBlankToFill;
      zombie.ansTypeArea.x = zombie.ansTextArea.left;
      zombie.ansTypeArea.y = zombie.ansTextArea.bottom;
      zombie.updateTextArea();
    }

    zombie.getShortQuizAnswer = function() {
      if(zombie.shortQuiz === undefined
          || zombie.shortQuiz === null) {

        return '';
      }
      return zombie.shortQuiz.answer;
    }

    zombie.getShortQuizQuestionDescription = function () {
      if(zombie.shortQuiz === undefined
          || zombie.shortQuiz === null) {

        return '';
      }
      return zombie.shortQuiz.quizDescription;
    }

    zombie.startCountdown = function(secToCount) {
      //console.log('lala');
      if(secToCount < 2) {
        secToCount = 2;
      }
      var countingSec = secToCount;
      zombie.timeCountdownArea.alpha = 1;
      zombie.timeCountdownArea.text = countingSec-- + '';
      var countdownLoop = game.time.events.loop(Phaser.Timer.SECOND, function() {
        zombie.timeCountdownArea.text = countingSec  + '';
        if(countingSec === 0) {
          zombie.onAttackSignal.dispatch(zombie);
          countingSec = secToCount;
        } else {
          countingSec--;
        }
      }, this);
    }

    zombie.Grrrrr = function (audioGrrrArray) {
      var foobar = game.rnd.integerInRange(0, audioGrrrArray.length - 1);
      var zg = audioGrrrArray[foobar];
      if(zg !== undefined && zg !== null) {
        // just in case
        zg.play();
      }
    }

    zombie.say = function (str) {
        if (str) {
            var dialogArea = zombie.dialogArea;
            dialogArea.text = str;
            dialogArea.x = -dialogArea.width + 10;
        }
    }

    zombie.nextDialog = function () {
        if (zombie.dialogs) {
            if (!zombie.nextDialogLine) {
                zombie.nextDialogLine = 0;
            }

            if (zombie.nextDialogLine < zombie.dialogs.length) {
                var dialogArea = zombie.dialogArea;
                dialogArea.text = zombie.dialogs[zombie.nextDialogLine];
                dialogArea.x = - dialogArea.width + 10;
                zombie.nextDialogLine += 1;

            } else {
                return -1;
            }

            return zombie.nextDialogLine;

        } else {
            return -1; // no more dialogs
        }

        return -1;
    }
    zombie.showDialog = function() {
        var zombieToHilight = zombie;

        // show answer area
        zombieToHilight.dialogArea.alpha = 1.0;
    };

    zombie.hilight = function() {
        var zombieToHilight = zombie;
        zombieToHilight.tint = 0xFE2E2E;

        // show answer area
        zombieToHilight.ansTypeArea.alpha = 1.0;
        zombieToHilight.ansTextArea.alpha = 1.0;
        zombieToHilight.textArea.alpha = 1.0;
        //zombieToHilight.dialogArea.alpha = 1.0;
    };

    zombie.lolight = function() {
        var zombieToHilight = zombie;
        zombieToHilight.tint = 0xFFFFFF;

        // show answer area
        zombieToHilight.ansTypeArea.alpha = 0.0;
        zombieToHilight.ansTextArea.alpha = 0.0;
        zombieToHilight.textArea.alpha = 0.0;
        //zombieToHilight.dialogArea.alpha = 1.0;
    };

    zombie.updateTextArea = function(){
        updateTextArea(zombie);
    }

    function updateTextArea(aZombie){
        var ansTypeArea = aZombie.ansTypeArea;
        var ansTextArea = aZombie.ansTextArea;
        var sprite = makeRectangle(ansTextArea.width, ansTypeArea.height+ansTextArea.height);

        var oldAlpha = 1.0;
        if (aZombie.textArea) {
            oldAlpha = aZombie.textArea.alpha; // preserve alpha setting
            aZombie.textArea.destroy();
        }

        if (sprite) {
            sprite.anchor.set(0.5);
            sprite.left = ansTextArea.left;
            sprite.top = ansTextArea.top;
            sprite.y += 10.0; // why?
            aZombie.addChild(sprite)
            aZombie.removeChild(ansTypeArea)
            aZombie.removeChild(ansTextArea)
            aZombie.addChild(ansTypeArea)
            aZombie.addChild(ansTextArea)
            aZombie.textArea = sprite;
            aZombie.textArea.alpha = oldAlpha;
        }

    }
    function makeRectangle(width, height){
        width += 20.0;
        height += 20.0;
        var stroke = 2.0;
        var bmd = game.make.bitmapData(width, height);

        bmd.ctx.beginPath();
        bmd.ctx.rect(0, 0, width, height);
        bmd.ctx.fillStyle = '#40FF00';
        bmd.ctx.fill();
        bmd.ctx.closePath();
        bmd.ctx.beginPath();
        bmd.ctx.rect(stroke, stroke, width-2*stroke, height-2*stroke);
        bmd.ctx.fillStyle = ansAreaBackgroundColor;
        bmd.ctx.fill();
        bmd.ctx.closePath();

        sprite = game.make.sprite(0, 0, bmd);
        return sprite;
    }

    //zombie.de_hilightAllZombie = function() {
        //zombie.tint = 0xFFFFFF;
    //};

    /**
     * isAbleToEsc : bool - 是否能逃離（按esc逃離，如果是殭屍遇上player就不能逃）
     */
    //function zombieIsDemandingForAnswer(zombieNeedAnswer, secToWait, isAbleToEsc) {
        //zombieNeedAnswer.zIndexBeforeClicked = zombieNeedAnswer.z;
        //zombieGroup.bringToTop(zombieNeedAnswer);
        //if(isDebug) {
            //console.log("zombie was on " + zombieNeedAnswer.zIndexBeforeClicked + ", and moved to front at " + zombieNeedAnswer.z);
        //}
        //zombieWaitingForAnswer = zombieNeedAnswer;
        //zombieWaitingForAnswer.isAbleToEsc = isAbleToEsc;
        //hilightZombie(zombieWaitingForAnswer);
        //pauseAllAliveZombies();
        //typingMode(true);
    //}

    /*
    function zombieMouseHover(zombieHoverOn, point) {
        if(isAnyZombieWaitingForAnswer() === false) {
            hilightZombie(zombieHoverOn);
        }
    }
    */

    //function zombieMouseHoverOut(zombieHoverThenOut, point) {
        //if(isAnyZombieWaitingForAnswer()) {
            //hilightZombie(zombieWaitingForAnswer);
        //} else {
            //de_hilightAllZombie();
        //}

    //}

    /**
     * 點下殭屍，就暫停所有殭屍動作，點下的殭屍進入等待答案模式。
     * 要按esc才會跳離開等待答案模式。
     */
    //function zombieClick(clickedZombie, point) {
        //if(isAnyZombieWaitingForAnswer()) {
            //return;
        //}

        //if(isDebug) {
            //console.log("zombie clicked at:" + zombieGroup.getIndex(clickedZombie));
        //}

        //zombieIsDemandingForAnswer(clickedZombie, ZOMBIE_SEC_TO_WAIT_FOR_ANSWER, true);
        //hintText = game.add.text(1, 50,
                //"Press ESC to quit answer.",
                //{ font: "20px Arial", fill: "#FFFFFF", });
    //}

    return zombie;
}   // zombieFunc
    return zombieFunc;
}   // zombie
    return {
        zombieInit: zombieInit,
        movingStyle: MovingStyle,
        ansPrefix: ZOMBIE_ANSWER_TYPING_AEAR_PREFIX
    };
}); // define
<commit_msg>- don't show timer<commit_after>/**
 *  creature object responsible for
 *
 *  1. init
 *  2. hilight
 *  3. de-light
 *  4. enter typing mode
 *  5. leave typing mode
 */
define([], function(){
var MovingStyle = {"scale":1, "static":2};

//殭屍答案打字區 prefix
var ZOMBIE_ANSWER_TYPING_AEAR_PREFIX = "Ans:　";

//boot場景，用來做一些遊戲啟動前的準備
var zombieInit = function(game){
var zombieFunc = function(zombie) {

    var ansAreaBackgroundColor =  '#000000';

    zombie.inputEnabled = true;
    zombie.tint = 0xFFFFFF;
    zombie.movingStyle = MovingStyle.scale;

    //zombie.events.onInputDown.add(zombieClick, this);
    //zombie.events.onInputOver.add(zombieMouseHover, this);
    //zombie.events.onInputOut.add(zombieMouseHoverOut, this);

    var dialogArea = game.make.text(0,
                                     -zombie.height*zombie.anchor.y + 0.3*zombie.height,
                                     "",
                                     { font: "30px Arial",
                                        fill: "#FFFFFF",
                                        wordWrap: true,
                                        align: "left",
                                        //backgroundColor: "#1C1C1C"
                                     });
    dialogArea.anchor.set(0.4, 1.0);
    dialogArea.wordWrapWidth = 200.0;

    var ansTextArea = game.make.text(0,
                                     0,
                                     "function hello() {\n    return('_____');        \n}",
                                     { font: "22px Arial",
                                       fill: "#40FF00",
                                       wordWrap: false,
                                       align: "left",
                                       backgroundColor: ansAreaBackgroundColor
                                     });
    ansTextArea.anchor.set(0.5, 0.5);

    var timeCountdownArea = game.make.text(ansTextArea.left, 50,
                                              "",
                                              { font: "15px Arial",
                                                fill: "#FFFFFF",
                                                wordWrap: false,
                                                align: "left",
                                                backgroundColor: "#1C1C1C"
                                              });
    //timeCountdownArea.anchor.set(0.5, 0.5);
    var ansTypeArea = game.make.text(timeCountdownArea.right + 22, 50,
                                     ZOMBIE_ANSWER_TYPING_AEAR_PREFIX,
                                     { font: "22px Arial",
                                       fill: "#F7FE2E",
                                       wordWrap: false,
                                       align: "left",
                                       backgroundColor: ansAreaBackgroundColor
                                     });
    //ansTypeArea.anchor.set(0.5, 0.5);
    // hide all dialogs
    ansTypeArea.alpha = 0.0;
    ansTextArea.alpha = 0.0;
    dialogArea.alpha = 0.0;
    timeCountdownArea.alpha = 0;
    zombie.addChild(ansTextArea);
    zombie.addChild(ansTypeArea)
    zombie.addChild(dialogArea)
    zombie.addChild(timeCountdownArea);
    zombie.ansTextArea = ansTextArea;
    zombie.ansTypeArea = ansTypeArea;
    zombie.dialogArea = dialogArea;
    zombie.timeCountdownArea = timeCountdownArea;

    zombie.ansTypeArea.x = zombie.ansTextArea.left;
    zombie.ansTypeArea.y = zombie.ansTextArea.bottom;

    updateTextArea(zombie);
    zombie.textArea.alpha = 0.0;
    // zombie's short quiz
    zombie.shortQuiz // ShortQuiz object

    // signal區
    zombie.onAttackSignal = new Phaser.Signal();
    zombie.onDeathSignal = new Phaser.Signal(); // signal before zombie is killed

    /**
      * reset all variables and events
      */
    zombie.refreshingUp =  function() {
      zombie.onAttackSignal.removeAll();
      zombie.onDeathSignal.removeAll();
      zombie.ansTypeArea.alpha = 0.0;
      zombie.ansTextArea.alpha = 0.0;
      zombie.textArea.alpha = 0.0;
      zombie.dialogArea.alpha = 0.0;
      zombie.timeCountdownArea.alpha = 0;
    }

    /**
      *  reset all variables and events , then kill
      */
    zombie.killThisZombie = function () {
      zombie.refreshingUp();
      zombie.onDeathSignal.dispatch(this);
      zombie.kill();
    }

    zombie.setAnsText = function (text){
        var oldText = zombie.ansTypeArea.text;
        zombie.ansTypeArea.text = text;

        if (zombie.ansTypeArea.width > zombie.ansTextArea.width){
            zombie.ansTypeArea.text = oldText;
        }
        zombie.updateAnsText();
    }

    zombie.clearAnsText = function (){
       zombie.ansTypeArea.text = ZOMBIE_ANSWER_TYPING_AEAR_PREFIX;
       zombie.updateTextArea();
    }

    zombie.updateAnsText = function () {
      zombie.ansTypeArea.x = zombie.ansTextArea.left;
      zombie.ansTypeArea.y = zombie.ansTextArea.bottom;
      zombie.updateTextArea();
    }

    /**
      *  @param shortQuiz : ShortQuiz -
      */
    zombie.setShortQuiz = function (shortQuiz) {
      zombie.shortQuiz = shortQuiz;
      zombie.ansTextArea.text = shortQuiz.quizAnswerWithBlankToFill;
      zombie.ansTypeArea.x = zombie.ansTextArea.left;
      zombie.ansTypeArea.y = zombie.ansTextArea.bottom;
      zombie.updateTextArea();
    }

    zombie.getShortQuizAnswer = function() {
      if(zombie.shortQuiz === undefined
          || zombie.shortQuiz === null) {

        return '';
      }
      return zombie.shortQuiz.answer;
    }

    zombie.getShortQuizQuestionDescription = function () {
      if(zombie.shortQuiz === undefined
          || zombie.shortQuiz === null) {

        return '';
      }
      return zombie.shortQuiz.quizDescription;
    }

    zombie.startCountdown = function(secToCount) {
      //console.log('lala');
      if(secToCount < 2) {
        secToCount = 2;
      }
      var countingSec = secToCount;
      zombie.timeCountdownArea.alpha = 0.0; // don't show timeCountDown, it's annoying
      zombie.timeCountdownArea.text = countingSec-- + '';
      var countdownLoop = game.time.events.loop(Phaser.Timer.SECOND, function() {
        zombie.timeCountdownArea.text = countingSec  + '';
        if(countingSec === 0) {
          zombie.onAttackSignal.dispatch(zombie);
          countingSec = secToCount;
        } else {
          countingSec--;
        }
      }, this);
    }

    zombie.Grrrrr = function (audioGrrrArray) {
      var foobar = game.rnd.integerInRange(0, audioGrrrArray.length - 1);
      var zg = audioGrrrArray[foobar];
      if(zg !== undefined && zg !== null) {
        // just in case
        zg.play();
      }
    }

    zombie.say = function (str) {
        if (str) {
            var dialogArea = zombie.dialogArea;
            dialogArea.text = str;
            dialogArea.x = -dialogArea.width + 10;
        }
    }

    zombie.nextDialog = function () {
        if (zombie.dialogs) {
            if (!zombie.nextDialogLine) {
                zombie.nextDialogLine = 0;
            }

            if (zombie.nextDialogLine < zombie.dialogs.length) {
                var dialogArea = zombie.dialogArea;
                dialogArea.text = zombie.dialogs[zombie.nextDialogLine];
                dialogArea.x = - dialogArea.width + 10;
                zombie.nextDialogLine += 1;

            } else {
                return -1;
            }

            return zombie.nextDialogLine;

        } else {
            return -1; // no more dialogs
        }

        return -1;
    }
    zombie.showDialog = function() {
        var zombieToHilight = zombie;

        // show answer area
        zombieToHilight.dialogArea.alpha = 1.0;
    };

    zombie.hilight = function() {
        var zombieToHilight = zombie;
        zombieToHilight.tint = 0xFE2E2E;

        // show answer area
        zombieToHilight.ansTypeArea.alpha = 1.0;
        zombieToHilight.ansTextArea.alpha = 1.0;
        zombieToHilight.textArea.alpha = 1.0;
        //zombieToHilight.dialogArea.alpha = 1.0;
    };

    zombie.lolight = function() {
        var zombieToHilight = zombie;
        zombieToHilight.tint = 0xFFFFFF;

        // show answer area
        zombieToHilight.ansTypeArea.alpha = 0.0;
        zombieToHilight.ansTextArea.alpha = 0.0;
        zombieToHilight.textArea.alpha = 0.0;
        //zombieToHilight.dialogArea.alpha = 1.0;
    };

    zombie.updateTextArea = function(){
        updateTextArea(zombie);
    }

    function updateTextArea(aZombie){
        var ansTypeArea = aZombie.ansTypeArea;
        var ansTextArea = aZombie.ansTextArea;
        var sprite = makeRectangle(ansTextArea.width, ansTypeArea.height+ansTextArea.height);

        var oldAlpha = 1.0;
        if (aZombie.textArea) {
            oldAlpha = aZombie.textArea.alpha; // preserve alpha setting
            aZombie.textArea.destroy();
        }

        if (sprite) {
            sprite.anchor.set(0.5);
            sprite.left = ansTextArea.left;
            sprite.top = ansTextArea.top;
            sprite.y += 10.0; // why?
            aZombie.addChild(sprite)
            aZombie.removeChild(ansTypeArea)
            aZombie.removeChild(ansTextArea)
            aZombie.addChild(ansTypeArea)
            aZombie.addChild(ansTextArea)
            aZombie.textArea = sprite;
            aZombie.textArea.alpha = oldAlpha;
        }

    }
    function makeRectangle(width, height){
        width += 20.0;
        height += 20.0;
        var stroke = 2.0;
        var bmd = game.make.bitmapData(width, height);

        bmd.ctx.beginPath();
        bmd.ctx.rect(0, 0, width, height);
        bmd.ctx.fillStyle = '#40FF00';
        bmd.ctx.fill();
        bmd.ctx.closePath();
        bmd.ctx.beginPath();
        bmd.ctx.rect(stroke, stroke, width-2*stroke, height-2*stroke);
        bmd.ctx.fillStyle = ansAreaBackgroundColor;
        bmd.ctx.fill();
        bmd.ctx.closePath();

        sprite = game.make.sprite(0, 0, bmd);
        return sprite;
    }

    //zombie.de_hilightAllZombie = function() {
        //zombie.tint = 0xFFFFFF;
    //};

    /**
     * isAbleToEsc : bool - 是否能逃離（按esc逃離，如果是殭屍遇上player就不能逃）
     */
    //function zombieIsDemandingForAnswer(zombieNeedAnswer, secToWait, isAbleToEsc) {
        //zombieNeedAnswer.zIndexBeforeClicked = zombieNeedAnswer.z;
        //zombieGroup.bringToTop(zombieNeedAnswer);
        //if(isDebug) {
            //console.log("zombie was on " + zombieNeedAnswer.zIndexBeforeClicked + ", and moved to front at " + zombieNeedAnswer.z);
        //}
        //zombieWaitingForAnswer = zombieNeedAnswer;
        //zombieWaitingForAnswer.isAbleToEsc = isAbleToEsc;
        //hilightZombie(zombieWaitingForAnswer);
        //pauseAllAliveZombies();
        //typingMode(true);
    //}

    /*
    function zombieMouseHover(zombieHoverOn, point) {
        if(isAnyZombieWaitingForAnswer() === false) {
            hilightZombie(zombieHoverOn);
        }
    }
    */

    //function zombieMouseHoverOut(zombieHoverThenOut, point) {
        //if(isAnyZombieWaitingForAnswer()) {
            //hilightZombie(zombieWaitingForAnswer);
        //} else {
            //de_hilightAllZombie();
        //}

    //}

    /**
     * 點下殭屍，就暫停所有殭屍動作，點下的殭屍進入等待答案模式。
     * 要按esc才會跳離開等待答案模式。
     */
    //function zombieClick(clickedZombie, point) {
        //if(isAnyZombieWaitingForAnswer()) {
            //return;
        //}

        //if(isDebug) {
            //console.log("zombie clicked at:" + zombieGroup.getIndex(clickedZombie));
        //}

        //zombieIsDemandingForAnswer(clickedZombie, ZOMBIE_SEC_TO_WAIT_FOR_ANSWER, true);
        //hintText = game.add.text(1, 50,
                //"Press ESC to quit answer.",
                //{ font: "20px Arial", fill: "#FFFFFF", });
    //}

    return zombie;
}   // zombieFunc
    return zombieFunc;
}   // zombie
    return {
        zombieInit: zombieInit,
        movingStyle: MovingStyle,
        ansPrefix: ZOMBIE_ANSWER_TYPING_AEAR_PREFIX
    };
}); // define
<|endoftext|>


<commit_before>var Resources = require('../conf.js');
var Mongoose = require('mongoose');
var Tunnel = require('tunnel-ssh');
var Conf = require('../conf.js');
var TvMaze = require('../libs/tvMaze.js');
var Common = require('../common.js');
var ScheduleManager = require('../schedule/scheduleManager.js');

var db = undefined;
var Schema = Mongoose.Schema;

var Alert = Mongoose.model('Alert', new Schema({
    ids: String,
    show_name: String,
    showId: Number,
    language: Schema.Types.Mixed,
    nextepisode_airdate: String,
    nextepisode_season: Number,
    nextepisode_episode: Number
}, {
        _id: false
    }));
var User = Mongoose.model('User', new Schema({
    ids: String,
    chatId: Number,
    first_name: String,
    alerts: Array
}));
var Language = Mongoose.model('Language', new Schema({
    code: String,
    int: String,
    native: String
}, {
        _id: false
    }));

exports.Alert = Alert;
exports.User = User;
exports.Language = Language;

exports.connectToDatabase = function () {
    var server = Tunnel(Conf.mongoConfig, function (error, server) {
        if (error) {
            console.log("SSH connection error: " + error);
        }
        // if(Conf.mongoHost == "raspi") Mongoose.connect('mongodb://127.0.0.1:');
        // else
        Mongoose.connect('mongodb://127.0.0.1:' + Conf.mongoConfig.localPort + '/' + Conf.dbName);
        db = Mongoose.connection;
        db.on('error', () => {
            console.log('DB connection error ')
        });
        db.once('open', function () {
            console.log("DB connection successful");
        });
    });
}

exports.subscribe = function (session, bot, from) {
    var alertsToStore = [];
    var alertsIdList = [];
    if (session.choosenSeriesAlert.show._links.nextepisode) {
        var nextepisodePromise = TvMaze.getNextEpisodeInformation(session.choosenSeriesAlert.show._links.nextepisode.href);

        nextepisodePromise.then(function (nextepisode) {
            session.chosenLanguagesAlert.forEach(function (languageElement, index) {
                var alertToStore = new Alert({
                    show_name: session.choosenSeriesAlert.show.name,
                    showId: session.choosenSeriesAlert.show.id,
                    language: languageElement,
                    nextepisode_airdate: nextepisode.airdate,
                    nextepisode_season: nextepisode.season,
                    nextepisode_episode: nextepisode.number
                    // FOR DEBUG:
                    // nextepisode_airdate: "today",
                    // nextepisode_season: "1",
                    // nextepisode_episode: "1"
                });
                if (alertToStore._id === undefined) {
                    delete alertToStore._id;
                }
                Alert.findOneAndUpdate({ showId: alertToStore.showId, language: languageElement },
                    alertToStore, { new: true, upsert: true },
                    function (err, storedAlert) {
                        if (err) console.log("ERROR IN SAVE MONGO", err);
                        else {
                            ScheduleManager.activateStoredSchedules(storedAlert._doc, bot);
                            alertsIdList.push(storedAlert._doc._id.toString());

                            if (index == session.chosenLanguagesAlert.length - 1) {
                                subscribeUser(alertsIdList, session, bot, from);
                            }
                        }
                    }
                );
            });
        });
    } else {
        bot.sendMessage(from.id, nextEpisodeNotAvailableMessage);
        Common.resetValues(session);
    }
}

function subscribeUser(alertsList, session, bot, from) {
    var alertsToAdd = [];
    User.findOne({ chatId: from.id }, function (err, user) {
        if (!user) {
            var newUser = new User({
                chatId: from.id,
                first_name: from.first_name,
                alerts: alertsList
            });
            User.create(newUser, function (err, value) {
                if (err) console.log("error saving new user");
            });
        } else {
            user._doc.alerts.addToSet(alertsList);
            user.save(function () {
                bot.sendMessage(from.id, Common.successSubscribeMessage(session.choosenSeriesAlert.show.name));
                Common.resetValues(session);
            });

        }
    });
}<commit_msg>re-added connectionString if else<commit_after>var Resources = require('../conf.js');
var Mongoose = require('mongoose');
var Tunnel = require('tunnel-ssh');
var Conf = require('../conf.js');
var TvMaze = require('../libs/tvMaze.js');
var Common = require('../common.js');
var ScheduleManager = require('../schedule/scheduleManager.js');

var db = undefined;
var Schema = Mongoose.Schema;

var Alert = Mongoose.model('Alert', new Schema({
    ids: String,
    show_name: String,
    showId: Number,
    language: Schema.Types.Mixed,
    nextepisode_airdate: String,
    nextepisode_season: Number,
    nextepisode_episode: Number
}, {
        _id: false
    }));
var User = Mongoose.model('User', new Schema({
    ids: String,
    chatId: Number,
    first_name: String,
    alerts: Array
}));
var Language = Mongoose.model('Language', new Schema({
    code: String,
    int: String,
    native: String
}, {
        _id: false
    }));

exports.Alert = Alert;
exports.User = User;
exports.Language = Language;

exports.connectToDatabase = function () {
    var server = Tunnel(Conf.mongoConfig, function (error, server) {
        if (error) {
            console.log("SSH connection error: " + error);
        }
        if(Conf.mongoHost == "raspi") Mongoose.connect('mongodb://127.0.0.1:');
        else Mongoose.connect('mongodb://127.0.0.1:' + Conf.mongoConfig.localPort + '/' + Conf.dbName);
        db = Mongoose.connection;
        db.on('error', () => {
            console.log('DB connection error ')
        });
        db.once('open', function () {
            console.log("DB connection successful");
        });
    });
}

exports.subscribe = function (session, bot, from) {
    var alertsToStore = [];
    var alertsIdList = [];
    if (session.choosenSeriesAlert.show._links.nextepisode) {
        var nextepisodePromise = TvMaze.getNextEpisodeInformation(session.choosenSeriesAlert.show._links.nextepisode.href);

        nextepisodePromise.then(function (nextepisode) {
            session.chosenLanguagesAlert.forEach(function (languageElement, index) {
                var alertToStore = new Alert({
                    show_name: session.choosenSeriesAlert.show.name,
                    showId: session.choosenSeriesAlert.show.id,
                    language: languageElement,
                    nextepisode_airdate: nextepisode.airdate,
                    nextepisode_season: nextepisode.season,
                    nextepisode_episode: nextepisode.number
                    // FOR DEBUG:
                    // nextepisode_airdate: "today",
                    // nextepisode_season: "1",
                    // nextepisode_episode: "1"
                });
                if (alertToStore._id === undefined) {
                    delete alertToStore._id;
                }
                Alert.findOneAndUpdate({ showId: alertToStore.showId, language: languageElement },
                    alertToStore, { new: true, upsert: true },
                    function (err, storedAlert) {
                        if (err) console.log("ERROR IN SAVE MONGO", err);
                        else {
                            ScheduleManager.activateStoredSchedules(storedAlert._doc, bot);
                            alertsIdList.push(storedAlert._doc._id.toString());

                            if (index == session.chosenLanguagesAlert.length - 1) {
                                subscribeUser(alertsIdList, session, bot, from);
                            }
                        }
                    }
                );
            });
        });
    } else {
        bot.sendMessage(from.id, nextEpisodeNotAvailableMessage);
        Common.resetValues(session);
    }
}

function subscribeUser(alertsList, session, bot, from) {
    var alertsToAdd = [];
    User.findOne({ chatId: from.id }, function (err, user) {
        if (!user) {
            var newUser = new User({
                chatId: from.id,
                first_name: from.first_name,
                alerts: alertsList
            });
            User.create(newUser, function (err, value) {
                if (err) console.log("error saving new user");
            });
        } else {
            user._doc.alerts.addToSet(alertsList);
            user.save(function () {
                bot.sendMessage(from.id, Common.successSubscribeMessage(session.choosenSeriesAlert.show.name));
                Common.resetValues(session);
            });

        }
    });
}<|endoftext|>


<commit_before><commit_msg>fix(dmscreen): correctly restore large panels from save data<commit_after><|endoftext|>


<commit_before>(function ($) {

  "use strict";

  /****
   ****
   ****  Tools
   ****
   ****/

  function clearFormFields($element)
  {
    $element.find('input:text, input:password, input:file, select').val('');
    $element.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
  }

  /****
   ****
   ****  Dropdown menus
   ****
   ****/

  $(function ()
    {
      // Stop propagation of dropdown menus so they don't get closed
      $('#user-menu .top-dropdown-container').click(
        function (e)
          {
            e.stopPropagation();
          });

      // TODO: focus() doesn't work
      $('#user-menu').on('click.dropdown.data-api', function(e)
        {
          var $menu = $(e.target).parent();
          if (!$menu.hasClass('open'))
          {
            $menu.find('#email').focus();
          }
        });
    });

  /****
   ****
   ****  Google Maps
   ****
   ****/

  $(function ()
    {
      var $container = $('.simple-map');

      if (!$container.length)
      {
        return;
      }

      window.initializeSimpleMap = function()
        {
          var location = new google.maps.LatLng($container.data('latitude'), $container.data('longitude'));
          var map = new google.maps.Map($container.get(0), {
              zoom: 16,
              center: location,
              panControl: false,
              mapTypeControl: true,
              zoomControl: true,
              scaleControl: false,
              streetViewControl: false,
              mapTypeControlOptions: { style: google.maps.MapTypeControlStyle.DROPDOWN_MENU },
              mapTypeId: google.maps.MapTypeId.ROADMAP,
              zoomControlOptions: { style: google.maps.ZoomControlStyle.SMALL },
            });
          var marker = new google.maps.Marker({ position: location, map: map});
        };

      $.getScript('https://maps.google.com/maps/api/js?sensor=false&callback=initializeSimpleMap&key=' + $container.data('key'));
    });

  /****
   ****
   ****  jQuery Masonry
   ****
   ****/

  $(document).ready(function()
    {
      var $container = $('.masonry');
      $container.imagesLoaded(function() {
        $container.masonry({
          itemSelector: '.brick',
          isAnimated: false,
          gutterWidth: 15,
          isFitWidth: $container.hasClass('centered')
        });
      });
    });

  /****
   ****
   ****  Autocomplete plugin
   ****
   ****/

  var Autocomplete = function (element)
    {
      this.$element = element;
      this.$realm = this.$element.parent().find('#search-realm');
      this.$form = this.$element.parent('form');
      this.$menu = $('

').appendTo(this.$form);

      this.source = this.$element.closest('form').data('autocomplete');
      this.shown = false;
      this.timeout = 150;

      this.listen();
      this.showRealm();
    };

  Autocomplete.prototype = {

    constructor: Autocomplete,

    listen: function()
      {
        $(window)
          .on('resize', $.proxy(this.resize, this));

        this.$element
          .on('focus', $.proxy(this.focus, this))
          .on('blur', $.proxy(this.blur, this))
          .on('keypress', $.proxy(this.keypress, this))
          .on('keyup', $.proxy(this.keyup, this));

        if ($.browser.webkit || $.browser.msie)
        {
          this.$element.on('keydown', $.proxy(this.keypress, this));
        }

        this.$menu.on('mouseenter', 'li', $.proxy(this.mouseenter, this));
      },

    resize: function()
      {
        this.hide();
        this.hideRealm();
      },

    show: function()
      {
        this.hideRealm();
        this.$menu.show();
        this.shown = true;

        return this;
      },

    showRealm: function (e)
      {
        this.hide();
        this.$realm.show();

        return this;
      },

    hide: function()
      {
        this.$menu.hide();
        this.shown = false;

        return this;
      },

    hideRealm: function (e)
      {
        this.$realm.hide();
      },

    lookup: function (e)
      {
        var query = this.$element.val();

        if (!query)
        {
          this.hide();
          this.showRealm();

          return this;
        }

        $.ajax(this.source,
          {
            context: this,
            data: { query: query },
            dataType: 'html'
          })
          .done(function(html)
            {
              if (html)
              {
                this.render(html).show();
              }
              else
              {
                this.hide();
              }
            });
      },

    render: function (html)
      {
        this.$menu.html(html);

        return this;
      },

    next: function (e) { },
    prev: function (e) { },
    select: function (e) { },

    keyup: function (e)
      {
        switch (e.keyCode)
        {
          case 40: // Down arrow
          case 38: // Up arrow
            break;

          case 9: // Tab
            if (!this.shown)
            {
              return;
            }
            this.select();
            break;

          case 27: // Escape
            this.$element.val('');
            this.hide();
            this.hideRealm();
            break;

          default:
            if (this.timer)
            {
              clearTimeout(this.timer);
            }
            var self = this;
            this.timer = setTimeout(function()
              {
                self.lookup();
              }, this.timeout);
        }

        e.stopPropagation();
        e.preventDefault();
      },

    keypress: function (e)
      {
        if (13 == e.keyCode && !e.target.value.length)
        {
          e.preventDefault();
          e.stopPropagation();
        }

        if (!this.shown)
        {
          return;
        }

        switch (e.keyCode)
        {
          case 9: // Tab
          case 27: // Escape
            e.preventDefault();
            break;

          case 38: // Up arrow
            e.preventDefault();
            this.prev();
            break;

          case 40: // Down arrow
            e.preventDefault();
            this.next();
            break;
        }

        e.stopPropagation();
      },

    blur: function (e)
      {
        var self = this;
        setTimeout(function ()
          {
            self.hide();
            self.$realm.hide();
            self.$element.val('');
          }, 150);

        this.$form.removeClass('active');
      },

    focus: function (e)
      {
        this.$element.val('');
        this.showRealm();

        this.$form.addClass('active');

        return this;
      },

    mouseenter: function (e)
      {
        this.$menu.find('active').removeClass('active');
        $(e.currentTarget).addClass('active');
      }
  };

  $.fn.autocomplete = function()
    {
      var $this = this;
      var data = $this.data('autocomplete');
      if (!data)
      {
        $this.data('autocomplete', new Autocomplete(this));
      }
    };

  $.fn.autocomplete.Constructor = Autocomplete;

  $(function ()
    {
      $('body').on('focus.qubit', '#search-form-wrapper input[name="query"]', function(e)
        {
          var $this = $(this);

          if ($this.data('autocomplete'))
          {
            return;
          }

          e.preventDefault();
          $this.autocomplete();
        });
    });

  /****
   ****
   ****  Advanced search
   ****
   ****/

  var AdvancedSearch = function (element)
    {
      this.$element = $(element);

      this.listen();
    };

  AdvancedSearch.prototype = {

    constructor: AdvancedSearch,

    listen: function()
    {
      this.$criteria = this.$element.find('.criteria');
      this.$filters = this.$element.find('#filters');

      // Hide first boolean
      this.$criteria.first().find('.boolean').hide();

      // Hide last criteria if more than once
      if (1 < this.$criteria.length)
      {
        this.$criteria.last().hide();
      }

      // Hide filters if not being used
      if (!this.$element.find('#toggle-filters').hasClass('active'))
      {
        this.$filters.hide();
      }

      // Bind events
      this.$element.on('click', $.proxy(this.click, this));
    },

    click: function (event)
    {
      var $target = $(event.target);
      var id = $target.attr('id');

      switch (id)
      {
        case 'add-criteria-and':
        case 'add-criteria-or':
        case 'add-criteria-not':
          event.preventDefault();
          this.addCriteria(id.replace('add-criteria-', ''));
          break;

        case 'toggle-filters':
          event.preventDefault();
          this.$filters.slideToggle('fast');
          $target.toggleClass('active');
          if (!$target.hasClass('active'))
          {
            clearFormFields(this.$filters);
          }
          break;
      }
    },

    addCriteria: function (option)
    {
      this
        .cloneLastCriteria()
        .insertAfter(this.getLastCriteria())
        .show()
        .find('.boolean select').val(option).end()
        .find('.criterion input').first().focus();
    },

    getLastCriteria: function()
    {
      return this.$element.find('.criteria:last');
    },

    cloneLastCriteria: function()
    {
      var $clone = this.getLastCriteria().clone();

      var nextNumber = parseInt($clone.find('input:first').attr('name').match(/\d+/).shift()) + 1;

      $clone
        .find('input, select').each(function()
          {
            var $this = $(this);
            $this.attr('name', $this.attr('name').replace(/\[\d+\]/, '[' + nextNumber +']'));
          }).end()
        .find('.boolean').show();

      return this.resetFormFields($clone);
    },

    resetFormFields: function($sender)
    {
      return $sender
        .find('input:text, input:password, input:file, select')
          .val('')
          .end()
        .find('input:radio, input:checkbox')
          .removeAttr('checked').removeAttr('selected')
          .end();
    }
  };

  $(function ()
    {
      var $advancedSearch = $('body.search.advanced');
      if (0 < $advancedSearch.length)
      {
        new AdvancedSearch($advancedSearch.get(0));
      }

      var $body = $('body.search');
      if ($body.hasClass('index') || $body.hasClass('advanced'))
      {
        $body.find('[name^=creationDate]').on('keyup', function (e)
          {
            // Enter
            if (e.keyCode == 13)
            {
              e.preventDefault();
              $(e.target).closest('form').submit();
            }
          });
      }
    });

})(window.jQuery);
<commit_msg>Tweaks in the search autocomplete<commit_after>(function ($) {

  "use strict";

  /****
   ****
   ****  Tools
   ****
   ****/

  function clearFormFields($element)
  {
    $element.find('input:text, input:password, input:file, select').val('');
    $element.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
  }

  /****
   ****
   ****  Dropdown menus
   ****
   ****/

  $(function ()
    {
      // Stop propagation of dropdown menus so they don't get closed
      $('#user-menu .top-dropdown-container').click(
        function (e)
          {
            e.stopPropagation();
          });

      // TODO: focus() doesn't work
      $('#user-menu').on('click.dropdown.data-api', function(e)
        {
          var $menu = $(e.target).parent();
          if (!$menu.hasClass('open'))
          {
            $menu.find('#email').focus();
          }
        });
    });

  /****
   ****
   ****  Google Maps
   ****
   ****/

  $(function ()
    {
      var $container = $('.simple-map');

      if (!$container.length)
      {
        return;
      }

      window.initializeSimpleMap = function()
        {
          var location = new google.maps.LatLng($container.data('latitude'), $container.data('longitude'));
          var map = new google.maps.Map($container.get(0), {
              zoom: 16,
              center: location,
              panControl: false,
              mapTypeControl: true,
              zoomControl: true,
              scaleControl: false,
              streetViewControl: false,
              mapTypeControlOptions: { style: google.maps.MapTypeControlStyle.DROPDOWN_MENU },
              mapTypeId: google.maps.MapTypeId.ROADMAP,
              zoomControlOptions: { style: google.maps.ZoomControlStyle.SMALL },
            });
          var marker = new google.maps.Marker({ position: location, map: map});
        };

      $.getScript('https://maps.google.com/maps/api/js?sensor=false&callback=initializeSimpleMap&key=' + $container.data('key'));
    });

  /****
   ****
   ****  jQuery Masonry
   ****
   ****/

  $(document).ready(function()
    {
      var $container = $('.masonry');
      $container.imagesLoaded(function() {
        $container.masonry({
          itemSelector: '.brick',
          isAnimated: false,
          gutterWidth: 15,
          isFitWidth: $container.hasClass('centered')
        });
      });
    });

  /****
   ****
   ****  Autocomplete plugin
   ****
   ****/

  var Autocomplete = function (element)
    {
      this.$element = element;
      this.$realm = this.$element.parents('#search-form-wrapper').find('#search-realm');
      this.$form = this.$element.parents('form');
      this.$menu = $('

').appendTo(this.$form);

      this.source = this.$element.closest('form').data('autocomplete');
      this.shown = false;
      this.timeout = 150;

      this.listen();
      this.showRealm();
    };

  Autocomplete.prototype = {

    constructor: Autocomplete,

    listen: function()
      {
        $(window)
          .on('resize', $.proxy(this.resize, this));

        this.$element
          .on('focus', $.proxy(this.focus, this))
          .on('blur', $.proxy(this.blur, this))
          .on('keypress', $.proxy(this.keypress, this))
          .on('keyup', $.proxy(this.keyup, this));

        if ($.browser.webkit || $.browser.msie)
        {
          this.$element.on('keydown', $.proxy(this.keypress, this));
        }

        this.$menu.on('mouseenter', 'li', $.proxy(this.mouseenter, this));
      },

    resize: function()
      {
        this.hide();
        this.hideRealm();
      },

    show: function()
      {
        this.hideRealm();
        this.$menu.show();

        this.shown = true;

        return this;
      },

    hide: function()
      {
        this.$menu.hide();
        this.shown = false;

        return this;
      },

    showRealm: function (e)
      {
        this.hide();
        this.$realm.css('display', 'block');

        // Remove radius when the realm is shown
        this.$element.css('border-bottom-left-radius', 0);

        if (undefined === this.$realm.positioned)
        {
          var position = this.$element.offset();
          this.$realm.css('left', position.left);
          this.$realm.width(this.$element.innerWidth());

          this.$realm.positioned = true;
        }

        return this;
      },

    hideRealm: function (e)
      {
        this.$realm.css('display', 'none');

        // Use radius again
        this.$element.css('border-bottom-left-radius', '4px');

        return this;
      },

    lookup: function (e)
      {
        var query = this.$element.val();

        if (!query)
        {
          this.hide();
          this.showRealm();

          return this;
        }

        $.ajax(this.source,
          {
            context: this,
            data: { query: query },
            dataType: 'html'
          })
          .done(function(html)
            {
              if (html)
              {
                this.render(html).show();
              }
              else
              {
                this.hide();
              }
            });
      },

    render: function (html)
      {
        this.$menu.html(html);

        if (undefined === this.$menu.positioned)
        {
          var position = this.$element.offset();
          this.$menu.css('left', position.left);
          this.$menu.width(this.$element.innerWidth());

          this.$menu.positioned = true;
        }

        return this;
      },

    next: function (e) { },
    prev: function (e) { },
    select: function (e) { },

    keyup: function (e)
      {
        switch (e.keyCode)
        {
          case 40: // Down arrow
          case 38: // Up arrow
            break;

          case 9: // Tab
            if (!this.shown)
            {
              return;
            }
            this.select();
            break;

          case 27: // Escape
            this.$element.val('');
            this.hide();
            this.hideRealm();
            break;

          default:
            if (this.timer)
            {
              clearTimeout(this.timer);
            }
            var self = this;
            this.timer = setTimeout(function()
              {
                self.lookup();
              }, this.timeout);
        }

        e.stopPropagation();
        e.preventDefault();
      },

    keypress: function (e)
      {
        if (13 == e.keyCode && !e.target.value.length)
        {
          e.preventDefault();
          e.stopPropagation();

          return;
        }

        // if (!this.shown) return;

        switch (e.keyCode)
        {
          case 9: // Tab
          case 27: // Escape
            e.preventDefault();
            break;

          case 13:
            e.preventDefault();
            $(e.target).closest('form').get(0).submit();
            break;

          case 38: // Up arrow
            e.preventDefault();
            this.prev();
            break;

          case 40: // Down arrow
            e.preventDefault();
            this.next();
            break;
        }

        e.stopPropagation();
      },

    blur: function (e)
      {
        var self = this;
        setTimeout(function ()
          {
            self.hide();
            self.hideRealm();
            self.$element.val('');
          }, 150);

        this.$form.removeClass('active');
      },

    focus: function (e)
      {
        this.$element.val('');
        this.showRealm();

        this.$form.addClass('active');

        return this;
      },

    mouseenter: function (e)
      {
        this.$menu.find('active').removeClass('active');
        $(e.currentTarget).addClass('active');
      }
  };

  $.fn.autocomplete = function()
    {
      var $this = this;
      var data = $this.data('autocomplete');
      if (!data)
      {
        $this.data('autocomplete', new Autocomplete(this));
      }
    };

  $.fn.autocomplete.Constructor = Autocomplete;

  $(function ()
    {
      $('body').on('focus.qubit', '#search-form-wrapper input[name="query"]', function(e)
        {
          var $this = $(this);

          if ($this.data('autocomplete'))
          {
            return;
          }

          e.preventDefault();
          $this.autocomplete();
        });
    });

  /****
   ****
   ****  Advanced search
   ****
   ****/

  var AdvancedSearch = function (element)
    {
      this.$element = $(element);

      this.listen();
    };

  AdvancedSearch.prototype = {

    constructor: AdvancedSearch,

    listen: function()
    {
      this.$criteria = this.$element.find('.criteria');
      this.$filters = this.$element.find('#filters');

      // Hide first boolean
      this.$criteria.first().find('.boolean').hide();

      // Hide last criteria if more than once
      if (1 < this.$criteria.length)
      {
        this.$criteria.last().hide();
      }

      // Hide filters if not being used
      if (!this.$element.find('#toggle-filters').hasClass('active'))
      {
        this.$filters.hide();
      }

      // Bind events
      this.$element.on('click', $.proxy(this.click, this));
    },

    click: function (event)
    {
      var $target = $(event.target);
      var id = $target.attr('id');

      switch (id)
      {
        case 'add-criteria-and':
        case 'add-criteria-or':
        case 'add-criteria-not':
          event.preventDefault();
          this.addCriteria(id.replace('add-criteria-', ''));
          break;

        case 'toggle-filters':
          event.preventDefault();
          this.$filters.slideToggle('fast');
          $target.toggleClass('active');
          if (!$target.hasClass('active'))
          {
            clearFormFields(this.$filters);
          }
          break;
      }
    },

    addCriteria: function (option)
    {
      this
        .cloneLastCriteria()
        .insertAfter(this.getLastCriteria())
        .show()
        .find('.boolean select').val(option).end()
        .find('.criterion input').first().focus();
    },

    getLastCriteria: function()
    {
      return this.$element.find('.criteria:last');
    },

    cloneLastCriteria: function()
    {
      var $clone = this.getLastCriteria().clone();

      var nextNumber = parseInt($clone.find('input:first').attr('name').match(/\d+/).shift()) + 1;

      $clone
        .find('input, select').each(function()
          {
            var $this = $(this);
            $this.attr('name', $this.attr('name').replace(/\[\d+\]/, '[' + nextNumber +']'));
          }).end()
        .find('.boolean').show();

      return this.resetFormFields($clone);
    },

    resetFormFields: function($sender)
    {
      return $sender
        .find('input:text, input:password, input:file, select')
          .val('')
          .end()
        .find('input:radio, input:checkbox')
          .removeAttr('checked').removeAttr('selected')
          .end();
    }
  };

  $(function ()
    {
      var $advancedSearch = $('body.search.advanced');
      if (0 < $advancedSearch.length)
      {
        new AdvancedSearch($advancedSearch.get(0));
      }

      var $body = $('body.search');
      if ($body.hasClass('index') || $body.hasClass('advanced'))
      {
        $body.find('[name^=creationDate]').on('keyup', function (e)
          {
            // Enter
            if (e.keyCode == 13)
            {
              e.preventDefault();
              $(e.target).closest('form').submit();
            }
          });
      }
    });

})(window.jQuery);
<|endoftext|>