<commit_before>'use strict';

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

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

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

module.exports = class exx extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'exx',
            'name': 'EXX',
            'countries': [ 'CN' ],
            'rateLimit': 1000 / 10,
            'userAgent': this.userAgents['chrome'],
            'has': {
                'cancelOrder': true,
                'createOrder': true,
                'fetchBalance': true,
                'fetchMarkets': true,
                'fetchOpenOrders': true,
                'fetchOrder': true,
                'fetchOrderBook': true,
                'fetchTicker': true,
                'fetchTickers': true,
                'fetchTrades': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/37770292-fbf613d0-2de4-11e8-9f79-f2dc451b8ccb.jpg',
                'api': {
                    'public': 'https://api.exx.com/data/v1',
                    'private': 'https://trade.exx.com/api',
                },
                'www': 'https://www.exx.com/',
                'doc': 'https://www.exx.com/help/restApi',
                'fees': 'https://www.exx.com/help/rate',
                'referral': 'https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1',
            },
            'api': {
                'public': {
                    'get': [
                        'markets',
                        'tickers',
                        'ticker',
                        'depth',
                        'trades',
                    ],
                },
                'private': {
                    'get': [
                        'order',
                        'cancel',
                        'getOrder',
                        'getOpenOrders',
                        'getBalance',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': this.parseNumber ('0.001'),
                    'taker': this.parseNumber ('0.001'),
                },
                'funding': {
                    'withdraw': {},
                },
            },
            'commonCurrencies': {
                'DOS': 'DEMOS',
                'TV': 'TIV', // Ti-Value
            },
            'exceptions': {
                '103': AuthenticationError,
            },
        });
    }

    async fetchMarkets (params = {}) {
        const response = await this.publicGetMarkets (params);
        const ids = Object.keys (response);
        const result = [];
        for (let i = 0; i < ids.length; i++) {
            const id = ids[i];
            const market = response[id];
            const [ baseId, quoteId ] = id.split ('_');
            const base = this.safeCurrencyCode (baseId);
            const quote = this.safeCurrencyCode (quoteId);
            const symbol = base + '/' + quote;
            const active = market['isOpen'] === true;
            const amountPrecisionString = this.safeString (market, 'amountScale');
            const pricePrecisionString = this.safeString (market, 'priceScale');
            const amountLimit = this.parsePrecision (amountPrecisionString);
            const priceLimit = this.parsePrecision (pricePrecisionString);
            const precision = {
                'amount': parseInt (amountPrecisionString),
                'price': parseInt (pricePrecisionString),
            };
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': this.parseNumber (amountLimit),
                        'max': undefined,
                    },
                    'price': {
                        'min': this.parseNumber (priceLimit),
                        'max': undefined,
                    },
                    'cost': {
                        'min': this.safeNumber (market, 'minAmount'),
                        'max': undefined,
                    },
                },
                'info': market,
            });
        }
        return result;
    }

    parseTicker (ticker, market = undefined) {
        const symbol = market['symbol'];
        const timestamp = this.safeInteger (ticker, 'date');
        ticker = ticker['ticker'];
        const last = this.safeNumber (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeNumber (ticker, 'high'),
            'low': this.safeNumber (ticker, 'low'),
            'bid': this.safeNumber (ticker, 'buy'),
            'bidVolume': undefined,
            'ask': this.safeNumber (ticker, 'sell'),
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': this.safeNumber (ticker, 'riseRate'),
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeNumber (ticker, 'vol'),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        return this.parseTicker (response, market);
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        const response = await this.publicGetTickers (params);
        const result = {};
        const timestamp = this.milliseconds ();
        const ids = Object.keys (response);
        for (let i = 0; i < ids.length; i++) {
            const id = ids[i];
            if (!(id in this.markets_by_id)) {
                continue;
            }
            const market = this.markets_by_id[id];
            const symbol = market['symbol'];
            const ticker = {
                'date': timestamp,
                'ticker': response[id],
            };
            result[symbol] = this.parseTicker (ticker, market);
        }
        return this.filterByArray (result, 'symbol', symbols);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {
            'currency': this.marketId (symbol),
        };
        const response = await this.publicGetDepth (this.extend (request, params));
        const timestamp = this.safeTimestamp (response, 'timestamp');
        return this.parseOrderBook (response, symbol, timestamp);
    }

    parseTrade (trade, market = undefined) {
        const timestamp = this.safeTimestamp (trade, 'date');
        const priceString = this.safeString (trade, 'price');
        const amountString = this.safeString (trade, 'amount');
        const price = this.parseNumber (priceString);
        const amount = this.parseNumber (amountString);
        const cost = this.parseNumber (Precise.stringMul (priceString, amountString));
        let symbol = undefined;
        if (market !== undefined) {
            symbol = market['symbol'];
        }
        const type = 'limit';
        const side = this.safeString (trade, 'type');
        const id = this.safeString (trade, 'tid');
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': undefined,
            'type': type,
            'side': side,
            'takerOrMaker': undefined,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
        };
        const response = await this.publicGetTrades (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        const response = await this.privateGetGetBalance (params);
        const result = { 'info': response };
        const balances = this.safeValue (response, 'funds');
        const currencies = Object.keys (balances);
        for (let i = 0; i < currencies.length; i++) {
            const currencyId = currencies[i];
            const balance = balances[currencyId];
            const code = this.safeCurrencyCode (currencyId);
            const account = this.account ();
            account['free'] = this.safeString (balance, 'balance');
            account['used'] = this.safeString (balance, 'freeze');
            account['total'] = this.safeString (balance, 'total');
            result[code] = account;
        }
        return this.parseBalance (result);
    }

    parseOrder (order, market = undefined) {
        //
        //     {
        //         "fees": 0,
        //         "total_amount": 1,
        //         "trade_amount": 0,
        //         "price": 30,
        //         "currency": “eth_hsr",
        //         "id": "13878",
        //         "trade_money": 0,
        //         "type": "buy",
        //         "trade_date": 1509728897772,
        //         "status": 0
        //     }
        //
        const symbol = market['symbol'];
        const timestamp = parseInt (order['trade_date']);
        const price = this.safeNumber (order, 'price');
        const cost = this.safeNumber (order, 'trade_money');
        const amount = this.safeNumber (order, 'total_amount');
        const filled = this.safeNumber (order, 'trade_amount', 0.0);
        let status = this.safeInteger (order, 'status');
        if (status === 1) {
            status = 'canceled';
        } else if (status === 2) {
            status = 'closed';
        } else {
            status = 'open';
        }
        let fee = undefined;
        if ('fees' in order) {
            fee = {
                'cost': this.safeNumber (order, 'fees'),
                'currency': market['quote'],
            };
        }
        return this.safeOrder ({
            'id': this.safeString (order, 'id'),
            'clientOrderId': undefined,
            'datetime': this.iso8601 (timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': undefined,
            'status': status,
            'symbol': symbol,
            'type': 'limit',
            'timeInForce': undefined,
            'postOnly': undefined,
            'side': order['type'],
            'price': price,
            'stopPrice': undefined,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': undefined,
            'trades': undefined,
            'fee': fee,
            'info': order,
            'average': undefined,
        });
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
            'type': side,
            'price': price,
            'amount': amount,
        };
        const response = await this.privateGetOrder (this.extend (request, params));
        const id = this.safeString (response, 'id');
        const order = this.parseOrder ({
            'id': id,
            'trade_date': this.milliseconds (),
            'total_amount': amount,
            'price': price,
            'type': side,
            'info': response,
        }, market);
        return order;
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'id': id,
            'currency': market['id'],
        };
        const response = await this.privateGetCancel (this.extend (request, params));
        return response;
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'id': id,
            'currency': market['id'],
        };
        const response = await this.privateGetGetOrder (this.extend (request, params));
        return this.parseOrder (response, market);
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument');
        }
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
        };
        const response = await this.privateGetGetOpenOrders (this.extend (request, params));
        if (!Array.isArray (response)) {
            return [];
        }
        return this.parseOrders (response, market, since, limit);
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'][api] + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length) {
                url += '?' + this.urlencode (params);
            }
        } else {
            this.checkRequiredCredentials ();
            const query = this.urlencode (this.keysort (this.extend ({
                'accesskey': this.apiKey,
                'nonce': this.nonce (),
            }, params)));
            const signed = this.hmac (this.encode (query), this.encode (this.secret), 'sha512');
            url += '?' + query + '&signature=' + signed;
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
        if (response === undefined) {
            return; // fallback to default error handler
        }
        //
        //  {"result":false,"message":"服务端忙碌"}
        //  ... and other formats
        //
        const code = this.safeString (response, 'code');
        const message = this.safeString (response, 'message');
        const feedback = this.id + ' ' + body;
        if (code === '100') {
            return;
        }
        if (code !== undefined) {
            this.throwExactlyMatchedException (this.exceptions, code, feedback);
            if (code === '308') {
                // this is returned by the exchange when there are no open orders
                // {"code":308,"message":"Not Found Transaction Record"}
                return;
            } else {
                throw new ExchangeError (feedback);
            }
        }
        const result = this.safeValue (response, 'result');
        if (result !== undefined) {
            if (!result) {
                if (message === '服务端忙碌') {
                    throw new ExchangeNotAvailable (feedback);
                } else {
                    throw new ExchangeError (feedback);
                }
            }
        }
    }
};
<commit_msg>exx market type<commit_after>'use strict';

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

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

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

