<commit_before>/*
 * easyCombo v0.2 for easy.js
 * 2013-07-01
 */
var rExistId = /define\(\s*['"][^\[f'"\{]+['"]\s*,?/,
    rProtocol = /^(http(?:s)?\:\/\/|file\:.+\:\/)/,
    rModId = /([^\/?]+?)(\.(?:js|css))?(\?.*)?$/,
    rRightEnd = /,?\s*(function\s*\(.*|\{.*)/,
    rPullDeps = /((?:define|E\.use)\(.*)/g,
    rDeps = /(define\([^\[]*\[)([^\]]+)/,
    rDefine = /define\(/,

    fs = require( 'fs' ),
    depsCache = {},

    define = function( name, deps ){
        if( Array.isArray(name) ){
            deps = name;
        }

        return deps;
    },

    // 将模块标识(相对路径)和基础路径合并成新的真正的模块路径(不含模块的文件名)
    mergePath = function( id, url ){
        var isHttp = url.slice( 0, 4 ) === 'http',
            protocol = '',
            domain = '',
            i = 0,
            protocol, isHttp, urlDir, idDir, dirPath, len, dir, matches;

        matches = url.match( rProtocol );

        if( matches ){
            protocol = matches[1];
            url = url.slice( protocol.length );
        }

        // HTTP协议的路径含有域名
        if( isHttp ){
            domain = url.slice( 0, url.indexOf('/') + 1 );
            url = url.slice( domain.length );
        }

        // 组装基础路径的目录数组
        urlDir = url.split( '/' );
        urlDir.pop();

        // 组装模块标识的目录数组
        idDir = id.split( '/' );
        idDir.pop();
        len = idDir.length;

        for( ; i < len; i++ ){
            dir = idDir[i];
            // 模块标识的目录数组中含有../则基础路径的目录数组删除最后一个目录
            // 否则直接将模块标识的目录数组的元素添加到基础路径的目录数组中
            if( dir === '..' ){
                urlDir.pop();
            }
            else if( dir !== '.' ){
                urlDir.push( dir );
            }
        }

        // 基础路径的目录数组转换成目录字符串
        dirPath = urlDir.join( '/' );
        // 无目录的情况不用加斜杠
        dirPath = dirPath === '' ? '' : dirPath + '/';
        return protocol + domain + dirPath;
    },

    // 解析模块标识，返回模块名和模块路径
    parseModId = function( id, url ){
        var isAbsoluteId = rProtocol.test( id ),
            result = id.match( rModId ),
            modName = result[1],
            suffix = result[2] || '.js',
            search = result[3] || '',
            baseUrl, modUrl;

        // 模块标识为绝对路径时，标识就是基础路径
        if( isAbsoluteId ){
            url = id;
            id = '';
        }

        baseUrl = mergePath( id, url );
        modUrl = baseUrl + modName + suffix + search;
        return [ modName, modUrl ];
    },

    // 分析模块的依赖，将依赖模块的模块标识组成一个数组以便合并
    parseDeps = function( key, baseUrl, ids, encoding ){
        var cache = depsCache[ key ],
            deps = [];

        ids.forEach(function( id ){
            var result = parseModId( id, baseUrl ),
                name = result[0],
                url = result[1],
                content, literals;

            // 读取文件
            try{
                content = fs.readFileSync( url, encoding );
            }
            catch( error ){
                console.log( 'Read file ' + error );
                return;
            }

            // 将define(), use()用正则提取出来
            literals = content.match( rPullDeps );

            literals.forEach(function( literal ){
                var arr;
                // define('hello', ['hello1'], function(){  =>  define('hello', ['hello1'])
                // use('hello', function(){  =>  use('hello')
                literal = literal.replace(rRightEnd, ')');

                // 然后用eval去执行处理过的define和use获取到依赖模块的标识
                arr = eval( literal );

                if( arr && arr.length ){
                    deps = deps.concat( arr );
                }
            });
        });

        if( deps.length ){
            cache.ids = deps.concat( cache.ids );

            // 递归调用直到所有的依赖模块都添加完
            parseDeps( key, baseUrl, deps, encoding );
        }
    },

    formatDeps = function( _, define, deps ){
        var arr = deps.split( ',' ),
            len = arr.length,
            i = 0,
            item, index;

        for( ; i < len; i++ ){
            item = arr[i];
            item = item.replace( /['"]/g, '' ).trim();
            index = item.lastIndexOf( '/' );
            arr[i] = ~index ? item.slice( index + 1 ) : item;
        }

        return define + "'" + arr.join("','") + "'";
    },

    // 合并内容
    comboContent = function( key, baseUrl, encoding, format ){
        var cache = depsCache[ key ],
            unique = cache.unique,
            ids = cache.ids;

        ids.forEach(function( id ){
            var result = parseModId( id, baseUrl ),
                name = result[0],
                url = result[1],
                content = fs.readFileSync( url, encoding );

            // 非use()的情况下防止重复合并
            if( !~content.indexOf('use(') ){
                if( unique[name] ){
                    return;
                }

                unique[ name ] = true;
            }

            // utf-8 编码格式的文件可能会有 BOM 头，需要去掉
            if( encoding === 'UTF-8' && content.charCodeAt(0) === 0xFEFF ){
                content = content.slice( 1 );
            }

            // 格式化
            if( typeof format === 'function' ){
                content = format( content );
            }

            // 为匿名模块添加模块名
            if( !rExistId.test(content) ){
                content = content.replace( rDefine, "define('" + name + "'," );
            }

            // 格式化依赖模块列表 ['../hello5'] => ['hello5']
            content = content.replace( rDeps, formatDeps );

            // 合并
            cache.contents += content + '\n';
            console.log( 'Combo the [' + url + '] success.' );
        });
    },

    // 写入文件
    writeFile = function( key, mod, uglifyUrl ){
        var output = mod.output,
            contents = depsCache[ key ].contents,
            uglify, jsp, pro, ast;

        // 压缩文件
        if( uglifyUrl ){
            uglify = require( uglifyUrl );
            jsp = uglify.parser;
            pro = uglify.uglify;

            ast = jsp.parse( contents );
            ast = pro.ast_mangle( ast );
            ast = pro.ast_squeeze( ast );
            contents = pro.gen_code( ast );
        }

        // 合并好文本内容后的回调
        if( typeof complete === 'function' ){
            contents = complete( contents );
        }

        // 写入文件
        try{
            fs.writeFileSync( output, contents, mod.encoding );
        }
        catch( error ){
            console.log( 'Output file ' + error );
            return;
        }

        console.log( 'Output the [' + output + '] success.' );
        delete depsCache[ key ];
    };

var E = easyJS = {
    use : function( ids ){
        return typeof ids === 'string' ? [ ids ] : ids;
    }
};

var easyCombo = function( options ){
    var modules = options.modules,
        baseUrl = options.baseUrl;

    modules.forEach(function( mod ){
        var encoding = mod.encoding = ( mod.encoding || 'UTF-8' ).toUpperCase(),
            randomKey = ( +new Date() + '' ) + ( Math.random() + '' ).slice( -8 );

        depsCache[ randomKey ] = {
            ids : [],
            unique : {},
            contents : ''
        };

        mod.input.forEach(function( id ){
            depsCache[ randomKey ].ids.push( baseUrl + id );
            parseDeps( randomKey, baseUrl, [id], encoding );
        });

        comboContent( randomKey, baseUrl, encoding, mod.format );
        writeFile( randomKey, mod, options.uglifyUrl );
    });
};

exports.easyCombo = easyCombo;<commit_msg>合并工具的正则优化<commit_after>/*
 * easyCombo v0.2 for easy.js
 * 2013-07-01
 */
var rExistId = /define\(\s*['"][^\[f'"\{]+['"]\s*,?/,
    rProtocol = /^(http(?:s)?\:\/\/|file\:.+\:\/)/,
    rModId = /([^\/?]+?)(\.(?:js|css))?(\?.*)?$/,
    rRightEnd = /,?\s*(function\s*\(.*|\{.*)/,
    rPullDeps = /((?:define|E\.use)\(.*)/g,
    rDeps = /(define\([^\[f\{]*\[)([^\]]+)/,
    rDefine = /define\(/,

    fs = require( 'fs' ),
    depsCache = {},

    define = function( name, deps ){
        if( Array.isArray(name) ){
            deps = name;
        }

        return deps;
    },

    // 将模块标识(相对路径)和基础路径合并成新的真正的模块路径(不含模块的文件名)
    mergePath = function( id, url ){
        var isHttp = url.slice( 0, 4 ) === 'http',
            protocol = '',
            domain = '',
            i = 0,
            protocol, isHttp, urlDir, idDir, dirPath, len, dir, matches;

        matches = url.match( rProtocol );

        if( matches ){
            protocol = matches[1];
            url = url.slice( protocol.length );
        }

        // HTTP协议的路径含有域名
        if( isHttp ){
            domain = url.slice( 0, url.indexOf('/') + 1 );
            url = url.slice( domain.length );
        }

        // 组装基础路径的目录数组
        urlDir = url.split( '/' );
        urlDir.pop();

        // 组装模块标识的目录数组
        idDir = id.split( '/' );
        idDir.pop();
        len = idDir.length;

        for( ; i < len; i++ ){
            dir = idDir[i];
            // 模块标识的目录数组中含有../则基础路径的目录数组删除最后一个目录
            // 否则直接将模块标识的目录数组的元素添加到基础路径的目录数组中
            if( dir === '..' ){
                urlDir.pop();
            }
            else if( dir !== '.' ){
                urlDir.push( dir );
            }
        }

        // 基础路径的目录数组转换成目录字符串
        dirPath = urlDir.join( '/' );
        // 无目录的情况不用加斜杠
        dirPath = dirPath === '' ? '' : dirPath + '/';
        return protocol + domain + dirPath;
    },

    // 解析模块标识，返回模块名和模块路径
    parseModId = function( id, url ){
        var isAbsoluteId = rProtocol.test( id ),
            result = id.match( rModId ),
            modName = result[1],
            suffix = result[2] || '.js',
            search = result[3] || '',
            baseUrl, modUrl;

        // 模块标识为绝对路径时，标识就是基础路径
        if( isAbsoluteId ){
            url = id;
            id = '';
        }

        baseUrl = mergePath( id, url );
        modUrl = baseUrl + modName + suffix + search;
        return [ modName, modUrl ];
    },

    // 分析模块的依赖，将依赖模块的模块标识组成一个数组以便合并
    parseDeps = function( key, baseUrl, ids, encoding ){
        var cache = depsCache[ key ],
            deps = [];

        ids.forEach(function( id ){
            var result = parseModId( id, baseUrl ),
                name = result[0],
                url = result[1],
                content, literals;

            // 读取文件
            try{
                content = fs.readFileSync( url, encoding );
            }
            catch( error ){
                console.log( 'Read file ' + error );
                return;
            }

            // 将define(), use()用正则提取出来
            literals = content.match( rPullDeps );

            literals.forEach(function( literal ){
                var arr;
                // define('hello', ['hello1'], function(){  =>  define('hello', ['hello1'])
                // use('hello', function(){  =>  use('hello')
                literal = literal.replace(rRightEnd, ')');

                // 然后用eval去执行处理过的define和use获取到依赖模块的标识
                arr = eval( literal );

                if( arr && arr.length ){
                    deps = deps.concat( arr );
                }
            });
        });

        if( deps.length ){
            cache.ids = deps.concat( cache.ids );

            // 递归调用直到所有的依赖模块都添加完
            parseDeps( key, baseUrl, deps, encoding );
        }
    },

    formatDeps = function( _, define, deps ){
        var arr = deps.split( ',' ),
            len = arr.length,
            i = 0,
            item, index;

        for( ; i < len; i++ ){
            item = arr[i];
            item = item.replace( /['"]/g, '' ).trim();
            index = item.lastIndexOf( '/' );
            arr[i] = ~index ? item.slice( index + 1 ) : item;
        }

        return define + "'" + arr.join("','") + "'";
    },

    // 合并内容
    comboContent = function( key, baseUrl, encoding, format ){
        var cache = depsCache[ key ],
            unique = cache.unique,
            ids = cache.ids;

        ids.forEach(function( id ){
            var result = parseModId( id, baseUrl ),
                name = result[0],
                url = result[1],
                content = fs.readFileSync( url, encoding );

            // 非use()的情况下防止重复合并
            if( !~content.indexOf('use(') ){
                if( unique[name] ){
                    return;
                }

                unique[ name ] = true;
            }

            // utf-8 编码格式的文件可能会有 BOM 头，需要去掉
            if( encoding === 'UTF-8' && content.charCodeAt(0) === 0xFEFF ){
                content = content.slice( 1 );
            }

            // 格式化
            if( typeof format === 'function' ){
                content = format( content );
            }

            // 为匿名模块添加模块名
            if( !rExistId.test(content) ){
                content = content.replace( rDefine, "define('" + name + "'," );
            }

            // 格式化依赖模块列表 ['../hello5'] => ['hello5']
            content = content.replace( rDeps, formatDeps );

            // 合并
            cache.contents += content + '\n';
            console.log( 'Combo the [' + url + '] success.' );
        });
    },

    // 写入文件
    writeFile = function( key, mod, uglifyUrl ){
        var output = mod.output,
            contents = depsCache[ key ].contents,
            uglify, jsp, pro, ast;

        // 压缩文件
        if( uglifyUrl ){
            uglify = require( uglifyUrl );
            jsp = uglify.parser;
            pro = uglify.uglify;

            ast = jsp.parse( contents );
            ast = pro.ast_mangle( ast );
            ast = pro.ast_squeeze( ast );
            contents = pro.gen_code( ast );
        }

        // 合并好文本内容后的回调
        if( typeof complete === 'function' ){
            contents = complete( contents );
        }

        // 写入文件
        try{
            fs.writeFileSync( output, contents, mod.encoding );
        }
        catch( error ){
            console.log( 'Output file ' + error );
            return;
        }

        console.log( 'Output the [' + output + '] success.' );
        delete depsCache[ key ];
    };

var E = easyJS = {
    use : function( ids ){
        return typeof ids === 'string' ? [ ids ] : ids;
    }
};

var easyCombo = function( options ){
    var modules = options.modules,
        baseUrl = options.baseUrl;

    modules.forEach(function( mod ){
        var encoding = mod.encoding = ( mod.encoding || 'UTF-8' ).toUpperCase(),
            randomKey = ( +new Date() + '' ) + ( Math.random() + '' ).slice( -8 );

        depsCache[ randomKey ] = {
            ids : [],
            unique : {},
            contents : ''
        };

        mod.input.forEach(function( id ){
            depsCache[ randomKey ].ids.push( baseUrl + id );
            parseDeps( randomKey, baseUrl, [id], encoding );
        });

        comboContent( randomKey, baseUrl, encoding, mod.format );
        writeFile( randomKey, mod, options.uglifyUrl );
    });
};

exports.easyCombo = easyCombo;<|endoftext|>


<commit_before>var values = require('../object/values')
var make = require('./_make')
var makeIterator = require('../function/makeIterator_')

// Get maximum value inside collection

function arrMax (arr, fn, thisObj) {
  if (arr == null || !arr.length) {
    return Infinity
  }

  if (arr.length && !fn) {
    return Math.max.apply(Math, arr)
  }

  fn = makeIterator(fn, thisObj)
  var compare = -Infinity
  var value
  var temp
  var result

  var i = -1
  var len = arr.length

  while (i++ < len) {
    value = arr[i]
    temp = fn(value, i, arr)

    if (temp > compare) {
      compare = temp
      result = value
    }
  }

  return result
}

function objMax (obj, fn) {
  return arrMax(values(obj), fn)
}

module.exports = make(arrMax, objMax)
<commit_msg>bug fix collection/max — incorrect iterator i++ —> ++i<commit_after>var values = require('../object/values')
var make = require('./_make')
var makeIterator = require('../function/makeIterator_')

// Get maximum value inside collection

function arrMax (arr, fn, thisObj) {
  if (arr == null || !arr.length) {
    return Infinity
  }

  if (arr.length && !fn) {
    return Math.max.apply(Math, arr)
  }

  fn = makeIterator(fn, thisObj)
  var compare = -Infinity
  var value
  var temp
  var result

  var i = -1
  var len = arr.length

  while (++i < len) {
    value = arr[i]
    temp = fn(value, i, arr)

    if (temp > compare) {
      compare = temp
      result = value
    }
  }

  return result
}

function objMax (obj, fn) {
  return arrMax(values(obj), fn)
}

module.exports = make(arrMax, objMax)
<|endoftext|>


<commit_before>/* Gratefully copied from https://github.com/brentvatne/react-native-animated-demo-tinder */
'use strict';

import React, { StyleSheet, Text, View, Animated, Component, PanResponder, Image} from 'react-native';
import clamp from 'clamp';

import Defaults from './Defaults.js';

var SWIPE_THRESHOLD = 120;

class SwipeCards extends Component {
  constructor(props) {
    super(props);

    this.state = {
      pan: new Animated.ValueXY(),
      enter: new Animated.Value(0.5),
      card: this.props.cards[0],
    }
  }

  _goToNextCard() {
    let currentCardIdx = this.props.cards.indexOf(this.state.card);
    let newIdx = currentCardIdx + 1;

    // Checks to see if last card.
    // If props.loop=true, will start again from the first card.
    let card = newIdx > this.props.cards.length - 1
      ? this.props.loop ? this.props.cards[0] : null
      : this.props.cards[newIdx];

    this.setState({
      card: card
    });
  }

  componentDidMount() {
    this._animateEntrance();
  }

  _animateEntrance() {
    Animated.spring(
      this.state.enter,
      { toValue: 1, friction: 8 }
    ).start();
  }

  componentWillMount() {
    this._panResponder = PanResponder.create({
      onMoveShouldSetResponderCapture: () => true,
      onMoveShouldSetPanResponderCapture: () => true,

      onPanResponderGrant: (e, gestureState) => {
        this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
        this.state.pan.setValue({x: 0, y: 0});
      },

      onPanResponderMove: Animated.event([
        null, {dx: this.state.pan.x, dy: this.state.pan.y},
      ]),

      onPanResponderRelease: (e, {vx, vy}) => {
        this.state.pan.flattenOffset();
        var velocity;

        if (vx >= 0) {
          velocity = clamp(vx, 3, 5);
        } else if (vx < 0) {
          velocity = clamp(vx * -1, 3, 5) * -1;
        }

        if (Math.abs(this.state.pan.x._value) > SWIPE_THRESHOLD) {

          this.state.pan.x._value > 0
            ? this.props.handleYup(this.state.card)
            : this.props.handleNope(this.state.card)

          this.props.cardRemoved
            ? this.props.cardRemoved(this.props.cards.indexOf(this.state.card))
            : null

          Animated.decay(this.state.pan, {
            velocity: {x: velocity, y: vy},
            deceleration: 0.98
          }).start(this._resetState.bind(this))
        } else {
          Animated.spring(this.state.pan, {
            toValue: {x: 0, y: 0},
            friction: 4
          }).start()
        }
      }
    })
  }

  _resetState() {
    this.state.pan.setValue({x: 0, y: 0});
    this.state.enter.setValue(0);
    this._goToNextCard();
    this._animateEntrance();
  }

  renderNoMoreCards() {
    if (this.props.renderNoMoreCards)
      return this.props.renderNoMoreCards();

    return (
      <Defaults.NoMoreCards />
    )
  }

  renderCard(cardData) {
    return this.props.renderCard(cardData)
  }

  render() {
    let { pan, enter, } = this.state;

    let [translateX, translateY] = [pan.x, pan.y];

    let rotate = pan.x.interpolate({inputRange: [-200, 0, 200], outputRange: ["-30deg", "0deg", "30deg"]});
    let opacity = pan.x.interpolate({inputRange: [-200, 0, 200], outputRange: [0.5, 1, 0.5]});
    let scale = enter;

    let animatedCardstyles = {transform: [{translateX}, {translateY}, {rotate}, {scale}], opacity};

    let yupOpacity = pan.x.interpolate({inputRange: [0, 150], outputRange: [0, 1]});
    let yupScale = pan.x.interpolate({inputRange: [0, 150], outputRange: [0.5, 1], extrapolate: 'clamp'});
    let animatedYupStyles = {transform: [{scale: yupScale}], opacity: yupOpacity}

    let nopeOpacity = pan.x.interpolate({inputRange: [-150, 0], outputRange: [1, 0]});
    let nopeScale = pan.x.interpolate({inputRange: [-150, 0], outputRange: [1, 0.5], extrapolate: 'clamp'});
    let animatedNopeStyles = {transform: [{scale: nopeScale}], opacity: nopeOpacity}

    return (
      <View style={styles.container}>
        { this.state.card
            ? (
            <Animated.View style={[styles.card, animatedCardstyles]} {...this._panResponder.panHandlers}>
              {this.renderCard(this.state.card)}
            </Animated.View>
            )
            : this.renderNoMoreCards() }

        { this.props.showNope
          ? (
            <Animated.View style={[styles.nope, animatedNopeStyles]}>
              <Text style={styles.nopeText}>Nope!</Text>
            </Animated.View>
            )
          : null
        }

        { this.props.showYup
          ? (
            <Animated.View style={[styles.yup, animatedYupStyles]}>
              <Text style={styles.yupText}>Yup!</Text>
            </Animated.View>
          )
          : null }

      </View>
    );
  }
}

SwipeCards.propTypes = {
  cards: React.PropTypes.array,
  renderCards: React.PropTypes.func,
  loop: React.PropTypes.bool,
  renderNoMoreCards: React.PropTypes.func,
  showYup: React.PropTypes.bool,
  showNope: React.PropTypes.bool,
  handleYup: React.PropTypes.func,
  handleNope: React.PropTypes.func
};

SwipeCards.defaultProps = {
  loop: false,
  showYup: true,
  showNope: true
};

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  yup: {
    borderColor: 'green',
    borderWidth: 2,
    position: 'absolute',
    padding: 20,
    bottom: 20,
    borderRadius: 5,
    right: 20,
  },
  yupText: {
    fontSize: 16,
    color: 'green',
  },
  nope: {
    borderColor: 'red',
    borderWidth: 2,
    position: 'absolute',
    bottom: 20,
    padding: 20,
    borderRadius: 5,
    left: 20,
  },
  nopeText: {
    fontSize: 16,
    color: 'red',
  }
});

export default SwipeCards
<commit_msg>added renderNope and renderYup handlers<commit_after>/* Gratefully copied from https://github.com/brentvatne/react-native-animated-demo-tinder */
'use strict';

import React, { StyleSheet, Text, View, Animated, Component, PanResponder, Image} from 'react-native';
import clamp from 'clamp';

import Defaults from './Defaults.js';

var SWIPE_THRESHOLD = 120;

class SwipeCards extends Component {
  constructor(props) {
    super(props);

    this.state = {
      pan: new Animated.ValueXY(),
      enter: new Animated.Value(0.5),
      card: this.props.cards[0],
    }
  }

  _goToNextCard() {
    let currentCardIdx = this.props.cards.indexOf(this.state.card);
    let newIdx = currentCardIdx + 1;

    // Checks to see if last card.
    // If props.loop=true, will start again from the first card.
    let card = newIdx > this.props.cards.length - 1
      ? this.props.loop ? this.props.cards[0] : null
      : this.props.cards[newIdx];

    this.setState({
      card: card
    });
  }

  componentDidMount() {
    this._animateEntrance();
  }

  _animateEntrance() {
    Animated.spring(
      this.state.enter,
      { toValue: 1, friction: 8 }
    ).start();
  }

  componentWillMount() {
    this._panResponder = PanResponder.create({
      onMoveShouldSetResponderCapture: () => true,
      onMoveShouldSetPanResponderCapture: () => true,

      onPanResponderGrant: (e, gestureState) => {
        this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
        this.state.pan.setValue({x: 0, y: 0});
      },

      onPanResponderMove: Animated.event([
        null, {dx: this.state.pan.x, dy: this.state.pan.y},
      ]),

      onPanResponderRelease: (e, {vx, vy}) => {
        this.state.pan.flattenOffset();
        var velocity;

        if (vx >= 0) {
          velocity = clamp(vx, 3, 5);
        } else if (vx < 0) {
          velocity = clamp(vx * -1, 3, 5) * -1;
        }

        if (Math.abs(this.state.pan.x._value) > SWIPE_THRESHOLD) {

          this.state.pan.x._value > 0
            ? this.props.handleYup(this.state.card)
            : this.props.handleNope(this.state.card)

          this.props.cardRemoved
            ? this.props.cardRemoved(this.props.cards.indexOf(this.state.card))
            : null

          Animated.decay(this.state.pan, {
            velocity: {x: velocity, y: vy},
            deceleration: 0.98
          }).start(this._resetState.bind(this))
        } else {
          Animated.spring(this.state.pan, {
            toValue: {x: 0, y: 0},
            friction: 4
          }).start()
        }
      }
    })
  }

  _resetState() {
    this.state.pan.setValue({x: 0, y: 0});
    this.state.enter.setValue(0);
    this._goToNextCard();
    this._animateEntrance();
  }

  renderNoMoreCards() {
    if (this.props.renderNoMoreCards)
      return this.props.renderNoMoreCards();

    return (
      <Defaults.NoMoreCards />
    )
  }

  renderCard(cardData) {
    return this.props.renderCard(cardData)
  }

  render() {
    let { pan, enter, } = this.state;

    let [translateX, translateY] = [pan.x, pan.y];

    let rotate = pan.x.interpolate({inputRange: [-200, 0, 200], outputRange: ["-30deg", "0deg", "30deg"]});
    let opacity = pan.x.interpolate({inputRange: [-200, 0, 200], outputRange: [0.5, 1, 0.5]});
    let scale = enter;

    let animatedCardstyles = {transform: [{translateX}, {translateY}, {rotate}, {scale}], opacity};

    let yupOpacity = pan.x.interpolate({inputRange: [0, 150], outputRange: [0, 1]});
    let yupScale = pan.x.interpolate({inputRange: [0, 150], outputRange: [0.5, 1], extrapolate: 'clamp'});
    let animatedYupStyles = {transform: [{scale: yupScale}], opacity: yupOpacity}

    let nopeOpacity = pan.x.interpolate({inputRange: [-150, 0], outputRange: [1, 0]});
    let nopeScale = pan.x.interpolate({inputRange: [-150, 0], outputRange: [1, 0.5], extrapolate: 'clamp'});
    let animatedNopeStyles = {transform: [{scale: nopeScale}], opacity: nopeOpacity}

    return (
      <View style={styles.container}>
        { this.state.card
            ? (
            <Animated.View style={[styles.card, animatedCardstyles]} {...this._panResponder.panHandlers}>
              {this.renderCard(this.state.card)}
            </Animated.View>
            )
            : this.renderNoMoreCards() }

        { this.props.renderNope
          ? this.props.renderNope(pan)
          : (
              this.props.showNope
              ? (
                <Animated.View style={[styles.nope, animatedNopeStyles]}>
                  <Text style={styles.nopeText}>Nope!</Text>
                </Animated.View>
                )
              : null
            )
        }

        { this.props.renderYup
          ? this.props.renderYup(pan)
          : (
              this.props.showYup
              ? (
                <Animated.View style={[styles.yup, animatedYupStyles]}>
                  <Text style={styles.yupText}>Yup!</Text>
                </Animated.View>
              )
              : null
            )
        }

      </View>
    );
  }
}

SwipeCards.propTypes = {
  cards: React.PropTypes.array,
  renderCards: React.PropTypes.func,
  loop: React.PropTypes.bool,
  renderNoMoreCards: React.PropTypes.func,
  showYup: React.PropTypes.bool,
  showNope: React.PropTypes.bool,
  handleYup: React.PropTypes.func,
  handleNope: React.PropTypes.func
};

SwipeCards.defaultProps = {
  loop: false,
  showYup: true,
  showNope: true
};

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  yup: {
    borderColor: 'green',
    borderWidth: 2,
    position: 'absolute',
    padding: 20,
    bottom: 20,
    borderRadius: 5,
    right: 20,
  },
  yupText: {
    fontSize: 16,
    color: 'green',
  },
  nope: {
    borderColor: 'red',
    borderWidth: 2,
    position: 'absolute',
    bottom: 20,
    padding: 20,
    borderRadius: 5,
    left: 20,
  },
  nopeText: {
    fontSize: 16,
    color: 'red',
  }
});

export default SwipeCards
<|endoftext|>


<commit_before>'use strict';

import ActionDispatcher from '../actions/ActionDispatcher';

/**
 * The UIComponent class is the base class for all atomic view components,
 * atoms, molecules and organisms.
 *
 */

class UIComponent extends ActionDispatcher {

  render() {

  }
}

export default UIComponent;
<commit_msg>feat(UIComponent): read-only name property<commit_after>'use strict';

import Action from '../actions/Action';
import ActionDispatcher from '../actions/ActionDispatcher';

/**
 * The UIComponent class is the base class for all atomic view components,
 * atoms, molecules and organisms.
 *
 */

class UIComponent extends ActionDispatcher {
  constructor(name) {
    super();

    /**
     * Define read-only 'name' property
     *
     */

    Object.defineProperty(this, 'name', {
      value: name,
      writable: false
    });

    this.dispatchAction(Action.ADDED);
  }

  /**
   * Render
   *
   */
  render() {

  }
}

export default UIComponent;
<|endoftext|>


<commit_before>
/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
----------------------------------------------------------------------------------------------------------------------*/

var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {

	numbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal
	bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid

	rowEls: null, // set of fake row elements
	cellEls: null, // set of whole-day elements comprising the row's background
	helperEls: null, // set of cell skeleton elements for rendering the mock event "helper"

	rowCoordCache: null,
	colCoordCache: null,

	// Renders the rows and columns into the component's `this.el`, which should already be assigned.
	// isRigid determins whether the individual rows should ignore the contents and be a constant height.
	// Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
	renderDates: function(isRigid) {
		var view = this.view;
		var rowCnt = this.rowCnt;
		var colCnt = this.colCnt;
		var html = '';
		var row;
		var col;

		for (row = 0; row < rowCnt; row++) {
			html += this.renderDayRowHtml(row, isRigid);
		}
		this.el.html(html);

		this.rowEls = this.el.find('.fc-row');
		this.cellEls = this.el.find('.fc-day');

		this.rowCoordCache = new CoordCache({
			els: this.rowEls,
			isVertical: true
		});
		this.colCoordCache = new CoordCache({
			els: this.cellEls.slice(0, this.colCnt), // only the first row
			isHorizontal: true
		});

		// trigger dayRender with each cell's element
		for (row = 0; row < rowCnt; row++) {
			for (col = 0; col < colCnt; col++) {
				view.trigger(
					'dayRender',
					null,
					this.getCellDate(row, col),
					this.getCellEl(row, col)
				);
			}
		}
	},

	unrenderDates: function() {
		this.removeSegPopover();
	},

	renderBusinessHours: function() {
		var segs = this.buildBusinessHourSegs(true); // wholeDay=true
		this.renderFill('businessHours', segs, 'bgevent');
	},

	unrenderBusinessHours: function() {
		this.unrenderFill('businessHours');
	},

	// Generates the HTML for a single row, which is a div that wraps a table.
	// `row` is the row number.
	renderDayRowHtml: function(row, isRigid) {
		var view = this.view;
		var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ];

		if (isRigid) {
			classes.push('fc-rigid');
		}

		return '' +
			'
' +
				'
' +
					'
' +
						this.renderBgTrHtml(row) +
					'
' +
				'
' +
				'
' +
					'
' +
						(this.numbersVisible ?
							'
' +
								this.renderNumberTrHtml(row) +
							'
' :
							''
							) +
					'
' +
				'
' +
			'
';
	},

	/* Grid Number Rendering
	------------------------------------------------------------------------------------------------------------------*/

	renderNumberTrHtml: function(row) {
		return '' +
			'
' +
				(this.isRTL ? '' : this.renderNumberIntroHtml(row)) +
				this.renderNumberCellsHtml(row) +
				(this.isRTL ? this.renderNumberIntroHtml(row) : '') +
			'
';
	},

	renderNumberIntroHtml: function(row) {
		return this.renderIntroHtml();
	},

	renderNumberCellsHtml: function(row) {
		var htmls = [];
		var col, date;

		for (col = 0; col < this.colCnt; col++) {
			date = this.getCellDate(row, col);
			htmls.push(this.renderNumberCellHtml(date));
		}

		return htmls.join('');
	},

	// Generates the HTML for the s of the "number" row in the DayGrid's content skeleton.
	// The number row will only exist if either day numbers or week numbers are turned on.
	renderNumberCellHtml: function(date) {
		var html = '';
		var classes;
		var weekCalcFirstDoW;

		if (!this.view.dayNumbersVisible && !this.view.cellWeekNumbersVisible) {
			// no numbers in day cell (week number must be along the side)
			return ''; //  will create an empty space above events :(
		}

		classes = this.getDayClasses(date);
		classes.unshift('fc-day-top');

		if (this.view.cellWeekNumbersVisible) {
			// To determine the day of week number change under ISO, we cannot
			// rely on moment.js methods such as firstDayOfWeek() or weekday(),
			// because they rely on the locale's dow (possibly overridden by
			// our firstDay option), which may not be Monday. We cannot change
			// dow, because that would affect the calendar start day as well.
			if ((date._locale || date._lang)._fullCalendar_weekCalc === 'ISO') {
				weekCalcFirstDoW = 1;  // Monday by ISO 8601 definition
			}
			else {
				weekCalcFirstDoW = (date._locale || date._lang).firstDayOfWeek();
			}
		}

		html += '';

		if (this.view.cellWeekNumbersVisible && (date.day() == weekCalcFirstDoW)) {
			html += this.view.buildGotoAnchorHtml(
				{ date: date, type: 'week' },
				{ 'class': 'fc-week-number' },
				date.format('w') // inner HTML
			);
		}

		if (this.view.dayNumbersVisible) {
			html += this.view.buildGotoAnchorHtml(
				date,
				{ 'class': 'fc-day-number' },
				date.date() // inner HTML
			);
		}

		html += '	';

		return html;
	},

	/* Options
	------------------------------------------------------------------------------------------------------------------*/

	// Computes a default event time formatting string if `timeFormat` is not explicitly defined
	computeEventTimeFormat: function() {
		return this.view.opt('extraSmallTimeFormat'); // like "6p" or "6:30p"
	},

	// Computes a default `displayEventEnd` value if one is not expliclty defined
	computeDisplayEventEnd: function() {
		return this.colCnt == 1; // we'll likely have space if there's only one day
	},

	/* Dates
	------------------------------------------------------------------------------------------------------------------*/

	rangeUpdated: function() {
		this.updateDayTable();
	},

	// Slices up the given span (unzoned start/end with other misc data) into an array of segments
	spanToSegs: function(span) {
		var segs = this.sliceRangeByRow(span);
		var i, seg;

		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			if (this.isRTL) {
				seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex;
				seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex;
			}
			else {
				seg.leftCol = seg.firstRowDayIndex;
				seg.rightCol = seg.lastRowDayIndex;
			}
		}

		return segs;
	},

	/* Hit System
	------------------------------------------------------------------------------------------------------------------*/

	prepareHits: function() {
		this.colCoordCache.build();
		this.rowCoordCache.build();
		this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding; // hack
	},

	releaseHits: function() {
		this.colCoordCache.clear();
		this.rowCoordCache.clear();
	},

	queryHit: function(leftOffset, topOffset) {
		if (this.colCoordCache.isLeftInBounds(leftOffset) && this.rowCoordCache.isTopInBounds(topOffset)) {
			var col = this.colCoordCache.getHorizontalIndex(leftOffset);
			var row = this.rowCoordCache.getVerticalIndex(topOffset);

			if (row != null && col != null) {
				return this.getCellHit(row, col);
			}
		}
	},

	getHitSpan: function(hit) {
		return this.getCellRange(hit.row, hit.col);
	},

	getHitEl: function(hit) {
		return this.getCellEl(hit.row, hit.col);
	},

	/* Cell System
	------------------------------------------------------------------------------------------------------------------*/
	// FYI: the first column is the leftmost column, regardless of date

	getCellHit: function(row, col) {
		return {
			row: row,
			col: col,
			component: this, // needed unfortunately :(
			left: this.colCoordCache.getLeftOffset(col),
			right: this.colCoordCache.getRightOffset(col),
			top: this.rowCoordCache.getTopOffset(row),
			bottom: this.rowCoordCache.getBottomOffset(row)
		};
	},

	getCellEl: function(row, col) {
		return this.cellEls.eq(row * this.colCnt + col);
	},

	/* Event Drag Visualization
	------------------------------------------------------------------------------------------------------------------*/
	// TODO: move to DayGrid.event, similar to what we did with Grid's drag methods

	// Renders a visual indication of an event or external element being dragged.
	// `eventLocation` has zoned start and end (optional)
	renderDrag: function(eventLocation, seg) {

		// always render a highlight underneath
		this.renderHighlight(this.eventToSpan(eventLocation));

		// if a segment from the same calendar but another component is being dragged, render a helper event
		if (seg && seg.component !== this) {
			return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
		}
	},

	// Unrenders any visual indication of a hovering event
	unrenderDrag: function() {
		this.unrenderHighlight();
		this.unrenderHelper();
	},

	/* Event Resize Visualization
	------------------------------------------------------------------------------------------------------------------*/

	// Renders a visual indication of an event being resized
	renderEventResize: function(eventLocation, seg) {
		this.renderHighlight(this.eventToSpan(eventLocation));
		return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
	},

	// Unrenders a visual indication of an event being resized
	unrenderEventResize: function() {
		this.unrenderHighlight();
		this.unrenderHelper();
	},

	/* Event Helper
	------------------------------------------------------------------------------------------------------------------*/

	// Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
	renderHelper: function(event, sourceSeg) {
		var helperNodes = [];
		var segs = this.eventToSegs(event);
		var rowStructs;

		segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
		rowStructs = this.renderSegRows(segs);

		// inject each new event skeleton into each associated row
		this.rowEls.each(function(row, rowNode) {
			var rowEl = $(rowNode); // the .fc-row
			var skeletonEl = $('

'); // will be absolutely positioned
			var skeletonTop;

			// If there is an original segment, match the top position. Otherwise, put it at the row's top level
			if (sourceSeg && sourceSeg.row === row) {
				skeletonTop = sourceSeg.el.position().top;
			}
			else {
				skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top;
			}

			skeletonEl.css('top', skeletonTop)
				.find('table')
					.append(rowStructs[row].tbodyEl);

			rowEl.append(skeletonEl);
			helperNodes.push(skeletonEl[0]);
		});

		return ( // must return the elements rendered
			this.helperEls = $(helperNodes) // array -> jQuery set
		);
	},

	// Unrenders any visual indication of a mock helper event
	unrenderHelper: function() {
		if (this.helperEls) {
			this.helperEls.remove();
			this.helperEls = null;
		}
	},

	/* Fill System (highlight, background events, business hours)
	------------------------------------------------------------------------------------------------------------------*/

	fillSegTag: 'td', // override the default tag name

	// Renders a set of rectangles over the given segments of days.
	// Only returns segments that successfully rendered.
	renderFill: function(type, segs, className) {
		var nodes = [];
		var i, seg;
		var skeletonEl;

		segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs

		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			skeletonEl = this.renderFillRow(type, seg, className);
			this.rowEls.eq(seg.row).append(skeletonEl);
			nodes.push(skeletonEl[0]);
		}

		this.elsByFill[type] = $(nodes);

		return segs;
	},

	// Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered.
	renderFillRow: function(type, seg, className) {
		var colCnt = this.colCnt;
		var startCol = seg.leftCol;
		var endCol = seg.rightCol + 1;
		var skeletonEl;
		var trEl;

		className = className || type.toLowerCase();

		skeletonEl = $(
			'
' +
				'

' +
			'
'
		);
		trEl = skeletonEl.find('tr');

		if (startCol > 0) {
			trEl.append('');
		}

		trEl.append(
			seg.el.attr('colspan', endCol - startCol)
		);

		if (endCol < colCnt) {
			trEl.append('');
		}

		this.bookendCells(trEl);

		return skeletonEl;
	}

});
<commit_msg>new weeknumbers styles dont need to support old momentjs<commit_after>
/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
----------------------------------------------------------------------------------------------------------------------*/

var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {

	numbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal
	bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid

	rowEls: null, // set of fake row elements
	cellEls: null, // set of whole-day elements comprising the row's background
	helperEls: null, // set of cell skeleton elements for rendering the mock event "helper"

	rowCoordCache: null,
	colCoordCache: null,

	// Renders the rows and columns into the component's `this.el`, which should already be assigned.
	// isRigid determins whether the individual rows should ignore the contents and be a constant height.
	// Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
	renderDates: function(isRigid) {
		var view = this.view;
		var rowCnt = this.rowCnt;
		var colCnt = this.colCnt;
		var html = '';
		var row;
		var col;

		for (row = 0; row < rowCnt; row++) {
			html += this.renderDayRowHtml(row, isRigid);
		}
		this.el.html(html);

		this.rowEls = this.el.find('.fc-row');
		this.cellEls = this.el.find('.fc-day');

		this.rowCoordCache = new CoordCache({
			els: this.rowEls,
			isVertical: true
		});
		this.colCoordCache = new CoordCache({
			els: this.cellEls.slice(0, this.colCnt), // only the first row
			isHorizontal: true
		});

		// trigger dayRender with each cell's element
		for (row = 0; row < rowCnt; row++) {
			for (col = 0; col < colCnt; col++) {
				view.trigger(
					'dayRender',
					null,
					this.getCellDate(row, col),
					this.getCellEl(row, col)
				);
			}
		}
	},

	unrenderDates: function() {
		this.removeSegPopover();
	},

	renderBusinessHours: function() {
		var segs = this.buildBusinessHourSegs(true); // wholeDay=true
		this.renderFill('businessHours', segs, 'bgevent');
	},

	unrenderBusinessHours: function() {
		this.unrenderFill('businessHours');
	},

	// Generates the HTML for a single row, which is a div that wraps a table.
	// `row` is the row number.
	renderDayRowHtml: function(row, isRigid) {
		var view = this.view;
		var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ];

		if (isRigid) {
			classes.push('fc-rigid');
		}

		return '' +
			'
' +
				'
' +
					'
' +
						this.renderBgTrHtml(row) +
					'
' +
				'
' +
				'
' +
					'
' +
						(this.numbersVisible ?
							'
' +
								this.renderNumberTrHtml(row) +
							'
' :
							''
							) +
					'
' +
				'
' +
			'
';
	},

	/* Grid Number Rendering
	------------------------------------------------------------------------------------------------------------------*/

	renderNumberTrHtml: function(row) {
		return '' +
			'
' +
				(this.isRTL ? '' : this.renderNumberIntroHtml(row)) +
				this.renderNumberCellsHtml(row) +
				(this.isRTL ? this.renderNumberIntroHtml(row) : '') +
			'
';
	},

	renderNumberIntroHtml: function(row) {
		return this.renderIntroHtml();
	},

	renderNumberCellsHtml: function(row) {
		var htmls = [];
		var col, date;

		for (col = 0; col < this.colCnt; col++) {
			date = this.getCellDate(row, col);
			htmls.push(this.renderNumberCellHtml(date));
		}

		return htmls.join('');
	},

	// Generates the HTML for the s of the "number" row in the DayGrid's content skeleton.
	// The number row will only exist if either day numbers or week numbers are turned on.
	renderNumberCellHtml: function(date) {
		var html = '';
		var classes;
		var weekCalcFirstDoW;

		if (!this.view.dayNumbersVisible && !this.view.cellWeekNumbersVisible) {
			// no numbers in day cell (week number must be along the side)
			return ''; //  will create an empty space above events :(
		}

		classes = this.getDayClasses(date);
		classes.unshift('fc-day-top');

		if (this.view.cellWeekNumbersVisible) {
			// To determine the day of week number change under ISO, we cannot
			// rely on moment.js methods such as firstDayOfWeek() or weekday(),
			// because they rely on the locale's dow (possibly overridden by
			// our firstDay option), which may not be Monday. We cannot change
			// dow, because that would affect the calendar start day as well.
			if (date._locale._fullCalendar_weekCalc === 'ISO') {
				weekCalcFirstDoW = 1;  // Monday by ISO 8601 definition
			}
			else {
				weekCalcFirstDoW = date._locale.firstDayOfWeek();
			}
		}

		html += '';

		if (this.view.cellWeekNumbersVisible && (date.day() == weekCalcFirstDoW)) {
			html += this.view.buildGotoAnchorHtml(
				{ date: date, type: 'week' },
				{ 'class': 'fc-week-number' },
				date.format('w') // inner HTML
			);
		}

		if (this.view.dayNumbersVisible) {
			html += this.view.buildGotoAnchorHtml(
				date,
				{ 'class': 'fc-day-number' },
				date.date() // inner HTML
			);
		}

		html += '	';

		return html;
	},

	/* Options
	------------------------------------------------------------------------------------------------------------------*/

	// Computes a default event time formatting string if `timeFormat` is not explicitly defined
	computeEventTimeFormat: function() {
		return this.view.opt('extraSmallTimeFormat'); // like "6p" or "6:30p"
	},

	// Computes a default `displayEventEnd` value if one is not expliclty defined
	computeDisplayEventEnd: function() {
		return this.colCnt == 1; // we'll likely have space if there's only one day
	},

	/* Dates
	------------------------------------------------------------------------------------------------------------------*/

	rangeUpdated: function() {
		this.updateDayTable();
	},

	// Slices up the given span (unzoned start/end with other misc data) into an array of segments
	spanToSegs: function(span) {
		var segs = this.sliceRangeByRow(span);
		var i, seg;

		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			if (this.isRTL) {
				seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex;
				seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex;
			}
			else {
				seg.leftCol = seg.firstRowDayIndex;
				seg.rightCol = seg.lastRowDayIndex;
			}
		}

		return segs;
	},

	/* Hit System
	------------------------------------------------------------------------------------------------------------------*/

	prepareHits: function() {
		this.colCoordCache.build();
		this.rowCoordCache.build();
		this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding; // hack
	},

	releaseHits: function() {
		this.colCoordCache.clear();
		this.rowCoordCache.clear();
	},

	queryHit: function(leftOffset, topOffset) {
		if (this.colCoordCache.isLeftInBounds(leftOffset) && this.rowCoordCache.isTopInBounds(topOffset)) {
			var col = this.colCoordCache.getHorizontalIndex(leftOffset);
			var row = this.rowCoordCache.getVerticalIndex(topOffset);

			if (row != null && col != null) {
				return this.getCellHit(row, col);
			}
		}
	},

	getHitSpan: function(hit) {
		return this.getCellRange(hit.row, hit.col);
	},

	getHitEl: function(hit) {
		return this.getCellEl(hit.row, hit.col);
	},

	/* Cell System
	------------------------------------------------------------------------------------------------------------------*/
	// FYI: the first column is the leftmost column, regardless of date

	getCellHit: function(row, col) {
		return {
			row: row,
			col: col,
			component: this, // needed unfortunately :(
			left: this.colCoordCache.getLeftOffset(col),
			right: this.colCoordCache.getRightOffset(col),
			top: this.rowCoordCache.getTopOffset(row),
			bottom: this.rowCoordCache.getBottomOffset(row)
		};
	},

	getCellEl: function(row, col) {
		return this.cellEls.eq(row * this.colCnt + col);
	},

	/* Event Drag Visualization
	------------------------------------------------------------------------------------------------------------------*/
	// TODO: move to DayGrid.event, similar to what we did with Grid's drag methods

	// Renders a visual indication of an event or external element being dragged.
	// `eventLocation` has zoned start and end (optional)
	renderDrag: function(eventLocation, seg) {

		// always render a highlight underneath
		this.renderHighlight(this.eventToSpan(eventLocation));

		// if a segment from the same calendar but another component is being dragged, render a helper event
		if (seg && seg.component !== this) {
			return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
		}
	},

	// Unrenders any visual indication of a hovering event
	unrenderDrag: function() {
		this.unrenderHighlight();
		this.unrenderHelper();
	},

	/* Event Resize Visualization
	------------------------------------------------------------------------------------------------------------------*/

	// Renders a visual indication of an event being resized
	renderEventResize: function(eventLocation, seg) {
		this.renderHighlight(this.eventToSpan(eventLocation));
		return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
	},

	// Unrenders a visual indication of an event being resized
	unrenderEventResize: function() {
		this.unrenderHighlight();
		this.unrenderHelper();
	},

	/* Event Helper
	------------------------------------------------------------------------------------------------------------------*/

	// Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
	renderHelper: function(event, sourceSeg) {
		var helperNodes = [];
		var segs = this.eventToSegs(event);
		var rowStructs;

		segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
		rowStructs = this.renderSegRows(segs);

		// inject each new event skeleton into each associated row
		this.rowEls.each(function(row, rowNode) {
			var rowEl = $(rowNode); // the .fc-row
			var skeletonEl = $('

'); // will be absolutely positioned
			var skeletonTop;

			// If there is an original segment, match the top position. Otherwise, put it at the row's top level
			if (sourceSeg && sourceSeg.row === row) {
				skeletonTop = sourceSeg.el.position().top;
			}
			else {
				skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top;
			}

			skeletonEl.css('top', skeletonTop)
				.find('table')
					.append(rowStructs[row].tbodyEl);

			rowEl.append(skeletonEl);
			helperNodes.push(skeletonEl[0]);
		});

		return ( // must return the elements rendered
			this.helperEls = $(helperNodes) // array -> jQuery set
		);
	},

	// Unrenders any visual indication of a mock helper event
	unrenderHelper: function() {
		if (this.helperEls) {
			this.helperEls.remove();
			this.helperEls = null;
		}
	},

	/* Fill System (highlight, background events, business hours)
	------------------------------------------------------------------------------------------------------------------*/

	fillSegTag: 'td', // override the default tag name

	// Renders a set of rectangles over the given segments of days.
	// Only returns segments that successfully rendered.
	renderFill: function(type, segs, className) {
		var nodes = [];
		var i, seg;
		var skeletonEl;

		segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs

		for (i = 0; i < segs.length; i++) {
			seg = segs[i];
			skeletonEl = this.renderFillRow(type, seg, className);
			this.rowEls.eq(seg.row).append(skeletonEl);
			nodes.push(skeletonEl[0]);
		}

		this.elsByFill[type] = $(nodes);

		return segs;
	},

	// Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered.
	renderFillRow: function(type, seg, className) {
		var colCnt = this.colCnt;
		var startCol = seg.leftCol;
		var endCol = seg.rightCol + 1;
		var skeletonEl;
		var trEl;

		className = className || type.toLowerCase();

		skeletonEl = $(
			'
' +
				'

' +
			'
'
		);
		trEl = skeletonEl.find('tr');

		if (startCol > 0) {
			trEl.append('');
		}

		trEl.append(
			seg.el.attr('colspan', endCol - startCol)
		);

		if (endCol < colCnt) {
			trEl.append('');
		}

		this.bookendCells(trEl);

		return skeletonEl;
	}

});
<|endoftext|>


<commit_before>import Immutable from "immutable";
import { msStringToDate, trigPrefixesAndBody } from "../../utils.js";
import { isUriRead } from "../../won-localstorage.js";

export function parseMessage(wonMessage, alreadyProcessed = false) {
  //seperating off header/@prefix-statements, so they can be folded in
  const { trigPrefixes, trigBody } = trigPrefixesAndBody(
    wonMessage.contentGraphTrig
  );

  let clauses = undefined;

  if (
    wonMessage.isProposeMessage() ||
    wonMessage.isAcceptMessage() ||
    wonMessage.isProposeToCancel()
  ) {
    if (wonMessage.isProposeMessage()) {
      clauses = wonMessage.getProposedMessages();
    } else if (wonMessage.isAcceptMessage()) {
      clauses = wonMessage.getAcceptedMessages();
    } else {
      clauses = wonMessage.getProposedToCancelMessages();
    }
  }

  const proposedMessages = wonMessage.getProposedMessages();
  const proposedToCancelMessages = wonMessage.getProposedToCancelMessages();
  const acceptedMessages = wonMessage.getAcceptedMessages();
  const rejectedMessages = wonMessage.getRejectsMessages();
  const retractedMessages = wonMessage.getRetractMessages();

  let parsedMessage = {
    belongsToUri: undefined,
    data: {
      uri: wonMessage.getMessageUri(),
      remoteUri: !wonMessage.isFromOwner()
        ? wonMessage.getRemoteMessageUri()
        : undefined,
      content: {
        text: wonMessage.getTextMessage(),
        matchScore: wonMessage.getMatchScore(),
      },
      references: {
        proposes:
          !proposedMessages || Array.isArray(proposedMessages)
            ? proposedMessages
            : [proposedMessages],
        proposesToCancel:
          !proposedToCancelMessages || Array.isArray(proposedToCancelMessages)
            ? proposedToCancelMessages
            : [proposedToCancelMessages],
        accepts:
          !acceptedMessages || Array.isArray(acceptedMessages)
            ? acceptedMessages
            : [acceptedMessages],
        rejects:
          !rejectedMessages || Array.isArray(rejectedMessages)
            ? rejectedMessages
            : [rejectedMessages],
        retracts:
          !retractedMessages || Array.isArray(retractedMessages)
            ? retractedMessages
            : [retractedMessages],
      },
      hasReferences: false, //will be determined by the hasReferences function
      hasContent: false, //will be determined by the hasContent function
      isParsable: false, //will be determined by the clause (hasReferences || hasContent) function
      isProposeMessage: wonMessage.isProposeMessage(),
      isAcceptMessage: wonMessage.isAcceptMessage(),
      isProposeToCancel: wonMessage.isProposeToCancel(),
      isRejectMessage: wonMessage.isRejectMessage(),
      isRetractMessage: wonMessage.isRetractMessage(),
      date: msStringToDate(wonMessage.getTimestamp()),
      outgoingMessage: wonMessage.isFromOwner(),
      messageType: wonMessage.getMessageType(),
      clauses: clauses,
      isRelevant: true,
      contentGraphTrig: {
        prefixes: trigPrefixes,
        body: trigBody,
      },
      contentGraphs: wonMessage.getContentGraphs(),
      contentGraphTrigRaw: wonMessage.contentGraphTrig,
      contentGraphTrigError: wonMessage.contentGraphTrigError,
      //Receive Status Flags
      unread:
        !wonMessage.isFromOwner() && !isUriRead(wonMessage.getMessageUri()),
      //Send Status Flags
      isReceivedByOwn: alreadyProcessed || !wonMessage.isFromOwner(), //if the message is not from the owner we know it has been received anyway
      isReceivedByRemote: alreadyProcessed || !wonMessage.isFromOwner(), //if the message is not from the owner we know it has been received anyway
      failedToSend: false,
    },
  };

  if (wonMessage.isFromOwner()) {
    parsedMessage.belongsToUri = wonMessage.getSender();
  } else {
    parsedMessage.belongsToUri = wonMessage.getReceiver();
  }

  parsedMessage.data.hasContent = hasContent(parsedMessage.data.content);
  parsedMessage.data.hasReferences = hasReferences(
    parsedMessage.data.references
  );
  parsedMessage.data.isParsable =
    parsedMessage.data.hasContent || parsedMessage.data.hasReferences;

  if (
    !parsedMessage.data.uri ||
    !parsedMessage.belongsToUri ||
    !parsedMessage.data.date
  ) {
    console.error(
      "Cant parse chat-message, data is an invalid message-object: ",
      wonMessage
    );
    return undefined;
  } else {
    return Immutable.fromJS(parsedMessage);
  }
}
function hasContent(content) {
  for (let prop in content) {
    if (content[prop] !== undefined && content[prop] != null) {
      return true;
    }
  }
  return false;
}

function hasReferences(refContent) {
  for (let prop in refContent) {
    if (refContent[prop] !== undefined && refContent[prop] != null) {
      return true;
    }
  }
  return false;
}
<commit_msg>remove clauses from parse-message<commit_after>import Immutable from "immutable";
import { msStringToDate, trigPrefixesAndBody } from "../../utils.js";
import { isUriRead } from "../../won-localstorage.js";

export function parseMessage(wonMessage, alreadyProcessed = false) {
  //seperating off header/@prefix-statements, so they can be folded in
  const { trigPrefixes, trigBody } = trigPrefixesAndBody(
    wonMessage.contentGraphTrig
  );

  const proposedMessages = wonMessage.getProposedMessages();
  const proposedToCancelMessages = wonMessage.getProposedToCancelMessages();
  const acceptedMessages = wonMessage.getAcceptedMessages();
  const rejectedMessages = wonMessage.getRejectsMessages();
  const retractedMessages = wonMessage.getRetractMessages();

  let parsedMessage = {
    belongsToUri: undefined,
    data: {
      uri: wonMessage.getMessageUri(),
      remoteUri: !wonMessage.isFromOwner()
        ? wonMessage.getRemoteMessageUri()
        : undefined,
      content: {
        text: wonMessage.getTextMessage(),
        matchScore: wonMessage.getMatchScore(),
      },
      references: {
        proposes:
          !proposedMessages || Array.isArray(proposedMessages)
            ? proposedMessages
            : [proposedMessages],
        proposesToCancel:
          !proposedToCancelMessages || Array.isArray(proposedToCancelMessages)
            ? proposedToCancelMessages
            : [proposedToCancelMessages],
        accepts:
          !acceptedMessages || Array.isArray(acceptedMessages)
            ? acceptedMessages
            : [acceptedMessages],
        rejects:
          !rejectedMessages || Array.isArray(rejectedMessages)
            ? rejectedMessages
            : [rejectedMessages],
        retracts:
          !retractedMessages || Array.isArray(retractedMessages)
            ? retractedMessages
            : [retractedMessages],
      },
      hasReferences: false, //will be determined by the hasReferences function
      hasContent: false, //will be determined by the hasContent function
      isParsable: false, //will be determined by the clause (hasReferences || hasContent) function
      isProposeMessage: wonMessage.isProposeMessage(),
      isAcceptMessage: wonMessage.isAcceptMessage(),
      isProposeToCancel: wonMessage.isProposeToCancel(),
      isRejectMessage: wonMessage.isRejectMessage(),
      isRetractMessage: wonMessage.isRetractMessage(),
      date: msStringToDate(wonMessage.getTimestamp()),
      outgoingMessage: wonMessage.isFromOwner(),
      messageType: wonMessage.getMessageType(),
      isRelevant: true,
      contentGraphTrig: {
        prefixes: trigPrefixes,
        body: trigBody,
      },
      contentGraphs: wonMessage.getContentGraphs(),
      contentGraphTrigRaw: wonMessage.contentGraphTrig,
      contentGraphTrigError: wonMessage.contentGraphTrigError,
      //Receive Status Flags
      unread:
        !wonMessage.isFromOwner() && !isUriRead(wonMessage.getMessageUri()),
      //Send Status Flags
      isReceivedByOwn: alreadyProcessed || !wonMessage.isFromOwner(), //if the message is not from the owner we know it has been received anyway
      isReceivedByRemote: alreadyProcessed || !wonMessage.isFromOwner(), //if the message is not from the owner we know it has been received anyway
      failedToSend: false,
    },
  };

  if (wonMessage.isFromOwner()) {
    parsedMessage.belongsToUri = wonMessage.getSender();
  } else {
    parsedMessage.belongsToUri = wonMessage.getReceiver();
  }

  parsedMessage.data.hasContent = hasContent(parsedMessage.data.content);
  parsedMessage.data.hasReferences = hasReferences(
    parsedMessage.data.references
  );
  parsedMessage.data.isParsable =
    parsedMessage.data.hasContent || parsedMessage.data.hasReferences;

  if (
    !parsedMessage.data.uri ||
    !parsedMessage.belongsToUri ||
    !parsedMessage.data.date
  ) {
    console.error(
      "Cant parse chat-message, data is an invalid message-object: ",
      wonMessage
    );
    return undefined;
  } else {
    return Immutable.fromJS(parsedMessage);
  }
}
function hasContent(content) {
  for (let prop in content) {
    if (content[prop] !== undefined && content[prop] != null) {
      return true;
    }
  }
  return false;
}

function hasReferences(refContent) {
  for (let prop in refContent) {
    if (refContent[prop] !== undefined && refContent[prop] != null) {
      return true;
    }
  }
  return false;
}
<|endoftext|>


<commit_before>import crypto from 'crypto';
import fs from 'fs';
import path from 'path';

import 'babel-polyfill';
import { oneLine } from 'common-tags';
import config from 'config';
import Express from 'express';
import helmet from 'helmet';
import Raven from 'raven';
import cookie from 'react-cookie';
import React from 'react';
import ReactDOM from 'react-dom/server';
import NestedStatus from 'react-nested-status';
import { Provider } from 'react-redux';
import { match } from 'react-router';
import { ReduxAsyncConnect, loadOnServer } from 'redux-connect';
import { loadFail } from 'redux-connect/lib/store';
import WebpackIsomorphicTools from 'webpack-isomorphic-tools';

import { createApiError } from 'core/api';
import ServerHtml from 'core/containers/ServerHtml';
import { prefixMiddleWare, trailingSlashesMiddleware } from 'core/middleware';
import { convertBoolean } from 'core/utils';
import { setClientApp, setLang, setJwt } from 'core/actions';
import log from 'core/logger';
import {
  getDirection,
  isValidLang,
  langToLocale,
  makeI18n,
} from 'core/i18n/utils';
import I18nProvider from 'core/i18n/Provider';

import WebpackIsomorphicToolsConfig from './webpack-isomorphic-tools-config';

const env = config.util.getEnv('NODE_ENV');
const version = path.join(config.get('basePath'), 'version.json');
const isDeployed = config.get('isDeployed');
const isDevelopment = config.get('isDevelopment');

function getNoScriptStyles({ appName }) {
  const cssPath = path.join(config.get('basePath'), `src/${appName}/noscript.css`);
  try {
    return fs.readFileSync(cssPath);
  } catch (e) {
    if (e.code !== 'ENOENT') {
      log.info(`noscript styles could not be parsed from ${cssPath}`);
    } else {
      log.debug(`noscript styles not found at ${cssPath}`);
    }
  }
  return undefined;
}

const appName = config.get('appName');

function getPageProps({ noScriptStyles = '', store, req, res }) {
  // Get SRI for deployed services only.
  const sriData = (isDeployed) ? JSON.parse(
    fs.readFileSync(path.join(config.get('basePath'), 'dist/sri.json'))
  ) : {};

  // Check the lang supplied by res.locals.lang for validity
  // or fall-back to the default.
  const lang = isValidLang(res.locals.lang) ?
    res.locals.lang : config.get('defaultLang');
  const dir = getDirection(lang);
  store.dispatch(setLang(lang));
  if (res.locals.clientApp) {
    store.dispatch(setClientApp(res.locals.clientApp));
  } else if (req && req.url) {
    log.warn(`No clientApp for this URL: ${req.url}`);
  } else {
    log.warn('No clientApp (error)');
  }

  return {
    appName,
    assets: webpackIsomorphicTools.assets(),
    htmlLang: lang,
    htmlDir: dir,
    includeSri: isDeployed,
    noScriptStyles,
    sriData,
    store,
    trackingEnabled: convertBoolean(config.get('trackingEnabled')),
  };
}

function showErrorPage({ createStore, error = {}, req, res, status }) {
  const store = createStore();
  const pageProps = getPageProps({ store, req, res });

  const apiError = createApiError({ response: { status } });
  store.dispatch(loadFail('ServerBase', { ...apiError, ...error }));

  const HTML = ReactDOM.renderToString(
    <ServerHtml {...pageProps} />);
  const httpStatus = NestedStatus.rewind();
  return res.status(status || httpStatus)
    .send(`<!DOCTYPE html>\n${HTML}`)
    .end();
}

function logRequests(req, res, next) {
  const start = new Date();
  next();
  const finish = new Date();
  const elapsed = finish - start;
  log.info({ req, res, start, finish, elapsed });
}

function baseServer(routes, createStore, { appInstanceName = appName } = {}) {
  const app = new Express();
  app.disable('x-powered-by');

  const sentryDsn = config.get('sentryDsn');
  if (sentryDsn) {
    Raven.config(sentryDsn).install();
    app.use(Raven.requestHandler());
    // The error handler is defined below.
  } else {
    log.warn(
      'Sentry reporting is disabled; Set config.sentryDsn to enable it.');
  }

  app.use(logRequests);

  // Set HTTP Strict Transport Security headers
  app.use(helmet.hsts({
    force: true,
    includeSubDomains: false,
    maxAge: 31536000, // seconds
  }));

  // Sets X-Frame-Options
  app.use(helmet.frameguard(config.get('frameGuard')));

  // Sets x-content-type-options:"nosniff"
  app.use(helmet.noSniff());

  // Sets x-xss-protection:"1; mode=block"
  app.use(helmet.xssFilter());

  // CSP configuration.
  const csp = config.get('CSP');
  const noScriptStyles = getNoScriptStyles({ appName: appInstanceName });
  if (csp) {
    if (noScriptStyles) {
      const hash = crypto.createHash('sha256').update(noScriptStyles).digest('base64');
      const cspValue = `'sha256-${hash}'`;
      if (!csp.directives.styleSrc.includes(cspValue)) {
        csp.directives.styleSrc.push(cspValue);
      }
    }
    app.use(helmet.contentSecurityPolicy(csp));
  } else {
    log.warn('CSP has been disabled from the config');
  }

  if (config.get('enableNodeStatics')) {
    app.use(Express.static(path.join(config.get('basePath'), 'dist')));
  }

  // Return version information as json
  app.get('/__version__', (req, res) => {
    fs.stat(version, (err) => {
      if (err) {
        res.sendStatus(415);
      } else {
        res.setHeader('Content-Type', 'application/json');
        fs.createReadStream(version).pipe(res);
      }
    });
  });

  // Return 200 for csp reports - this will need to be overridden when deployed.
  app.post('/__cspreport__', (req, res) => res.status(200).end('ok'));

  // Redirect from / for the search app it's a 302 to prevent caching.
  if (appInstanceName === 'search') {
    app.get('/', (req, res) => res.redirect(302, '/search'));
  }

  if (appInstanceName === 'disco' && isDevelopment) {
    app.get('/', (req, res) =>
      res.redirect(302, '/en-US/firefox/discovery/pane/48.0/Darwin/normal'));
  }

  // Handle application and lang redirections.
  if (config.get('enablePrefixMiddleware')) {
    app.use(prefixMiddleWare);
  }

  // Add trailing slashes to URLs
  if (config.get('enableTrailingSlashesMiddleware')) {
    app.use(trailingSlashesMiddleware);
  }

  app.use((req, res) => {
    if (isDevelopment) {
      log.info(oneLine`Clearing require cache for webpack isomorphic tools.
        [Development Mode]`);

      // clear require() cache if in development mode
      webpackIsomorphicTools.refresh();
    }

    match({ location: req.url, routes }, (
      err, redirectLocation, renderProps
    ) => {
      cookie.plugToRequest(req, res);

      const store = createStore();
      const token = cookie.load(config.get('cookieName'));
      if (token) {
        store.dispatch(setJwt(token));
      }

      // github.com/mozilla/addons-frontend/pull/1685#discussion_r99705186
      if (err) {
        return showErrorPage({ createStore, status: 500, req, res });
      }

      if (!renderProps) {
        return showErrorPage({ createStore, status: 404, req, res });
      }

      const pageProps = getPageProps({ noScriptStyles, store, req, res });
      const { htmlLang } = pageProps;
      const locale = langToLocale(htmlLang);

      function hydrateOnClient(props = {}) {
        const HTML = ReactDOM.renderToString(
          <ServerHtml {...pageProps} {...props} />);
        const httpStatus = NestedStatus.rewind();
        res.status(httpStatus).send(`<!DOCTYPE html>\n${HTML}`);
      }

      // Set disableSSR to true to debug
      // client-side-only render.
      if (config.get('disableSSR') === true) {
        return Promise.resolve(hydrateOnClient());
      }

      return loadOnServer({ ...renderProps, store })
        .then(() => {
          // eslint-disable-next-line global-require
          let i18nData = {};
          try {
            if (locale !== langToLocale(config.get('defaultLang'))) {
              // eslint-disable-next-line global-require, import/no-dynamic-require
              i18nData = require(
                `../../locale/${locale}/${appInstanceName}.js`);
            }
          } catch (e) {
            log.info(
              `Locale JSON not found or required for locale: "${locale}"`);
            log.info(
              `Falling back to default lang: "${config.get('defaultLang')}".`);
          }
          const i18n = makeI18n(i18nData, htmlLang);

          const InitialComponent = (
            <I18nProvider i18n={i18n}>
              <Provider store={store} key="provider">
                <ReduxAsyncConnect {...renderProps} />
              </Provider>
            </I18nProvider>
          );

          const errorPage = store.getState().errorPage;
          if (errorPage && errorPage.hasError) {
            return showErrorPage({
              createStore,
              error: errorPage.error,
              req,
              res,
              status: errorPage.statusCode,
            });
          }

          return hydrateOnClient({ component: InitialComponent });
        })
        .catch((error) => {
          log.error({ err: error });
          return showErrorPage({ createStore, error, status: 500, req, res });
        });
    });
  });

  // Error handlers:

  if (sentryDsn) {
    app.use(Raven.errorHandler());
  }

  // eslint-disable-next-line no-unused-vars
  app.use((error, req, res, next) => {
    log.error({ err: error });
    return showErrorPage({ createStore, error, status: 500, req, res });
  });

  return app;
}

export function runServer({
  listen = true, app = appName, exitProcess = true,
} = {}) {
  const port = config.get('serverPort');
  const host = config.get('serverHost');

  const isoMorphicServer = new WebpackIsomorphicTools(
    WebpackIsomorphicToolsConfig);

  return new Promise((resolve) => {
    if (!app) {
      throw new Error(
        `Please specify a valid appName from ${config.get('validAppNames')}`);
    }
    resolve();
  })
    .then(() => isoMorphicServer.server(config.get('basePath')))
    .then(() => {
      global.webpackIsomorphicTools = isoMorphicServer;
      // Webpack Isomorphic tools is ready
      // now fire up the actual server.
      return new Promise((resolve, reject) => {
        /* eslint-disable global-require, import/no-dynamic-require */
        const routes = require(`${app}/routes`).default;
        const createStore = require(`${app}/store`).default;
        /* eslint-enable global-require, import/no-dynamic-require */
        const server = baseServer(
          routes, createStore, { appInstanceName: app });
        if (listen === true) {
          server.listen(port, host, (err) => {
            if (err) {
              return reject(err);
            }
            const proxyEnabled = convertBoolean(config.get('proxyEnabled'));
            // Not using oneLine here since it seems to change '  ' to ' '.
            log.info([
              `🔥  Addons-frontend server is running [ENV:${env}] [APP:${app}]`,
              `[isDevelopment:${isDevelopment}] [isDeployed:${isDeployed}]`,
              `[apiHost:${config.get('apiHost')}] [apiPath:${config.get('apiPath')}]`,
            ].join(' '));
            if (proxyEnabled) {
              const proxyPort = config.get('proxyPort');
              log.info(
                `🚦  Proxy detected, frontend running at http://${host}:${port}.`);
              log.info(
                `👁  Open your browser at http://localhost:${proxyPort} to view it.`);
            } else {
              log.info(
                `👁  Open your browser at http://${host}:${port} to view it.`);
            }
            return resolve(server);
          });
        } else {
          resolve(server);
        }
      });
    })
    .catch((err) => {
      log.error({ err });
      if (exitProcess) {
        process.exit(1);
      } else {
        throw err;
      }
    });
}

export default baseServer;
<commit_msg>Fix how errors are passed to middleware (fixes Sentry) (#1951)<commit_after>import crypto from 'crypto';
import fs from 'fs';
import path from 'path';

import 'babel-polyfill';
import { oneLine } from 'common-tags';
import config from 'config';
import Express from 'express';
import helmet from 'helmet';
import Raven from 'raven';
import cookie from 'react-cookie';
import React from 'react';
import ReactDOM from 'react-dom/server';
import NestedStatus from 'react-nested-status';
import { Provider } from 'react-redux';
import { match } from 'react-router';
import { ReduxAsyncConnect, loadOnServer } from 'redux-connect';
import { loadFail } from 'redux-connect/lib/store';
import WebpackIsomorphicTools from 'webpack-isomorphic-tools';

import { createApiError } from 'core/api';
import ServerHtml from 'core/containers/ServerHtml';
import { prefixMiddleWare, trailingSlashesMiddleware } from 'core/middleware';
import { convertBoolean } from 'core/utils';
import { setClientApp, setLang, setJwt } from 'core/actions';
import log from 'core/logger';
import {
  getDirection,
  isValidLang,
  langToLocale,
  makeI18n,
} from 'core/i18n/utils';
import I18nProvider from 'core/i18n/Provider';

import WebpackIsomorphicToolsConfig from './webpack-isomorphic-tools-config';

const env = config.util.getEnv('NODE_ENV');
const version = path.join(config.get('basePath'), 'version.json');
const isDeployed = config.get('isDeployed');
const isDevelopment = config.get('isDevelopment');

function getNoScriptStyles({ appName }) {
  const cssPath = path.join(config.get('basePath'), `src/${appName}/noscript.css`);
  try {
    return fs.readFileSync(cssPath);
  } catch (e) {
    if (e.code !== 'ENOENT') {
      log.info(`noscript styles could not be parsed from ${cssPath}`);
    } else {
      log.debug(`noscript styles not found at ${cssPath}`);
    }
  }
  return undefined;
}

const appName = config.get('appName');

function getPageProps({ noScriptStyles = '', store, req, res }) {
  // Get SRI for deployed services only.
  const sriData = (isDeployed) ? JSON.parse(
    fs.readFileSync(path.join(config.get('basePath'), 'dist/sri.json'))
  ) : {};

  // Check the lang supplied by res.locals.lang for validity
  // or fall-back to the default.
  const lang = isValidLang(res.locals.lang) ?
    res.locals.lang : config.get('defaultLang');
  const dir = getDirection(lang);
  store.dispatch(setLang(lang));
  if (res.locals.clientApp) {
    store.dispatch(setClientApp(res.locals.clientApp));
  } else if (req && req.url) {
    log.warn(`No clientApp for this URL: ${req.url}`);
  } else {
    log.warn('No clientApp (error)');
  }

  return {
    appName,
    assets: webpackIsomorphicTools.assets(),
    htmlLang: lang,
    htmlDir: dir,
    includeSri: isDeployed,
    noScriptStyles,
    sriData,
    store,
    trackingEnabled: convertBoolean(config.get('trackingEnabled')),
  };
}

function showErrorPage({ createStore, error = {}, req, res, status }) {
  const store = createStore();
  const pageProps = getPageProps({ store, req, res });

  const componentDeclaredStatus = NestedStatus.rewind();
  let adjustedStatus = status || componentDeclaredStatus || 500;
  if (error.response && error.response.status) {
    adjustedStatus = error.response.status;
  }

  const apiError = createApiError({ response: { status: adjustedStatus } });
  store.dispatch(loadFail('ServerBase', { ...apiError, ...error }));

  const HTML = ReactDOM.renderToString(
    <ServerHtml {...pageProps} />);
  return res.status(adjustedStatus)
    .send(`<!DOCTYPE html>\n${HTML}`)
    .end();
}

function logRequests(req, res, next) {
  const start = new Date();
  next();
  const finish = new Date();
  const elapsed = finish - start;
  log.info({ req, res, start, finish, elapsed });
}

function hydrateOnClient({ res, props = {}, pageProps }) {
  const HTML =
    ReactDOM.renderToString(<ServerHtml {...pageProps} {...props} />);
  const componentDeclaredStatus = NestedStatus.rewind();
  return res.status(componentDeclaredStatus)
    .send(`<!DOCTYPE html>\n${HTML}`)
    .end();
}

function baseServer(routes, createStore, { appInstanceName = appName } = {}) {
  const app = new Express();
  app.disable('x-powered-by');

  const sentryDsn = config.get('sentryDsn');
  if (sentryDsn) {
    Raven.config(sentryDsn).install();
    app.use(Raven.requestHandler());
    log.info(`Sentry reporting configured with DSN ${sentryDsn}`);
    // The error handler is defined below.
  } else {
    log.warn(
      'Sentry reporting is disabled; Set config.sentryDsn to enable it.');
  }

  app.use(logRequests);

  // Set HTTP Strict Transport Security headers
  app.use(helmet.hsts({
    force: true,
    includeSubDomains: false,
    maxAge: 31536000, // seconds
  }));

  // Sets X-Frame-Options
  app.use(helmet.frameguard(config.get('frameGuard')));

  // Sets x-content-type-options:"nosniff"
  app.use(helmet.noSniff());

  // Sets x-xss-protection:"1; mode=block"
  app.use(helmet.xssFilter());

  // CSP configuration.
  const csp = config.get('CSP');
  const noScriptStyles = getNoScriptStyles({ appName: appInstanceName });
  if (csp) {
    if (noScriptStyles) {
      const hash = crypto.createHash('sha256').update(noScriptStyles).digest('base64');
      const cspValue = `'sha256-${hash}'`;
      if (!csp.directives.styleSrc.includes(cspValue)) {
        csp.directives.styleSrc.push(cspValue);
      }
    }
    app.use(helmet.contentSecurityPolicy(csp));
  } else {
    log.warn('CSP has been disabled from the config');
  }

  if (config.get('enableNodeStatics')) {
    app.use(Express.static(path.join(config.get('basePath'), 'dist')));
  }

  // Return version information as json
  app.get('/__version__', (req, res) => {
    fs.stat(version, (err) => {
      if (err) {
        res.sendStatus(415);
      } else {
        res.setHeader('Content-Type', 'application/json');
        fs.createReadStream(version).pipe(res);
      }
    });
  });

  // Return 200 for csp reports - this will need to be overridden when deployed.
  app.post('/__cspreport__', (req, res) => res.status(200).end('ok'));

  // Redirect from / for the search app it's a 302 to prevent caching.
  if (appInstanceName === 'search') {
    app.get('/', (req, res) => res.redirect(302, '/search'));
  }

  if (appInstanceName === 'disco' && isDevelopment) {
    app.get('/', (req, res) =>
      res.redirect(302, '/en-US/firefox/discovery/pane/48.0/Darwin/normal'));
  }

  // Handle application and lang redirections.
  if (config.get('enablePrefixMiddleware')) {
    app.use(prefixMiddleWare);
  }

  // Add trailing slashes to URLs
  if (config.get('enableTrailingSlashesMiddleware')) {
    app.use(trailingSlashesMiddleware);
  }

  app.use((req, res, next) => {
    if (isDevelopment) {
      log.info(oneLine`Clearing require cache for webpack isomorphic tools.
        [Development Mode]`);

      // clear require() cache if in development mode
      webpackIsomorphicTools.refresh();
    }

    match({ location: req.url, routes }, (
      matchError, redirectLocation, renderProps
    ) => {
      if (matchError) {
        log.info(`match() returned an error for ${req.url}: ${matchError}`);
        return next(matchError);
      }
      if (!renderProps) {
        log.info(`match() did not return renderProps for ${req.url}`);
        return showErrorPage({ createStore, status: 404, req, res });
      }

      let htmlLang;
      let locale;
      let pageProps;
      let store;

      try {
        cookie.plugToRequest(req, res);

        store = createStore();
        const token = cookie.load(config.get('cookieName'));
        if (token) {
          store.dispatch(setJwt(token));
        }

        pageProps = getPageProps({ noScriptStyles, store, req, res });
        if (config.get('disableSSR') === true) {
          log.warn(
            'Server side rendering disabled; responding without loading');
          return hydrateOnClient({ res, pageProps });
        }

        htmlLang = pageProps.htmlLang;
        locale = langToLocale(htmlLang);
      } catch (preLoadError) {
        log.info(
          `Caught an error in match() before loadOnServer(): ${preLoadError}`);
        return next(preLoadError);
      }

      return loadOnServer({ ...renderProps, store })
        .then(() => {
          // eslint-disable-next-line global-require
          let i18nData = {};
          try {
            if (locale !== langToLocale(config.get('defaultLang'))) {
              // eslint-disable-next-line global-require, import/no-dynamic-require
              i18nData = require(
                `../../locale/${locale}/${appInstanceName}.js`);
            }
          } catch (e) {
            log.info(
              `Locale JSON not found or required for locale: "${locale}"`);
            log.info(
              `Falling back to default lang: "${config.get('defaultLang')}".`);
          }
          const i18n = makeI18n(i18nData, htmlLang);

          const InitialComponent = (
            <I18nProvider i18n={i18n}>
              <Provider store={store} key="provider">
                <ReduxAsyncConnect {...renderProps} />
              </Provider>
            </I18nProvider>
          );

          const errorPage = store.getState().errorPage;
          if (errorPage && errorPage.hasError) {
            log.info(`Error page was dispatched to state: ${errorPage.error}`);
            throw errorPage.error;
          }

          return hydrateOnClient({
            props: { component: InitialComponent },
            pageProps,
            res,
          });
        })
        .catch((loadError) => {
          log.error(`Caught error from loadOnServer(): ${loadError}`);
          next(loadError);
        });
    });
  });

  // Error handlers:

  if (sentryDsn) {
    app.use(Raven.errorHandler());
  }

  app.use((error, req, res, next) => {
    if (res.headersSent) {
      log.warn(dedent`Ignoring error for ${req.url}
        because a response was already sent; error: ${error}`);
      return next(error);
    }
    log.error(`Showing 500 page for error: ${error}`);
    log.error({ err: error }); // log the stack trace too.
    return showErrorPage({ createStore, error, status: 500, req, res });
  });

  return app;
}

export function runServer({
  listen = true, app = appName, exitProcess = true,
} = {}) {
  const port = config.get('serverPort');
  const host = config.get('serverHost');

  const isoMorphicServer = new WebpackIsomorphicTools(
    WebpackIsomorphicToolsConfig);

  return new Promise((resolve) => {
    if (!app) {
      throw new Error(
        `Please specify a valid appName from ${config.get('validAppNames')}`);
    }
    resolve();
  })
    .then(() => isoMorphicServer.server(config.get('basePath')))
    .then(() => {
      global.webpackIsomorphicTools = isoMorphicServer;
      // Webpack Isomorphic tools is ready
      // now fire up the actual server.
      return new Promise((resolve, reject) => {
        /* eslint-disable global-require, import/no-dynamic-require */
        const routes = require(`${app}/routes`).default;
        const createStore = require(`${app}/store`).default;
        /* eslint-enable global-require, import/no-dynamic-require */
        const server = baseServer(
          routes, createStore, { appInstanceName: app });
        if (listen === true) {
          server.listen(port, host, (err) => {
            if (err) {
              return reject(err);
            }
            const proxyEnabled = convertBoolean(config.get('proxyEnabled'));
            // Not using oneLine here since it seems to change '  ' to ' '.
            log.info([
              `🔥  Addons-frontend server is running [ENV:${env}] [APP:${app}]`,
              `[isDevelopment:${isDevelopment}] [isDeployed:${isDeployed}]`,
              `[apiHost:${config.get('apiHost')}] [apiPath:${config.get('apiPath')}]`,
            ].join(' '));
            if (proxyEnabled) {
              const proxyPort = config.get('proxyPort');
              log.info(
                `🚦  Proxy detected, frontend running at http://${host}:${port}.`);
              log.info(
                `👁  Open your browser at http://localhost:${proxyPort} to view it.`);
            } else {
              log.info(
                `👁  Open your browser at http://${host}:${port} to view it.`);
            }
            return resolve(server);
          });
        } else {
          resolve(server);
        }
      });
    })
    .catch((err) => {
      log.error({ err });
      if (exitProcess) {
        process.exit(1);
      } else {
        throw err;
      }
    });
}

export default baseServer;
<|endoftext|>