module.exports = class exx extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'exx',
            'name': 'EXX',
            'countries': [ 'CN' ],
            'rateLimit': 1000 / 10,
            'userAgent': this.userAgents['chrome'],
            'has': {
                'cancelOrder': true,
                'createOrder': true,
                'fetchBalance': true,
                'fetchMarkets': true,
                'fetchOpenOrders': true,
                'fetchOrder': true,
                'fetchOrderBook': true,
                'fetchTicker': true,
                'fetchTickers': true,
                'fetchTrades': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/37770292-fbf613d0-2de4-11e8-9f79-f2dc451b8ccb.jpg',
                'api': {
                    'public': 'https://api.exx.com/data/v1',
                    'private': 'https://trade.exx.com/api',
                },
                'www': 'https://www.exx.com/',
                'doc': 'https://www.exx.com/help/restApi',
                'fees': 'https://www.exx.com/help/rate',
                'referral': 'https://www.exx.com/r/fde4260159e53ab8a58cc9186d35501f?recommQd=1',
            },
            'api': {
                'public': {
                    'get': [
                        'markets',
                        'tickers',
                        'ticker',
                        'depth',
                        'trades',
                    ],
                },
                'private': {
                    'get': [
                        'order',
                        'cancel',
                        'getOrder',
                        'getOpenOrders',
                        'getBalance',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': this.parseNumber ('0.001'),
                    'taker': this.parseNumber ('0.001'),
                },
                'funding': {
                    'withdraw': {},
                },
            },
            'commonCurrencies': {
                'DOS': 'DEMOS',
                'TV': 'TIV', // Ti-Value
            },
            'exceptions': {
                '103': AuthenticationError,
            },
        });
    }

    async fetchMarkets (params = {}) {
        const response = await this.publicGetMarkets (params);
        const ids = Object.keys (response);
        const result = [];
        for (let i = 0; i < ids.length; i++) {
            const id = ids[i];
            const market = response[id];
            const [ baseId, quoteId ] = id.split ('_');
            const base = this.safeCurrencyCode (baseId);
            const quote = this.safeCurrencyCode (quoteId);
            const symbol = base + '/' + quote;
            const active = market['isOpen'] === true;
            const amountPrecisionString = this.safeString (market, 'amountScale');
            const pricePrecisionString = this.safeString (market, 'priceScale');
            const amountLimit = this.parsePrecision (amountPrecisionString);
            const priceLimit = this.parsePrecision (pricePrecisionString);
            const precision = {
                'amount': parseInt (amountPrecisionString),
                'price': parseInt (pricePrecisionString),
            };
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'type': 'spot',
                'spot': true,
                'active': active,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': this.parseNumber (amountLimit),
                        'max': undefined,
                    },
                    'price': {
                        'min': this.parseNumber (priceLimit),
                        'max': undefined,
                    },
                    'cost': {
                        'min': this.safeNumber (market, 'minAmount'),
                        'max': undefined,
                    },
                },
                'info': market,
            });
        }
        return result;
    }

    parseTicker (ticker, market = undefined) {
        const symbol = market['symbol'];
        const timestamp = this.safeInteger (ticker, 'date');
        ticker = ticker['ticker'];
        const last = this.safeNumber (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeNumber (ticker, 'high'),
            'low': this.safeNumber (ticker, 'low'),
            'bid': this.safeNumber (ticker, 'buy'),
            'bidVolume': undefined,
            'ask': this.safeNumber (ticker, 'sell'),
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': this.safeNumber (ticker, 'riseRate'),
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeNumber (ticker, 'vol'),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        return this.parseTicker (response, market);
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        const response = await this.publicGetTickers (params);
        const result = {};
        const timestamp = this.milliseconds ();
        const ids = Object.keys (response);
        for (let i = 0; i < ids.length; i++) {
            const id = ids[i];
            if (!(id in this.markets_by_id)) {
                continue;
            }
            const market = this.markets_by_id[id];
            const symbol = market['symbol'];
            const ticker = {
                'date': timestamp,
                'ticker': response[id],
            };
            result[symbol] = this.parseTicker (ticker, market);
        }
        return this.filterByArray (result, 'symbol', symbols);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const request = {
            'currency': this.marketId (symbol),
        };
        const response = await this.publicGetDepth (this.extend (request, params));
        const timestamp = this.safeTimestamp (response, 'timestamp');
        return this.parseOrderBook (response, symbol, timestamp);
    }

    parseTrade (trade, market = undefined) {
        const timestamp = this.safeTimestamp (trade, 'date');
        const priceString = this.safeString (trade, 'price');
        const amountString = this.safeString (trade, 'amount');
        const price = this.parseNumber (priceString);
        const amount = this.parseNumber (amountString);
        const cost = this.parseNumber (Precise.stringMul (priceString, amountString));
        let symbol = undefined;
        if (market !== undefined) {
            symbol = market['symbol'];
        }
        const type = 'limit';
        const side = this.safeString (trade, 'type');
        const id = this.safeString (trade, 'tid');
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': symbol,
            'order': undefined,
            'type': type,
            'side': side,
            'takerOrMaker': undefined,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': undefined,
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
        };
        const response = await this.publicGetTrades (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        const response = await this.privateGetGetBalance (params);
        const result = { 'info': response };
        const balances = this.safeValue (response, 'funds');
        const currencies = Object.keys (balances);
        for (let i = 0; i < currencies.length; i++) {
            const currencyId = currencies[i];
            const balance = balances[currencyId];
            const code = this.safeCurrencyCode (currencyId);
            const account = this.account ();
            account['free'] = this.safeString (balance, 'balance');
            account['used'] = this.safeString (balance, 'freeze');
            account['total'] = this.safeString (balance, 'total');
            result[code] = account;
        }
        return this.parseBalance (result);
    }

    parseOrder (order, market = undefined) {
        //
        //     {
        //         "fees": 0,
        //         "total_amount": 1,
        //         "trade_amount": 0,
        //         "price": 30,
        //         "currency": “eth_hsr",
        //         "id": "13878",
        //         "trade_money": 0,
        //         "type": "buy",
        //         "trade_date": 1509728897772,
        //         "status": 0
        //     }
        //
        const symbol = market['symbol'];
        const timestamp = parseInt (order['trade_date']);
        const price = this.safeNumber (order, 'price');
        const cost = this.safeNumber (order, 'trade_money');
        const amount = this.safeNumber (order, 'total_amount');
        const filled = this.safeNumber (order, 'trade_amount', 0.0);
        let status = this.safeInteger (order, 'status');
        if (status === 1) {
            status = 'canceled';
        } else if (status === 2) {
            status = 'closed';
        } else {
            status = 'open';
        }
        let fee = undefined;
        if ('fees' in order) {
            fee = {
                'cost': this.safeNumber (order, 'fees'),
                'currency': market['quote'],
            };
        }
        return this.safeOrder ({
            'id': this.safeString (order, 'id'),
            'clientOrderId': undefined,
            'datetime': this.iso8601 (timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': undefined,
            'status': status,
            'symbol': symbol,
            'type': 'limit',
            'timeInForce': undefined,
            'postOnly': undefined,
            'side': order['type'],
            'price': price,
            'stopPrice': undefined,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': undefined,
            'trades': undefined,
            'fee': fee,
            'info': order,
            'average': undefined,
        });
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
            'type': side,
            'price': price,
            'amount': amount,
        };
        const response = await this.privateGetOrder (this.extend (request, params));
        const id = this.safeString (response, 'id');
        const order = this.parseOrder ({
            'id': id,
            'trade_date': this.milliseconds (),
            'total_amount': amount,
            'price': price,
            'type': side,
            'info': response,
        }, market);
        return order;
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'id': id,
            'currency': market['id'],
        };
        const response = await this.privateGetCancel (this.extend (request, params));
        return response;
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'id': id,
            'currency': market['id'],
        };
        const response = await this.privateGetGetOrder (this.extend (request, params));
        return this.parseOrder (response, market);
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument');
        }
        const market = this.market (symbol);
        const request = {
            'currency': market['id'],
        };
        const response = await this.privateGetGetOpenOrders (this.extend (request, params));
        if (!Array.isArray (response)) {
            return [];
        }
        return this.parseOrders (response, market, since, limit);
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'][api] + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length) {
                url += '?' + this.urlencode (params);
            }
        } else {
            this.checkRequiredCredentials ();
            const query = this.urlencode (this.keysort (this.extend ({
                'accesskey': this.apiKey,
                'nonce': this.nonce (),
            }, params)));
            const signed = this.hmac (this.encode (query), this.encode (this.secret), 'sha512');
            url += '?' + query + '&signature=' + signed;
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    handleErrors (httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody) {
        if (response === undefined) {
            return; // fallback to default error handler
        }
        //
        //  {"result":false,"message":"服务端忙碌"}
        //  ... and other formats
        //
        const code = this.safeString (response, 'code');
        const message = this.safeString (response, 'message');
        const feedback = this.id + ' ' + body;
        if (code === '100') {
            return;
        }
        if (code !== undefined) {
            this.throwExactlyMatchedException (this.exceptions, code, feedback);
            if (code === '308') {
                // this is returned by the exchange when there are no open orders
                // {"code":308,"message":"Not Found Transaction Record"}
                return;
            } else {
                throw new ExchangeError (feedback);
            }
        }
        const result = this.safeValue (response, 'result');
        if (result !== undefined) {
            if (!result) {
                if (message === '服务端忙碌') {
                    throw new ExchangeNotAvailable (feedback);
                } else {
                    throw new ExchangeError (feedback);
                }
            }
        }
    }
};
<|endoftext|>


<commit_before>var h = require('hyperscript')

/*
element classes are set with BEM convention.
https://css-tricks.com/bem-101/

B = block. module name, on the top level of the component.
E = element. a specific part of the component
M = modifier. something that changes an element (or block)
*/

var u = require('./util')
var Menu = require('./menu')

var each = u.each
module.exports = function (onSelect) {

  var d
  var content = h('div.row.hypertabs__content')

  function getSelection () {
    var sel = []
    each(content.children, function (tab, i) {
      if(tab.style.display !== 'none')
        sel.push(i)
    })
    if(''+sel === ''+selection) return
    d.selected = selection = sel
    if(onSelect) onSelect(selection)
    return sel
  }

  var menu = Menu(content, function () {
    getSelection()
  })
  var d = h('div.hypertabs.column',  menu, h('div.column', content))

  var selection = d.selected = []

  d.add = function (tab, change, split) {
    if(!split) tab.style.display = 'none'
    var index = content.children.length
    content.appendChild(tab)
    if(change !== false && !split) d.select(index)
    getSelection()
  }

  function find(name) {
    if(Number.isInteger(name)) return name // content.children[name]

    for(var i = 0; i < content.children.length; i++)
      if(content.children[i].id == name) return i

    return -1
  }

  d.has = function (name) {
    return ~find(name)
  }

  d.get = function (name) {
    return content.children[find(name)]
  }

  d.select = function (index, change, split) {
    if('string' === typeof index) index = find(index)

    var max = content.children.length - 1
    if(index > max) index = 0
    if(index < 0) index = max

    if(split)
      content.children[index].style.display = 'flex'
    else
      [].forEach.call(content.children, function (tab, i) {
        tab.style.display = i == index ? 'flex' : 'none'
      })
    getSelection()
  }

  d.selectRelative = function (n) {
    getSelection()
    d.select(selection[0] + n)
  }

  d.remove = function (i) {
    if(Array.isArray(i)) return i.reverse().forEach(d.remove)
    var el = d.get(i)
    if(el) content.removeChild(d.get(i))
  }

  var _display
  d.fullscreen = function (full) {
    menu.style.display = full ? 'none' : null
  }

  return d
}

<commit_msg>isFullscreen to get current state<commit_after>var h = require('hyperscript')

/*
element classes are set with BEM convention.
https://css-tricks.com/bem-101/

B = block. module name, on the top level of the component.
E = element. a specific part of the component
M = modifier. something that changes an element (or block)
*/

var u = require('./util')
var Menu = require('./menu')

var each = u.each
module.exports = function (onSelect) {

  var d
  var content = h('div.row.hypertabs__content')

  function getSelection () {
    var sel = []
    each(content.children, function (tab, i) {
      if(tab.style.display !== 'none')
        sel.push(i)
    })
    if(''+sel === ''+selection) return
    d.selected = selection = sel
    if(onSelect) onSelect(selection)
    return sel
  }

  var menu = Menu(content, function () {
    getSelection()
  })
  var d = h('div.hypertabs.column',  menu, h('div.column', content))

  var selection = d.selected = []

  d.add = function (tab, change, split) {
    if(!split) tab.style.display = 'none'
    var index = content.children.length
    content.appendChild(tab)
    if(change !== false && !split) d.select(index)
    getSelection()
  }

  function find(name) {
    if(Number.isInteger(name)) return name // content.children[name]

    for(var i = 0; i < content.children.length; i++)
      if(content.children[i].id == name) return i

    return -1
  }

  d.has = function (name) {
    return ~find(name)
  }

  d.get = function (name) {
    return content.children[find(name)]
  }

  d.select = function (index, change, split) {
    if('string' === typeof index) index = find(index)

    var max = content.children.length - 1
    if(index > max) index = 0
    if(index < 0) index = max

    if(split)
      content.children[index].style.display = 'flex'
    else
      [].forEach.call(content.children, function (tab, i) {
        tab.style.display = i == index ? 'flex' : 'none'
      })
    getSelection()
  }

  d.selectRelative = function (n) {
    getSelection()
    d.select(selection[0] + n)
  }

  d.remove = function (i) {
    if(Array.isArray(i)) return i.reverse().forEach(d.remove)
    var el = d.get(i)
    if(el) content.removeChild(d.get(i))
  }

  var _display
  d.fullscreen = function (full) {
    menu.style.display = full ? 'none' : null
    return full
  }

  d.isFullscreen = function () {
    return menu.style.display === 'none'
  }

  return d
}

<|endoftext|>


<commit_before>// Styles
import '../../stylus/components/_sliders.styl'

// Components
import VLabel from '../VLabel'
import { VScaleTransition } from '../transitions'

// Extensions
import VInput from '../VInput'

// Directives
import ClickOutside from '../../directives/click-outside'

// Utilities
import { addOnceEventListener, createRange } from '../../util/helpers'
import { consoleWarn } from '../../util/console'

export default {
  name: 'v-slider',

  extends: VInput,

  directives: { ClickOutside },

  data: vm => ({
    app: {},
    defaultColor: 'primary',
    isActive: false,
    keyPressed: 0,
    lazyValue: vm.value || 0
  }),

  props: {
    label: String,
    min: {
      type: [Number, String],
      default: 0
    },
    max: {
      type: [Number, String],
      default: 100
    },
    step: {
      type: [Number, String],
      default: 1
    },
    ticks: Boolean,
    thumbColor: {
      type: String,
      default: null
    },
    thumbLabel: Boolean,
    trackColor: {
      type: String,
      default: null
    },
    value: [Number, String]
  },

  computed: {
    classes () {
      return {
        'v-input--slider': true,
        'v-input--slider--ticks': !this.disabled &&
          this.stepNumeric && this.ticks
      }
    },
    computedColor () {
      return this.disabled ? null : (this.color || this.defaultColor)
    },
    computedTrackColor () {
      return this.disabled ? null : (this.trackColor || null)
    },
    computedThumbColor () {
      return (this.disabled || !this.inputWidth) ? null : (this.thumbColor || this.color || this.defaultColor)
    },
    stepNumeric () {
      return this.step > 0 ? parseFloat(this.step) : 1
    },
    inputValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        const { min, max } = this
        val = Math.min(Math.max(val, min), max)

        // Round value to ensure the
        // entire slider range can
        // be selected with step
        const value = this.roundValue(val)
        this.lazyValue = value

        this.$emit('input', value)
      }
    },
    thumbStyles () {
      return {
        transition: this.keyPressed >= 2 ? 'none' : '',
        left: `${this.inputWidth}%`
      }
    },
    trackPadding () {
      if (this.thumbLabel && this.isActive) return 0

      return 6 + (this.isActive && !this.disabled ? 3 : 0)
    },
    trackStyles () {
      return {
        transition: this.keyPressed >= 2 ? 'none' : '',
        left: `calc(${this.inputWidth}% + ${this.trackPadding}px)`,
        width: `calc(${100 - this.inputWidth}% - ${this.trackPadding}px)`
      }
    },
    trackFillStyles () {
      return {
        transition: this.keyPressed >= 2 ? 'none' : '',
        width: `calc(${this.inputWidth}% - ${this.trackPadding}px)`
      }
    },
    numTicks () {
      return Math.ceil((this.max - this.min) / this.stepNumeric)
    },
    inputWidth () {
      return (this.roundValue(this.inputValue) - this.min) / (this.max - this.min) * 100
    }
  },

  watch: {
    isActive (val) {
      this.isFocused = val
    },
    min (val) {
      val > this.inputValue && this.$emit('input', parseFloat(val))
    },
    max (val) {
      val < this.inputValue && this.$emit('input', parseFloat(val))
    },
    value (val) {
      this.inputValue = parseFloat(val)
    }
  },

  mounted () {
    // Without a v-app, iOS does not work with body selectors
    this.app = document.querySelector('[data-app]') ||
      consoleWarn('Missing v-app or a non-body wrapping element with the [data-app] attribute', this)
  },

  methods: {
    genDefaultSlot () {
      return [
        this.genLabel(),
        this.genSlider()
      ]
    },
    genLabel () {
      const data = {
        props: {
          color: this.validationState
        }
      }

      if (this.$attrs.id) data.props.for = this.$attrs.id

      return this.$createElement(VLabel, data, this.$slots.label || this.label)
    },
    genInput () {
      return null
    },
    genSlider () {
      return this.$createElement('div', {
        staticClass: 'v-slider',
        attrs: {
          role: 'slider',
          tabindex: this.disabled ? -1 : this.tabindex
        },
        on: Object.assign({}, {
          mouseup: this.onSliderMove,
          keydown: this.onKeyDown,
          keyup: this.onKeyUp
        }, this.$listeners),
        directives: [{
          name: 'click-outside',
          value: () => (this.isActive = false)
        }]
      }, [
        this.genInput(),
        this.genTrackContainer(),
        this.genSteps(),
        this.genThumbContainer()
      ])
    },
    genSteps () {
      if (!this.step || !this.ticks) return null

      const ticks = createRange(this.numTicks + 1).map(i => {
        const span = this.$createElement('span', {
          key: i,
          staticClass: 'v-slider__tick',
          style: {
            left: `${i * (100 / this.numTicks)}%`
          }
        })

        return span
      })

      return this.$createElement('div', {
        staticClass: 'v-slider__ticks-container'
      }, ticks)
    },
    genThumbContainer () {
      const children = []
      children.push(this.$createElement('div', {
        staticClass: 'v-slider__thumb',
        'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor)
      }))

      this.thumbLabel && children.push(this.genThumbLabel())

      return this.$createElement('div', {
        staticClass: 'v-slider__thumb-container',
        'class': {
          'v-slider__thumb-container-label': this.thumbLabel
        },
        style: this.thumbStyles,
        on: {
          touchstart: this.onMouseDown,
          mousedown: this.onMouseDown
        },
        ref: 'thumb'
      }, children)
    },
    genThumbLabel () {
      return this.$createElement(VScaleTransition, {
        props: { origin: 'bottom center' }
      }, [
        this.$createElement('div', {
          staticClass: 'v-slider__thumb-label__container',
          directives: [
            {
              name: 'show',
              value: this.isActive
            }
          ]
        }, [
          this.$createElement('div', {
            staticClass: 'v-slider__thumb-label',
            'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor)
          }, [
            this.$createElement('span', {}, this.inputValue)
          ])
        ])
      ])
    },
    genTrackContainer () {
      const children = [
        this.$createElement('div', {
          staticClass: 'v-slider__track',
          'class': this.addBackgroundColorClassChecks({}, this.computedTrackColor),
          style: this.trackStyles
        }),
        this.$createElement('div', {
          staticClass: 'v-slider__track-fill',
          'class': this.addBackgroundColorClassChecks(),
          style: this.trackFillStyles
        })
      ]

      return this.$createElement('div', {
        staticClass: 'v-slider__track__container', ref: 'track'
      }, children)
    },
    onMouseDown (e) {
      this.keyPressed = 2
      const options = { passive: true }
      this.isActive = true

      if ('touches' in e) {
        this.app.addEventListener('touchmove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'touchend', this.onMouseUp)
      } else {
        this.app.addEventListener('mousemove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'mouseup', this.onMouseUp)
      }
    },
    onMouseUp () {
      this.keyPressed = 0
      const options = { passive: true }
      this.isActive = false
      this.app.removeEventListener('touchmove', this.onMouseMove, options)
      this.app.removeEventListener('mousemove', this.onMouseMove, options)
    },
    onMouseMove (e) {
      const {
        left: offsetLeft,
        width: trackWidth
      } = this.$refs.track.getBoundingClientRect()
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      const left = Math.min(Math.max((clientX - offsetLeft) / trackWidth, 0), 1)
      if (clientX >= offsetLeft - 8 && clientX <= offsetLeft + trackWidth + 8) {
        const inputValue = parseFloat(this.min) + left * (this.max - this.min)
        this.inputValue = inputValue
      }
    },
    onKeyDown (e) {
      if (this.disabled || ![33, 34, 35, 36, 37, 39].includes(e.keyCode)) return

      e.preventDefault()
      const step = this.stepNumeric
      const steps = (this.max - this.min) / step
      if (e.keyCode === 37 || e.keyCode === 39) {
        // Left/right
        this.keyPressed += 1

        const direction = e.keyCode === 37 ? -1 : 1
        const multiplier = e.shiftKey ? 3 : (e.ctrlKey ? 2 : 1)

        this.inputValue = this.inputValue + (direction * step * multiplier)
      } else if (e.keyCode === 36) {
        // Home
        this.inputValue = parseFloat(this.min)
      } else if (e.keyCode === 35) {
        // End
        this.inputValue = parseFloat(this.max)
      } else /* if (e.keyCode === 33 || e.keyCode === 34) */ {
        // Page up/down
        const direction = e.keyCode === 34 ? 1 : -1
        this.inputValue = this.inputValue - (direction * step * (steps > 100 ? steps / 10 : 10))
      }
    },
    onKeyUp () {
      this.keyPressed = 0
    },
    onSliderMove (e) {
      if (!this.isActive) {
        this.onMouseMove(e)
      }
    },
    roundValue (value) {
      // Format input value using the same number
      // of decimals places as in the step prop
      const trimmedStep = this.step.toString().trim()
      const decimals = trimmedStep.indexOf('.') > -1
        ? (trimmedStep.length - trimmedStep.indexOf('.') - 1)
        : 0

      const newValue = 1 * Math.round(value / this.stepNumeric) * this.stepNumeric

      return parseFloat(Math.min(newValue, this.max).toFixed(decimals))
    }
  }
}
<commit_msg>fix(v-slider): hide label if none provided<commit_after>// Styles
import '../../stylus/components/_sliders.styl'

// Components
import VLabel from '../VLabel'
import { VScaleTransition } from '../transitions'

// Extensions
import VInput from '../VInput'

// Directives
import ClickOutside from '../../directives/click-outside'

// Utilities
import { addOnceEventListener, createRange } from '../../util/helpers'
import { consoleWarn } from '../../util/console'

export default {
  name: 'v-slider',

  extends: VInput,

  directives: { ClickOutside },

  data: vm => ({
    app: {},
    defaultColor: 'primary',
    isActive: false,
    keyPressed: 0,
    lazyValue: vm.value || 0
  }),

  props: {
    label: String,
    min: {
      type: [Number, String],
      default: 0
    },
    max: {
      type: [Number, String],
      default: 100
    },
    step: {
      type: [Number, String],
      default: 1
    },
    ticks: Boolean,
    thumbColor: {
      type: String,
      default: null
    },
    thumbLabel: Boolean,
    trackColor: {
      type: String,
      default: null
    },
    value: [Number, String]
  },

  computed: {
    classes () {
      return {
        'v-input--slider': true,
        'v-input--slider--ticks': !this.disabled &&
          this.stepNumeric && this.ticks
      }
    },
    computedColor () {
      return this.disabled ? null : (this.color || this.defaultColor)
    },
    computedTrackColor () {
      return this.disabled ? null : (this.trackColor || null)
    },
    computedThumbColor () {
      return (this.disabled || !this.inputWidth) ? null : (this.thumbColor || this.color || this.defaultColor)
    },
    stepNumeric () {
      return this.step > 0 ? parseFloat(this.step) : 1
    },
    inputValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        const { min, max } = this
        val = Math.min(Math.max(val, min), max)

        // Round value to ensure the
        // entire slider range can
        // be selected with step
        const value = this.roundValue(val)
        this.lazyValue = value

        this.$emit('input', value)
      }
    },
    thumbStyles () {
      return {
        transition: this.keyPressed >= 2 ? 'none' : '',
        left: `${this.inputWidth}%`
      }
    },
    trackPadding () {
      if (this.thumbLabel && this.isActive) return 0

      return 6 + (this.isActive && !this.disabled ? 3 : 0)
    },
    trackStyles () {
      return {
        transition: this.keyPressed >= 2 ? 'none' : '',
        left: `calc(${this.inputWidth}% + ${this.trackPadding}px)`,
        width: `calc(${100 - this.inputWidth}% - ${this.trackPadding}px)`
      }
    },
    trackFillStyles () {
      return {
        transition: this.keyPressed >= 2 ? 'none' : '',
        width: `calc(${this.inputWidth}% - ${this.trackPadding}px)`
      }
    },
    numTicks () {
      return Math.ceil((this.max - this.min) / this.stepNumeric)
    },
    inputWidth () {
      return (this.roundValue(this.inputValue) - this.min) / (this.max - this.min) * 100
    }
  },

  watch: {
    isActive (val) {
      this.isFocused = val
    },
    min (val) {
      val > this.inputValue && this.$emit('input', parseFloat(val))
    },
    max (val) {
      val < this.inputValue && this.$emit('input', parseFloat(val))
    },
    value (val) {
      this.inputValue = parseFloat(val)
    }
  },

  mounted () {
    // Without a v-app, iOS does not work with body selectors
    this.app = document.querySelector('[data-app]') ||
      consoleWarn('Missing v-app or a non-body wrapping element with the [data-app] attribute', this)
  },

  methods: {
    genDefaultSlot () {
      return [
        this.genLabel(),
        this.genSlider()
      ]
    },
    genLabel () {
      if (!this.label) return null

      const data = {
        props: {
          color: this.validationState
        }
      }

      if (this.$attrs.id) data.props.for = this.$attrs.id

      return this.$createElement(VLabel, data, this.$slots.label || this.label)
    },
    genInput () {
      return null
    },
    genSlider () {
      return this.$createElement('div', {
        staticClass: 'v-slider',
        attrs: {
          role: 'slider',
          tabindex: this.disabled ? -1 : this.tabindex
        },
        on: Object.assign({}, {
          mouseup: this.onSliderMove,
          keydown: this.onKeyDown,
          keyup: this.onKeyUp
        }, this.$listeners),
        directives: [{
          name: 'click-outside',
          value: () => (this.isActive = false)
        }]
      }, [
        this.genInput(),
        this.genTrackContainer(),
        this.genSteps(),
        this.genThumbContainer()
      ])
    },
    genSteps () {
      if (!this.step || !this.ticks) return null

      const ticks = createRange(this.numTicks + 1).map(i => {
        const span = this.$createElement('span', {
          key: i,
          staticClass: 'v-slider__tick',
          style: {
            left: `${i * (100 / this.numTicks)}%`
          }
        })

        return span
      })

      return this.$createElement('div', {
        staticClass: 'v-slider__ticks-container'
      }, ticks)
    },
    genThumbContainer () {
      const children = []
      children.push(this.$createElement('div', {
        staticClass: 'v-slider__thumb',
        'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor)
      }))

      this.thumbLabel && children.push(this.genThumbLabel())

      return this.$createElement('div', {
        staticClass: 'v-slider__thumb-container',
        'class': {
          'v-slider__thumb-container-label': this.thumbLabel
        },
        style: this.thumbStyles,
        on: {
          touchstart: this.onMouseDown,
          mousedown: this.onMouseDown
        },
        ref: 'thumb'
      }, children)
    },
    genThumbLabel () {
      return this.$createElement(VScaleTransition, {
        props: { origin: 'bottom center' }
      }, [
        this.$createElement('div', {
          staticClass: 'v-slider__thumb-label__container',
          directives: [
            {
              name: 'show',
              value: this.isActive
            }
          ]
        }, [
          this.$createElement('div', {
            staticClass: 'v-slider__thumb-label',
            'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor)
          }, [
            this.$createElement('span', {}, this.inputValue)
          ])
        ])
      ])
    },
    genTrackContainer () {
      const children = [
        this.$createElement('div', {
          staticClass: 'v-slider__track',
          'class': this.addBackgroundColorClassChecks({}, this.computedTrackColor),
          style: this.trackStyles
        }),
        this.$createElement('div', {
          staticClass: 'v-slider__track-fill',
          'class': this.addBackgroundColorClassChecks(),
          style: this.trackFillStyles
        })
      ]

      return this.$createElement('div', {
        staticClass: 'v-slider__track__container', ref: 'track'
      }, children)
    },
    onMouseDown (e) {
      this.keyPressed = 2
      const options = { passive: true }
      this.isActive = true

      if ('touches' in e) {
        this.app.addEventListener('touchmove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'touchend', this.onMouseUp)
      } else {
        this.app.addEventListener('mousemove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'mouseup', this.onMouseUp)
      }
    },
    onMouseUp () {
      this.keyPressed = 0
      const options = { passive: true }
      this.isActive = false
      this.app.removeEventListener('touchmove', this.onMouseMove, options)
      this.app.removeEventListener('mousemove', this.onMouseMove, options)
    },
    onMouseMove (e) {
      const {
        left: offsetLeft,
        width: trackWidth
      } = this.$refs.track.getBoundingClientRect()
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      const left = Math.min(Math.max((clientX - offsetLeft) / trackWidth, 0), 1)
      if (clientX >= offsetLeft - 8 && clientX <= offsetLeft + trackWidth + 8) {
        const inputValue = parseFloat(this.min) + left * (this.max - this.min)
        this.inputValue = inputValue
      }
    },
    onKeyDown (e) {
      if (this.disabled || ![33, 34, 35, 36, 37, 39].includes(e.keyCode)) return

      e.preventDefault()
      const step = this.stepNumeric
      const steps = (this.max - this.min) / step
      if (e.keyCode === 37 || e.keyCode === 39) {
        // Left/right
        this.keyPressed += 1

        const direction = e.keyCode === 37 ? -1 : 1
        const multiplier = e.shiftKey ? 3 : (e.ctrlKey ? 2 : 1)

        this.inputValue = this.inputValue + (direction * step * multiplier)
      } else if (e.keyCode === 36) {
        // Home
        this.inputValue = parseFloat(this.min)
      } else if (e.keyCode === 35) {
        // End
        this.inputValue = parseFloat(this.max)
      } else /* if (e.keyCode === 33 || e.keyCode === 34) */ {
        // Page up/down
        const direction = e.keyCode === 34 ? 1 : -1
        this.inputValue = this.inputValue - (direction * step * (steps > 100 ? steps / 10 : 10))
      }
    },
    onKeyUp () {
      this.keyPressed = 0
    },
    onSliderMove (e) {
      if (!this.isActive) {
        this.onMouseMove(e)
      }
    },
    roundValue (value) {
      // Format input value using the same number
      // of decimals places as in the step prop
      const trimmedStep = this.step.toString().trim()
      const decimals = trimmedStep.indexOf('.') > -1
        ? (trimmedStep.length - trimmedStep.indexOf('.') - 1)
        : 0

      const newValue = 1 * Math.round(value / this.stepNumeric) * this.stepNumeric

      return parseFloat(Math.min(newValue, this.max).toFixed(decimals))
    }
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

var async = require('async');
var cli = require('cli');
var colors = require('colors');
var fs = require('fs');
var path = require('path');
var argv = require('optimist').usage('Usage: $0 -qo')
			.options(
				{
					q: {
						alias: 'quiet',
						boolean: true,
						default: false
					},
					v: {
						alias: 'verbose',
						boolean: true,
						default: false
					},
					o: {
						alias: 'open',
						boolean: true,
						default: false
					},
					r: {
						alias: 'relative',
						boolean: true,
						default: false
					}
				}
			).argv;

colors.setTheme({
	help: 'cyan',
	warn: 'yellow',
	error: 'red',
	subtle: 'grey'
});

var args = argv._;

var INDENT = '    ';

var QUIET = argv.q;
var VERBOSE = argv.v;
var RELATIVE = argv.r;

var CWD = process.env.GIT_PWD || process.cwd();
var TOP_LEVEL;

var REGEX_EXT_CSS = /\.(s)?css$/;
var REGEX_EXT_HTML = /\.(jsp.?|html|vm|ftl)$/;
var REGEX_EXT_JS = /\.js$/;

var REGEX_LEADING_SPACE = /^\s+/;
var REGEX_LEADING_INCLUDE = /^@include /;
var REGEX_PROP_KEY = /^\s*(?:@include\s)?([^:]+)(?:)/;

var REGEX_PROPERTY = /^\t*([^:]+:|@include\s)[^;]+;$/;
var REGEX_SUB = /\{\s*([^|}]+?)\s*(?:\|([^}]*))?\s*\}/g;
// var REGEX_ZERO_UNIT = /\b0(?!s\b)[a-zA-Z]{1,}\b/;
var REGEX_INTEGER_DECIMAL = /[^0-9]\.\d+\b/;
var REGEX_DOUBLE_QUOTES = /"[^"]*"/;
var REGEX_SINGLE_QUOTES = /'[^']*'/g;
var REGEX_ZERO_UNIT = /(#?)(\b0(?!s\b)[a-zA-Z]{1,}\b)/;
var REGEX_HEX_REDUNDANT = /#([0-9A-Fa-f])\1([0-9A-Fa-f])\2([0-9A-Fa-f])\3/;
var REGEX_HEX_LOWER = /[a-f]/;
var REGEX_HEX = /#[0-9A-Fa-f]{3,6}/;

var REGEX_MIXED_SPACES = /^.*( \t|\t ).*$/;

var REPLACE_REGEX_REDUNDANT = '#$1$2$3';

var hasProperty = function(item) {
	return REGEX_PROPERTY.test(item);
};

var hasLowerCaseRegex = function(item) {
	var match = item.match(REGEX_HEX);
	var lowerCaseRegex = false;

	if (match) {
		lowerCaseRegex = REGEX_HEX_LOWER.test(match);
	}

	return lowerCaseRegex;
};

var hasRedundantRegex = function(item) {
	return REGEX_HEX_REDUNDANT.test(item);
};

var hasMissingInteger = function(item) {
	return REGEX_INTEGER_DECIMAL.test(item);
};

var hasNeedlessUnit = function(item) {
	var m = item.match(REGEX_ZERO_UNIT);

	return m && !m[1];
	// return REGEX_ZERO_UNIT.test(item);
};

var hasMixedSpaces = function(item) {
	return REGEX_MIXED_SPACES.test(item);
};

var _testDoubleQuotes = function(item) {
	return REGEX_DOUBLE_QUOTES.test(item);
};

var hasDoubleQuotes = function(item) {
	var doubleQuoted = false;

	if (_testDoubleQuotes(item)) {
		// Remove single quoted strings from the line to
		// handle cases where we have var = ''
		var newItem = item.replace(REGEX_SINGLE_QUOTES, '');

		doubleQuoted = _testDoubleQuotes(newItem);
	}

	return doubleQuoted;
};

var fileErrors = {};

var trackErr = function(err, file) {
	var errors = fileErrors[file];

	if (!errors) {
		errors = [];

		fileErrors[file] = errors;
	}

	errors.push(err);
}

var sub = function(str, obj) {
	var objType = typeof obj;

	if (objType !== 'object' && objType !== 'function') {
		obj = Array.prototype.slice.call(arguments, 1);
	}

	return str.replace ? str.replace(REGEX_SUB, function(match, key) {
		return (typeof obj[key] !== 'undefined') ? obj[key] : match;
	}) : s;
};

var iterateLines = function(contents, iterator) {
	var lines = contents.split('\n');

	lines.forEach(iterator);
};

var checkCss = function(contents, file) {
	iterateLines(
		contents,
		function(item, index, collection) {
			var fullItem = item;

			item = item.trim();

			var lineNum = index + 1;
			var nextItem = collection[lineNum] && collection[lineNum].trim();

			if (hasProperty(item)) {
				item = item.replace(REGEX_LEADING_SPACE, '').replace(REGEX_LEADING_INCLUDE, '');

				var nextItemMatch;

				var itemMatch = item.match(REGEX_PROP_KEY);

				if (nextItem && hasProperty(nextItem)) {
					nextItem = nextItem.replace(REGEX_LEADING_SPACE, '');
					nextItemMatch = nextItem && hasProperty(nextItem) && nextItem.match(REGEX_PROP_KEY);
				}

				if (itemMatch && nextItemMatch) {
					if (itemMatch[1] > nextItemMatch[1]) {
						trackErr(sub('Line: {0} Sort: {1} {2}', lineNum, item, nextItem).warn, file);
					}
				}
			}

			var hexMatch = item.match(REGEX_HEX);

			if (hexMatch) {
				hexMatch = hexMatch[0];

				if (hasLowerCaseRegex(hexMatch)) {
					trackErr(sub('Line {0} Hex code should be all uppercase: {1}', lineNum, item).warn, file);
				}

				if (hasRedundantRegex(hexMatch)) {
					trackErr(sub('Line {0} Hex code can be reduced to {2}: {1}', lineNum, item, hexMatch.replace(REGEX_HEX_REDUNDANT, REPLACE_REGEX_REDUNDANT)).warn, file);
				}
			}

			if (hasNeedlessUnit(item)) {
				trackErr(sub('Line {0} Needless unit: {1}', lineNum, item).warn, file);
			}

			if (hasMissingInteger(item)) {
				trackErr(sub('Line {0} Missing integer: {1}', lineNum, item).warn, file);
			}

			if (hasMixedSpaces(fullItem)) {
				trackErr(sub('Line {0} Mixed spaces and tabs: {1}', lineNum, item).warn, file);
			}
		}
	);
};

var checkJs = function(contents, file) {
	iterateLines(
		contents,
		function(item, index, collection) {
			var fullItem = item;

			item = item.trim();

			var lineNum = index + 1;

			if (hasMixedSpaces(fullItem)) {
				trackErr(sub('Line {0} Mixed spaces and tabs: {1}', lineNum, item).warn, file);
			}

			if (hasDoubleQuotes(fullItem)) {
				trackErr(sub('Line {0} Strings should be single quoted: {1}', lineNum, item).warn, file);
			}
		}
	);
};

var checkHTML = function(contents, file) {
	iterateLines(
		contents,
		function(item, index, collection) {
			var fullItem = item;

			item = item.trim();

			var lineNum = index + 1;

			var attrs = fullItem.match(/(?: )([A-Za-z0-9-]+=["'][^"']+["'])/g);

			if (attrs) {
				var lastAttr = -1;

				attrs.forEach(
					function(item, index, collection) {
						var pieces = item.trim().split('=');

						var attrName = pieces[0];
						var attrValue = pieces[1].trim().replace(/(^["']|["']$)/g, '');

						if (lastAttr > attrName) {
							var re = new RegExp('\\b' + lastAttr + '\\b.*?> ?<.*?' + attrName);

							var note = '';

							if (re.test(fullItem)) {
								note = '**'
							}

							if (!note || note && VERBOSE) {
								trackErr(sub('Line {0} Sort attributes{3}: {1} {2}', lineNum, lastAttr, attrName, note).warn, file);
							}
						}

						var attrValuePieces = attrValue.split(' ');

						var lastAttrPiece = -1;

						attrValuePieces.forEach(
							function(item, index, collection) {
								if (/^[A-Za-z]/.test(item)) {
									// Skip event handlers like onClick, etc since they will have
									// complex values that probably shouldn't be sorted
									if (attrName.indexOf('on') !== 0 && lastAttrPiece > item) {
										trackErr(sub('Line {0} Sort attribute values: {1} {2}', lineNum, lastAttrPiece, item).warn, file);
									}

									lastAttrPiece = item;
								}
							}
						);

						lastAttr = attrName;
					}
				);
			}
		}
	);
};

var series = args.map(
	function(file) {
		return function(cb) {
			fs.readFile(file, 'utf-8', function (err, data) {
				if (err) {
					return cb(err);
				}

				if (REGEX_EXT_CSS.test(file)) {
					checkCss(data, file);
				}
				else if (REGEX_EXT_JS.test(file)) {
					checkJs(data, file);
				}
				else if (REGEX_EXT_HTML.test(file)) {
					checkHTML(data, file);
				}

				var errors = fileErrors[file] || [];

				var includeHeaderFooter = (errors.length || !QUIET);

				if (includeHeaderFooter) {
					var fileName = file;

					if (RELATIVE) {
						file = path.relative(CWD, file);
					}

					console.log('File:'.blackBG + ' ' + file.underline);
				}

				if (errors.length) {
					console.log(INDENT + errors.join('\n' + INDENT));
				}
				else if (includeHeaderFooter) {
					console.log(INDENT + 'clear');
				}

				if (includeHeaderFooter) {
					console.log('----'.subtle);
				}

				cb();
			});
		}
	}
);

var callback = function() {};

if (argv.o) {
	callback = function(err, result) {
		var errorFiles = Object.keys(fileErrors);

		if (errorFiles.length) {
			cli.exec(
				'git config --get user.editor',
				function(res) {
					cli.exec(
						'open -a "' + res[0] + '" "' + errorFiles.join('" "') + '"'
					);
				}
			);
		}
	};
}

if (RELATIVE) {
	series.unshift(
		function(cb) {
			cli.exec(
				'git rev-parse --show-toplevel',
				function(res) {
					TOP_LEVEL = res;
					cb();
				}
			);
		}
	);
}

async.series(series, callback);<commit_msg>Add support for modifying the files inline for some errors<commit_after>#!/usr/bin/env node

var async = require('async');
var cli = require('cli');
var colors = require('colors');
var fs = require('fs');
var path = require('path');
var argv = require('optimist').usage('Usage: $0 -qo')
			.options(
				{
					i: {
						alias: 'inline-edit',
						boolean: true,
						default: false
					},
					o: {
						alias: 'open',
						boolean: true,
						default: false
					},
					q: {
						alias: 'quiet',
						boolean: true,
						default: false
					},
					r: {
						alias: 'relative',
						boolean: true,
						default: false
					},
					v: {
						alias: 'verbose',
						boolean: true,
						default: false
					}
				}
			).argv;

colors.setTheme({
	help: 'cyan',
	warn: 'yellow',
	error: 'red',
	subtle: 'grey'
});

var args = argv._;

var INDENT = '    ';

var QUIET = argv.q;
var VERBOSE = argv.v;
var RELATIVE = argv.r;
var INLINE_REPLACE = argv.i;

var CWD = process.env.GIT_PWD || process.cwd();
var TOP_LEVEL;

var REGEX_EXT_CSS = /\.(s)?css$/;
var REGEX_EXT_HTML = /\.(jsp.?|html|vm|ftl)$/;
var REGEX_EXT_JS = /\.js$/;

var REGEX_LEADING_SPACE = /^\s+/;
var REGEX_LEADING_INCLUDE = /^@include /;
var REGEX_PROP_KEY = /^\s*(?:@include\s)?([^:]+)(?:)/;

var REGEX_PROPERTY = /^\t*([^:]+:|@include\s)[^;]+;$/;
var REGEX_SUB = /\{\s*([^|}]+?)\s*(?:\|([^}]*))?\s*\}/g;
// var REGEX_ZERO_UNIT = /\b0(?!s\b)[a-zA-Z]{1,}\b/;
var REGEX_INTEGER_DECIMAL = /([^0-9])(\.\d+)/;
var REGEX_DOUBLE_QUOTES = /"[^"]*"/;
var REGEX_SINGLE_QUOTES = /'[^']*'/g;
var REGEX_ZERO_UNIT = /(#?)(\b0(?!s\b)[a-zA-Z]{1,}\b)/;
var REGEX_HEX_REDUNDANT = /#([0-9A-Fa-f])\1([0-9A-Fa-f])\2([0-9A-Fa-f])\3/;
var REGEX_HEX_LOWER = /[a-f]/;
var REGEX_HEX = /#[0-9A-Fa-f]{3,6}/;

var REGEX_MIXED_SPACES = /^.*( \t|\t ).*$/;

var REPLACE_REGEX_REDUNDANT = '#$1$2$3';

var hasProperty = function(item) {
	return REGEX_PROPERTY.test(item);
};

var hasLowerCaseRegex = function(item) {
	var match = item.match(REGEX_HEX);
	var lowerCaseRegex = false;

	if (match) {
		lowerCaseRegex = REGEX_HEX_LOWER.test(match);
	}

	return lowerCaseRegex;
};

var hasRedundantRegex = function(item) {
	return REGEX_HEX_REDUNDANT.test(item);
};

var hasMissingInteger = function(item) {
	return REGEX_INTEGER_DECIMAL.test(item);
};

var hasNeedlessUnit = function(item) {
	var m = item.match(REGEX_ZERO_UNIT);

	return m && !m[1];
	// return REGEX_ZERO_UNIT.test(item);
};

var hasMixedSpaces = function(item) {
	return REGEX_MIXED_SPACES.test(item);
};

var _testDoubleQuotes = function(item) {
	return REGEX_DOUBLE_QUOTES.test(item);
};

var hasDoubleQuotes = function(item) {
	var doubleQuoted = false;

	if (_testDoubleQuotes(item)) {
		// Remove single quoted strings from the line to
		// handle cases where we have var = ''
		var newItem = item.replace(REGEX_SINGLE_QUOTES, '');

		doubleQuoted = _testDoubleQuotes(newItem);
	}

	return doubleQuoted;
};

var fileErrors = {};

var trackErr = function(err, file) {
	var errors = fileErrors[file];

	if (!errors) {
		errors = [];

		fileErrors[file] = errors;
	}

	errors.push(err);
}

var sub = function(str, obj) {
	var objType = typeof obj;

	if (objType !== 'object' && objType !== 'function') {
		obj = Array.prototype.slice.call(arguments, 1);
	}

	return str.replace ? str.replace(REGEX_SUB, function(match, key) {
		return (typeof obj[key] !== 'undefined') ? obj[key] : match;
	}) : s;
};

var iterateLines = function(contents, iterator) {
	var lines = contents.split('\n');

	return lines.map(iterator).join('\n');
};

var checkCss = function(contents, file) {
	return iterateLines(
		contents,
		function(item, index, collection) {
			var fullItem = item;

			item = item.trim();

			var lineNum = index + 1;
			var nextItem = collection[lineNum] && collection[lineNum].trim();

			if (hasProperty(item)) {
				item = item.replace(REGEX_LEADING_SPACE, '').replace(REGEX_LEADING_INCLUDE, '');

				var nextItemMatch;

				var itemMatch = item.match(REGEX_PROP_KEY);

				if (nextItem && hasProperty(nextItem)) {
					nextItem = nextItem.replace(REGEX_LEADING_SPACE, '');
					nextItemMatch = nextItem && hasProperty(nextItem) && nextItem.match(REGEX_PROP_KEY);
				}

				if (itemMatch && nextItemMatch) {
					if (itemMatch[1] > nextItemMatch[1]) {
						trackErr(sub('Line: {0} Sort: {1} {2}', lineNum, item, nextItem).warn, file);
					}
				}
			}

			var hexMatch = item.match(REGEX_HEX);

			if (hexMatch) {
				hexMatch = hexMatch[0];

				if (hasLowerCaseRegex(hexMatch)) {
					trackErr(sub('Line {0} Hex code should be all uppercase: {1}', lineNum, item).warn, file);

					var newHex = hexMatch.toUpperCase();

					fullItem = fullItem.replace(hexMatch, newHex);

					hexMatch = newHex;

					item = fullItem.trim();
				}

				if (hasRedundantRegex(hexMatch)) {
					var reducedHex = hexMatch.replace(REGEX_HEX_REDUNDANT, REPLACE_REGEX_REDUNDANT);

					trackErr(sub('Line {0} Hex code can be reduced to {2}: {1}', lineNum, item, reducedHex).warn, file);

					fullItem = fullItem.replace(hexMatch, reducedHex);

					hexMatch = reducedHex;

					item = fullItem.trim();
				}
			}

			if (hasNeedlessUnit(item)) {
				trackErr(sub('Line {0} Needless unit: {1}', lineNum, item).warn, file);

				fullItem = fullItem.replace(REGEX_ZERO_UNIT, '0');

				item = fullItem.trim();
			}

			if (hasMissingInteger(item)) {
				trackErr(sub('Line {0} Missing integer: {1}', lineNum, item).warn, file);

				fullItem = fullItem.replace(REGEX_INTEGER_DECIMAL, '$10$2');

				item = fullItem.trim();
			}

			if (hasMixedSpaces(fullItem)) {
				trackErr(sub('Line {0} Mixed spaces and tabs: {1}', lineNum, item).warn, file);
			}

			return fullItem;
		}
	);
};

var checkJs = function(contents, file) {
	return iterateLines(
		contents,
		function(item, index, collection) {
			var fullItem = item;

			item = item.trim();

			var lineNum = index + 1;

			if (hasMixedSpaces(fullItem)) {
				trackErr(sub('Line {0} Mixed spaces and tabs: {1}', lineNum, item).warn, file);
			}

			if (hasDoubleQuotes(fullItem)) {
				trackErr(sub('Line {0} Strings should be single quoted: {1}', lineNum, item).warn, file);
			}

			return fullItem;
		}
	);
};

var checkHTML = function(contents, file) {
	return iterateLines(
		contents,
		function(item, index, collection) {
			var fullItem = item;

			item = item.trim();

			var lineNum = index + 1;

			var attrs = fullItem.match(/(?: )([A-Za-z0-9-]+=["'][^"']+["'])/g);

			if (attrs) {
				var lastAttr = -1;

				attrs.forEach(
					function(item, index, collection) {
						var pieces = item.trim().split('=');

						var attrName = pieces[0];
						var attrValue = pieces[1].trim().replace(/(^["']|["']$)/g, '');

						if (lastAttr > attrName) {
							var re = new RegExp('\\b' + lastAttr + '\\b.*?> ?<.*?' + attrName);

							var note = '';

							if (re.test(fullItem)) {
								note = '**'
							}

							if (!note || note && VERBOSE) {
								trackErr(sub('Line {0} Sort attributes{3}: {1} {2}', lineNum, lastAttr, attrName, note).warn, file);
							}
						}

						var attrValuePieces = attrValue.split(' ');

						var lastAttrPiece = -1;

						attrValuePieces.forEach(
							function(item, index, collection) {
								if (/^[A-Za-z]/.test(item)) {
									// Skip event handlers like onClick, etc since they will have
									// complex values that probably shouldn't be sorted
									if (attrName.indexOf('on') !== 0 && lastAttrPiece > item) {
										trackErr(sub('Line {0} Sort attribute values: {1} {2}', lineNum, lastAttrPiece, item).warn, file);
									}

									lastAttrPiece = item;
								}
							}
						);

						lastAttr = attrName;
					}
				);
			}

			return fullItem;
		}
	);
};

var series = args.map(
	function(file) {
		return function(cb) {
			fs.readFile(file, 'utf-8', function (err, data) {
				if (err) {
					return cb(err);
				}

				if (REGEX_EXT_CSS.test(file)) {
					formatter = checkCss;
				}
				else if (REGEX_EXT_JS.test(file)) {
					formatter = checkJs;
				}
				else if (REGEX_EXT_HTML.test(file)) {
					formatter = checkHTML;
				}

				var content = formatter(data, file);
// console.log(content == data, file);
				var errors = fileErrors[file] || [];

				var includeHeaderFooter = (errors.length || !QUIET);

				if (includeHeaderFooter) {
					var fileName = file;

					if (RELATIVE) {
						file = path.relative(CWD, file);
					}

					console.log('File:'.blackBG + ' ' + file.underline);
				}

				if (errors.length) {
					console.log(INDENT + errors.join('\n' + INDENT));
				}
				else if (includeHeaderFooter) {
					console.log(INDENT + 'clear');
				}

				if (includeHeaderFooter) {
					console.log('----'.subtle);
				}

				var changed = (content != data);

				if (INLINE_REPLACE && changed) {
					fs.writeFile(file, content, function(err, result) {
						if (err) {
							return cb(err);
						}

						cb(null, content);
					});
				}
				else {
					cb(null, content);
				}
			});
		}
	}
);

var callback = function() {};

if (argv.o) {
	callback = function(err, result) {
		var errorFiles = Object.keys(fileErrors);

		if (errorFiles.length) {
			cli.exec(
				'git config --get user.editor',
				function(res) {
					cli.exec(
						'open -a "' + res[0] + '" "' + errorFiles.join('" "') + '"'
					);
				}
			);
		}
	};
}

if (RELATIVE) {
	series.unshift(
		function(cb) {
			cli.exec(
				'git rev-parse --show-toplevel',
				function(res) {
					TOP_LEVEL = res;
					cb();
				}
			);
		}
	);
}

async.series(series, callback);<|endoftext|>


<commit_before>/**
 * remove '.md' and put the date in the end
 *
 * @param title
 * @returns {string}
 */
export function cleanTitle(title) {
    return title.replace(/\.md$/, '')
                .replace(/^(\d{4}-\d{2}-\d{2})-(.+)$/, '$2 - $1');
}

/**
 * get title from file name
 *
 * @param title
 * @returns {string}
 */
export function onlyTitle(title) {
    return title.replace(/^\d{4}-\d{2}-\d{2}-(.+?)\.md$/, '$1');
}

/**
 * get date from file name
 *
 * @param title
 * @returns {string}
 */
export function onlyPublishDate(title) {
    return /^\d{4}-\d{2}-\d{2}/.exec(title);
}<commit_msg>重写过滤器，以适应不规范的 markdown 文件名<commit_after>/**
 * get title from file name
 *
 * @param title
 * @returns {string}
 */
export function onlyTitle(title) {
    // return title.replace(/^\d{4}-\d{1,2}-\d{1,2}-(.+?)\.md$/, '$1');
    return title.replace(/\.md$/, '')
                .replace(/^\d{4}-\d{1,2}-\d{1,2}-/, '');
}

/**
 * get date from file name
 *
 * @param title
 * @returns {array|null}
 */
export function onlyPublishDate(title) {
    // return /^\d{4}-(?:0?[1-9]|1[0-2])-\d{1,2}/.exec(title);
    return /^\d{4}-\d{1,2}-\d{1,2}/.exec(title);
}<|endoftext|>


<commit_before>import React from 'react';
import I18nHelper from './../../utils/helpers/i18n';
import Input from './../../utils/decorators/input';
import InputLabel from './../../utils/decorators/input-label';
import InputValidation from './../../utils/decorators/input-validation';

/**
 * A decimal widget.
 *
 * == How to use a Decimal in a component:
 *
 * In your file
 *
 *   import Decimal from 'carbon/lib/components/decimal';
 *
 * To render the Decimal:
 *
 *   <Decimal name="myDecimal" />
 *
 * @class Decimal
 * @constructor
 * @decorators {Input,InputLabel,InputValidation}
 */
const Decimal = Input(InputLabel(InputValidation(
class Decimal extends React.Component {

  /**
   * Stores the document - allows us to override it different contexts, such as
   * when running tests.
   *
   * @property _document
   * @type {document}
   */
  _document = document;

  /**
   * Used within the onClick and onBlur method to
   * check if the current visible input value is
   * highlighted
   *
   * @property highlighted
   * @type {Boolean}
   */
  highlighted = false;

  static propTypes = {
    /**
     * Sets the default value of the decimal field
     *
     * @property defaultValue
     * @type {String}
     * @default '0.00'
     */
    defaultValue: React.PropTypes.string,

    /**
     * Sets the default value alignment
     *
     * @property align
     * @type {String}
     * @default 'right'
     */
    align: React.PropTypes.string,

    /**
     * Sets the pricision of the field
     *
     * @property precision
     * @type {Integer}
     * @default 2
     */
    precision: React.PropTypes.number
  }

  static defaultProps = {
    defaultValue: '0.00',
    align: "right",
    precision: 2
  }

  state = {
    /**
     * The formatted value for display
     *
     * @property visibleValue
     * @type {String}
     */
    visibleValue: I18nHelper.formatDecimal(this.value, this.props.precision)
  }

  /**
   * A lifecycle method to update the visible value with a formatted version,
   * only when the field is not the active element.
   *
   * @method componentWillReceiveProps
   * @param {Object} props The new props passed down to the component
   * @return {void}
   */
  componentWillReceiveProps(props) {
    if (this._document.activeElement != this._input) {
      let value = props.value || props.defaultValue;
      this.setState({ visibleValue: I18nHelper.formatDecimal(value, this.props.precision) });
    }
  }

  /**
   * Callback to update the hidden field on change.
   *
   * @method emitOnChangeCallback
   * @param {String} val The unformatted decimal value
   * @return {void}
   */
  emitOnChangeCallback = (val) => {
    let hiddenField = this.refs.hidden;
    hiddenField.value = val;

    this._handleOnChange({ target: hiddenField });
  }

  /**
   * Checks that visibleValue is valid decimal.
   * This is a post-processor applied after the value has been updated.
   *
   * @method isValidDecimal
   * @param {String} value new prop value
   * @return {Boolean} true if a valid decimal
   */
  isValidDecimal = (value, precision) => {
    let del, regex, sep, format = I18nHelper.format();
    del = format.delimiter;
    sep = format.separator;
    regex = precision > 0 ?
        new RegExp('^[-]?[0-9]*(?:\${del}?[0-9]?)*\${sep}?[0-9]{0,${precision}}$') :
        new RegExp('^[-]?[0-9]*(?:\${del}?[0-9]?)*$');
    return regex.test(value);
  }

  /**
   * Handles Change to visible field
   *
   * @method handleVisibleInputChange
   * @param {Object} ev event
   * @return {void}
   */
  handleVisibleInputChange = (ev) => {
    if (this.isValidDecimal(ev.target.value, this.props.precision)) {
      this.setState({ visibleValue: ev.target.value });
      this.emitOnChangeCallback(I18nHelper.unformatDecimal(ev.target.value));
    } else {
      // reset the value
      ev.target.value = this.state.visibleValue;
      // reset the selection range
      ev.target.setSelectionRange(this.selectionStart, this.selectionEnd);
    }
  }

  /**
   * Updates visible value on blur
   *
   * @method handleBlur
   * @return {void}
   */
  handleBlur = () => {
    this.setState({ visibleValue: I18nHelper.formatDecimal(this.value, this.props.precision) });
    this.highlighted = false;
  }

  /*
   * Selects visible input text depending on where the user clicks
   *
   * @method handleOnClick
   * @param {Object} ev event
   * @return {void}
   */
  handleOnClick = () => {
    // if value is already highlighted then don't re-highlight it
    if (this.highlighted) {
      this.highlighted = false;
      return;
    }

    let input = this._input;
    // only do it if the selection is not within the value
    if ((input.selectionStart === 0) && (input.selectionEnd === 0)) {
      input.setSelectionRange(0, input.value.length);
      this.highlighted = true;
    }
  }

  /*
   * Triggers on key down of the input
   *
   * @method handleKeyDown
   * @param {Object} ev event
   * @return {void}
   */
  handleKeyDown = (ev) => {
    // track the selection start and end
    this.selectionStart = ev.target.selectionStart;
    this.selectionEnd = ev.target.selectionEnd;

    if (this.props.onKeyDown) {
      // we also send the props so more information can be extracted by the action
      this.props.onKeyDown(ev, this.props);
    }
  }

  /**
   * Returns the current value or default value.
   *
   * @method value
   * @return {String}
   */
  get value() {
    return this.props.value || getDefaultValue(this);
  }

  /**
   * A getter that combines props passed down from the input decorator with
   * textbox specific props.
   *
   * @method inputProps
   * @return {Object} props to apply to input field
   */
  get inputProps() {
    let { ...props } = this.props;
    props.className = this.inputClasses;
    props.onChange = this.handleVisibleInputChange;
    props.onClick = this.handleOnClick;
    props.name = null;
    props.onBlur = this.handleBlur;
    props.value = this.state.visibleValue;
    props.onKeyDown = this.handleKeyDown;
    return props;
  }

  /**
   * A getter for hidden input props.
   *
   * @method hiddenInputProps
   * @return {Object} props to apply to hidden field
   */
  get hiddenInputProps() {
    var props = {
      ref: "hidden",
      type: "hidden",
      readOnly: true,
      name: this.props.name
    };

    if (typeof this.props.value !== 'undefined')
      { props.value = this.props.value; }
    else
      { props.defaultValue = this.props.defaultValue; }

    return props;
  }

  /**
   * Uses the mainClasses method provided by the decorator to add additional classes.
   *
   * @method mainClasses
   * @return {String} Main className
   */
  get mainClasses() {
    return 'carbon-decimal';
  }

  /**
   * Uses the inputClasses method provided by the decorator to add additional classes.
   *
   * @method inputClasses
   * @return {String} Input className
   */
  get inputClasses() {
    return 'carbon-decimal__input';
  }

  /**
   * Renders the component.
   *
   * @method render
   * @return {Object} JSX
   */
  render() {
    return (

        { this.labelHTML }
        { this.inputHTML }
        <input { ...this.hiddenInputProps } />
        { this.validationHTML }
        { this.fieldHelpHTML }

    );
  }
}
)));

// Private Methods

/**
 * Returns defaultValue for specified scope,
 *
 * @method getDefaultValue
 * @private
 * @param {Object} scope used to get default value of current scope
 * @return {String} default Value
 */
function getDefaultValue(scope) {
  if (typeof scope.refs.hidden !== 'undefined') {
    return scope.refs.hidden.value;
  } else {
    return scope.props.defaultValue;
  }
}

export default Decimal;
<commit_msg>use backtick instead of single quote<commit_after>import React from 'react';
import I18nHelper from './../../utils/helpers/i18n';
import Input from './../../utils/decorators/input';
import InputLabel from './../../utils/decorators/input-label';
import InputValidation from './../../utils/decorators/input-validation';

/**
 * A decimal widget.
 *
 * == How to use a Decimal in a component:
 *
 * In your file
 *
 *   import Decimal from 'carbon/lib/components/decimal';
 *
 * To render the Decimal:
 *
 *   <Decimal name="myDecimal" />
 *
 * @class Decimal
 * @constructor
 * @decorators {Input,InputLabel,InputValidation}
 */
const Decimal = Input(InputLabel(InputValidation(
class Decimal extends React.Component {

  /**
   * Stores the document - allows us to override it different contexts, such as
   * when running tests.
   *
   * @property _document
   * @type {document}
   */
  _document = document;

  /**
   * Used within the onClick and onBlur method to
   * check if the current visible input value is
   * highlighted
   *
   * @property highlighted
   * @type {Boolean}
   */
  highlighted = false;

  static propTypes = {
    /**
     * Sets the default value of the decimal field
     *
     * @property defaultValue
     * @type {String}
     * @default '0.00'
     */
    defaultValue: React.PropTypes.string,

    /**
     * Sets the default value alignment
     *
     * @property align
     * @type {String}
     * @default 'right'
     */
    align: React.PropTypes.string,

    /**
     * Sets the pricision of the field
     *
     * @property precision
     * @type {Integer}
     * @default 2
     */
    precision: React.PropTypes.number
  }

  static defaultProps = {
    defaultValue: '0.00',
    align: "right",
    precision: 2
  }

  state = {
    /**
     * The formatted value for display
     *
     * @property visibleValue
     * @type {String}
     */
    visibleValue: I18nHelper.formatDecimal(this.value, this.props.precision)
  }

  /**
   * A lifecycle method to update the visible value with a formatted version,
   * only when the field is not the active element.
   *
   * @method componentWillReceiveProps
   * @param {Object} props The new props passed down to the component
   * @return {void}
   */
  componentWillReceiveProps(props) {
    if (this._document.activeElement != this._input) {
      let value = props.value || props.defaultValue;
      this.setState({ visibleValue: I18nHelper.formatDecimal(value, this.props.precision) });
    }
  }

  /**
   * Callback to update the hidden field on change.
   *
   * @method emitOnChangeCallback
   * @param {String} val The unformatted decimal value
   * @return {void}
   */
  emitOnChangeCallback = (val) => {
    let hiddenField = this.refs.hidden;
    hiddenField.value = val;

    this._handleOnChange({ target: hiddenField });
  }

  /**
   * Checks that visibleValue is valid decimal.
   * This is a post-processor applied after the value has been updated.
   *
   * @method isValidDecimal
   * @param {String} value new prop value
   * @return {Boolean} true if a valid decimal
   */
  isValidDecimal = (value, precision) => {
    let del, regex, sep, format = I18nHelper.format();
    del = format.delimiter;
    sep = format.separator;
    regex = precision > 0 ?
        new RegExp(`^[-]?[0-9]*(?:\${del}?[0-9]?)*\${sep}?[0-9]{0,${precision}}$`) :
        new RegExp(`^[-]?[0-9]*(?:\${del}?[0-9]?)*$`);
    return regex.test(value);
  }

  /**
   * Handles Change to visible field
   *
   * @method handleVisibleInputChange
   * @param {Object} ev event
   * @return {void}
   */
  handleVisibleInputChange = (ev) => {
    if (this.isValidDecimal(ev.target.value, this.props.precision)) {
      this.setState({ visibleValue: ev.target.value });
      this.emitOnChangeCallback(I18nHelper.unformatDecimal(ev.target.value));
    } else {
      // reset the value
      ev.target.value = this.state.visibleValue;
      // reset the selection range
      ev.target.setSelectionRange(this.selectionStart, this.selectionEnd);
    }
  }

  /**
   * Updates visible value on blur
   *
   * @method handleBlur
   * @return {void}
   */
  handleBlur = () => {
    this.setState({ visibleValue: I18nHelper.formatDecimal(this.value, this.props.precision) });
    this.highlighted = false;
  }

  /*
   * Selects visible input text depending on where the user clicks
   *
   * @method handleOnClick
   * @param {Object} ev event
   * @return {void}
   */
  handleOnClick = () => {
    // if value is already highlighted then don't re-highlight it
    if (this.highlighted) {
      this.highlighted = false;
      return;
    }

    let input = this._input;
    // only do it if the selection is not within the value
    if ((input.selectionStart === 0) && (input.selectionEnd === 0)) {
      input.setSelectionRange(0, input.value.length);
      this.highlighted = true;
    }
  }

  /*
   * Triggers on key down of the input
   *
   * @method handleKeyDown
   * @param {Object} ev event
   * @return {void}
   */
  handleKeyDown = (ev) => {
    // track the selection start and end
    this.selectionStart = ev.target.selectionStart;
    this.selectionEnd = ev.target.selectionEnd;

    if (this.props.onKeyDown) {
      // we also send the props so more information can be extracted by the action
      this.props.onKeyDown(ev, this.props);
    }
  }

  /**
   * Returns the current value or default value.
   *
   * @method value
   * @return {String}
   */
  get value() {
    return this.props.value || getDefaultValue(this);
  }

  /**
   * A getter that combines props passed down from the input decorator with
   * textbox specific props.
   *
   * @method inputProps
   * @return {Object} props to apply to input field
   */
  get inputProps() {
    let { ...props } = this.props;
    props.className = this.inputClasses;
    props.onChange = this.handleVisibleInputChange;
    props.onClick = this.handleOnClick;
    props.name = null;
    props.onBlur = this.handleBlur;
    props.value = this.state.visibleValue;
    props.onKeyDown = this.handleKeyDown;
    return props;
  }

  /**
   * A getter for hidden input props.
   *
   * @method hiddenInputProps
   * @return {Object} props to apply to hidden field
   */
  get hiddenInputProps() {
    var props = {
      ref: "hidden",
      type: "hidden",
      readOnly: true,
      name: this.props.name
    };

    if (typeof this.props.value !== 'undefined')
      { props.value = this.props.value; }
    else
      { props.defaultValue = this.props.defaultValue; }

    return props;
  }

  /**
   * Uses the mainClasses method provided by the decorator to add additional classes.
   *
   * @method mainClasses
   * @return {String} Main className
   */
  get mainClasses() {
    return 'carbon-decimal';
  }

  /**
   * Uses the inputClasses method provided by the decorator to add additional classes.
   *
   * @method inputClasses
   * @return {String} Input className
   */
  get inputClasses() {
    return 'carbon-decimal__input';
  }

  /**
   * Renders the component.
   *
   * @method render
   * @return {Object} JSX
   */
  render() {
    return (

        { this.labelHTML }
        { this.inputHTML }
        <input { ...this.hiddenInputProps } />
        { this.validationHTML }
        { this.fieldHelpHTML }

    );
  }
}
)));

// Private Methods

/**
 * Returns defaultValue for specified scope,
 *
 * @method getDefaultValue
 * @private
 * @param {Object} scope used to get default value of current scope
 * @return {String} default Value
 */
function getDefaultValue(scope) {
  if (typeof scope.refs.hidden !== 'undefined') {
    return scope.refs.hidden.value;
  } else {
    return scope.props.defaultValue;
  }
}

export default Decimal;
<|endoftext|>