<commit_before>/**
 * React Coverflow
 *
 * Author: andyyou
 */
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Radium from 'radium';
import styles from './stylesheets/coverflow';

import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

var TOUCH = {move: false,
  lastX: 0,
  sign: 0,
  lastMove: 0
};
var TRANSITIONS = [
  'transitionend',
  'oTransitionEnd',
  'otransitionend',
  'MSTransitionEnd',
  'webkitTransitionEnd'
];
var HandleAnimationState = function() {
  this._removePointerEvents();
};

@Radium
class Coverflow extends Component {
  /**
   * Life cycle events
   */
  constructor(props) {
    super(props);

    this.state = {
      current: this._center(),
      move: 0,
      width: this.props.width || 'auto',
      height: this.props.height || 'auto'
    };
  }

  componentDidMount() {
    this.updateDimensions();
    let length = React.Children.count(this.props.children);

    TRANSITIONS.forEach(event => {
      for (let i = 0; i < length; i++) {
        var figureID = `figure_${i}`;
        this.refs[figureID].addEventListener(event, HandleAnimationState.bind(this));
      }
    });
    window.addEventListener('resize', this.updateDimensions.bind(this));
    document.addEventListener('keypress', this._keypress.bind(this), false);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.active !== nextProps.active) {
      this.updateDimensions(nextProps.active);
    }
  }

  componentWillUnmount() {
    let length = React.Children.count(this.props.children);

    TRANSITIONS.forEach(event => {
      for (let i = 0; i < length; i++) {
        var figureID = `figure_${i}`;
        this.refs[figureID].removeEventListener(event, HandleAnimationState.bind(this));
      }
    });
    window.removeEventListener('resize', this.updateDimensions.bind(this));
    document.removeEventListener('keypress', this._keypress.bind(this));
  }

  updateDimensions(active) {
    const {displayQuantityOfSide} = this.props;
    let length = React.Children.count(this.props.children);
    let center = this._center();
    var state = {
      width: ReactDOM.findDOMNode(this).offsetWidth,
      height: ReactDOM.findDOMNode(this).offsetHeight
    };
    var baseWidth = state.width / (displayQuantityOfSide * 2 + 1);
    var active = active || this.props.active;
    if (typeof active === 'number' && ~~active < length) {
      active = ~~active;
      var move = 0;
      move = baseWidth * (center - active);

      state = Object.assign({}, state, {
        current: active,
        move: move
      });
    }
    this.setState(state);
  }

  render() {
    const {enableScroll} = this.props;
    const {width, height} = this.state;
    return (
        <div className={styles.container}
             style={[{width: `${width}px`, height: `${height}px`}, this.props.media]}
             onWheel={enableScroll ? this._handleWheel.bind(this) : null}
             onTouchStart={this._handleTouchStart.bind(this)}
             onTouchMove={this._handleTouchMove.bind(this)}
             >

                {this._renderFigureNodes()}

            {
              this.props.navigation &&
              (

                  <button type="button" className={styles.button} onClick={ this._handlePrevFigure.bind(this) }>Previous</button>
                  <button type="button" className={styles.button} onClick={ this._handleNextFigure.bind(this) }>Next</button>

              )
            }

    );
  }

  /**
   * Private methods
   */
  _center() {
    let length = React.Children.count(this.props.children);
    return Math.floor(length / 2);
  }

  _keypress(e) {
    if (e.keyCode === 39) {
      this._handlePrevFigure();
    } else if (e.keyCode === 37) {
      this._handleNextFigure();
    }
  }

  _handleFigureStyle(index, current) {
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;
    let style = {};
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let length = React.Children.count(this.props.children);
    let offset = length % 2 === 0 ? -width/10 : 0;
    // Handle opacity
    let depth = displayQuantityOfSide - Math.abs(current - index);
    let opacity = depth === 1 ? 0.95 : 0.5;
    opacity = depth === 2 ? 0.92 : opacity;
    opacity = depth === 3 ? 0.9 : opacity;
    opacity = current === index ? 1 : opacity;
    // Handle translateX
    if (index === current) {
      style['width'] = `${baseWidth}px`;
      style['transform'] = `translateX(${this.state.move + offset}px) scale(1.2)`;
      style['zIndex'] = `${10 - depth}`;
      style['opacity'] = opacity;
    } else if (index < current) {
      // Left side
      style['width'] = `${baseWidth}px`;
      style['transform'] = `translateX(${this.state.move + offset}px) rotateY(40deg)`;
      style['zIndex'] = `${10 - depth}`;
      style['opacity'] = opacity;
    } else if (index > current) {
      // Right side
      style['width'] = `${baseWidth}px`;
      style['transform'] = ` translateX(${this.state.move + offset}px) rotateY(-40deg)`;
      style['zIndex'] = `${10 - depth}`;
      style['opacity'] = opacity;
    }
    return style;
  }

  _handleFigureClick(index, action, e) {
    e.preventDefault();
    if (!this.props.clickable) {
      return;
    }

    this.refs.stage.style['pointerEvents'] = 'none';
    if (this.state.current === index) {
      if (typeof action === 'string') {
        window.open(action, '_blank');
      } else if (typeof action === 'function') {
        action();
      }

      this._removePointerEvents();
    } else {
      const {displayQuantityOfSide} = this.props;
      const {width} = this.state;
      let baseWidth = width / (displayQuantityOfSide * 2 + 1);
      let distance = this._center() - index;
      let move = distance * baseWidth;
      this.setState({current: index, move: move});
    }
  }

  _renderFigureNodes() {
    const {enableHeading} = this.props;

    let figureNodes = React.Children.map(this.props.children, (child, index) => {
      let figureElement = React.cloneElement(child, {className: styles.cover});
      let style = this._handleFigureStyle(index, this.state.current);
      return (
        <figure className={styles.figure}
          key={index}
          style={style}
          onClick={ this._handleFigureClick.bind(this, index, figureElement.props['data-action']) }
          ref={`figure_${index}`}
          >
          {figureElement}
          {
            enableHeading &&

{figureElement.props.alt}

          }

      );
    });
    return figureNodes;
  }

  _removePointerEvents() {
    this.refs.stage.style['pointerEvents'] = 'auto';
  }

  _handlePrevFigure() {
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;
    let current = this.state.current;
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let distance = this._center() - (current - 1);
    let move = distance * baseWidth;

    if (current - 1 >= 0) {
      this.setState({ current: current - 1, move: move });
      TOUCH.lastMove = move;
    }
  }

  _handleNextFigure() {
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;
    let current = this.state.current;
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let distance = this._center() - (current + 1);
    let move = distance * baseWidth;

    if (current + 1 < this.props.children.length) {
      this.setState({ current: current + 1, move: move });
      TOUCH.lastMove = move;
    }
  }

  _handleWheel(e) {
    e.preventDefault();

    let delta = e.deltaY * (-120);
    let count = Math.ceil(Math.abs(delta) / 120);

    if (count > 0) {
      const sign = Math.abs(delta) / delta;
      let func = null;

      if (sign > 0) {
        func = this._handlePrevFigure.bind(this);
      } else if (sign < 0) {
        func = this._handleNextFigure.bind(this);
      }

      if (typeof func === 'function') {
        for (let i = 0; i < count; i++) func();
      }
    }
  }

  _handleTouchStart(e) {
    TOUCH.lastX = e.nativeEvent.touches[0].clientX;
    TOUCH.lastMove = this.state.move;
  }

  _handleTouchMove(e) {
    e.preventDefault();
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;

    let clientX = e.nativeEvent.touches[0].clientX;
    let lastX = TOUCH.lastX;
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let move = clientX - lastX;
    let totalMove = TOUCH.lastMove - move;
    let sign = Math.abs(move) / move;

    if (Math.abs(totalMove) >= baseWidth) {
      let fn = null;
      if (sign > 0) {
        fn = this._handlePrevFigure.bind(this);
      } else if (sign < 0) {
        fn = this._handleNextFigure.bind(this);
      }
      if (typeof fn === 'function') {
        fn();
      }
    }
  }
};

Coverflow.propTypes = {
  displayQuantityOfSide: React.PropTypes.number.isRequired,
  navigation: React.PropTypes.bool,
  enableHeading: React.PropTypes.bool,
  enableScroll: React.PropTypes.bool,
  active: React.PropTypes.number
};

Coverflow.defaultProps = {
  navigation: false,
  enableHeading: true,
  enableScroll: true,
  clickable: true
};

Coverflow.displayName = 'Coverflow';

export default Coverflow;
<commit_msg>Add props to set the scale factor of images<commit_after>/**
 * React Coverflow
 *
 * Author: andyyou
 */
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Radium from 'radium';
import styles from './stylesheets/coverflow';

import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

var TOUCH = {move: false,
  lastX: 0,
  sign: 0,
  lastMove: 0
};
var TRANSITIONS = [
  'transitionend',
  'oTransitionEnd',
  'otransitionend',
  'MSTransitionEnd',
  'webkitTransitionEnd'
];
var HandleAnimationState = function() {
  this._removePointerEvents();
};

@Radium
class Coverflow extends Component {
  /**
   * Life cycle events
   */
  constructor(props) {
    super(props);

    this.state = {
      current: this._center(),
      move: 0,
      width: this.props.width || 'auto',
      height: this.props.height || 'auto'
    };
  }

  componentDidMount() {
    this.updateDimensions();
    let length = React.Children.count(this.props.children);

    TRANSITIONS.forEach(event => {
      for (let i = 0; i < length; i++) {
        var figureID = `figure_${i}`;
        this.refs[figureID].addEventListener(event, HandleAnimationState.bind(this));
      }
    });
    window.addEventListener('resize', this.updateDimensions.bind(this));
    document.addEventListener('keypress', this._keypress.bind(this), false);
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.active !== nextProps.active) {
      this.updateDimensions(nextProps.active);
    }
  }

  componentWillUnmount() {
    let length = React.Children.count(this.props.children);

    TRANSITIONS.forEach(event => {
      for (let i = 0; i < length; i++) {
        var figureID = `figure_${i}`;
        this.refs[figureID].removeEventListener(event, HandleAnimationState.bind(this));
      }
    });
    window.removeEventListener('resize', this.updateDimensions.bind(this));
    document.removeEventListener('keypress', this._keypress.bind(this));
  }

  updateDimensions(active) {
    const {displayQuantityOfSide} = this.props;
    let length = React.Children.count(this.props.children);
    let center = this._center();
    var state = {
      width: ReactDOM.findDOMNode(this).offsetWidth,
      height: ReactDOM.findDOMNode(this).offsetHeight
    };
    var baseWidth = state.width / (displayQuantityOfSide * 2 + 1);
    var active = active || this.props.active;
    if (typeof active === 'number' && ~~active < length) {
      active = ~~active;
      var move = 0;
      move = baseWidth * (center - active);

      state = Object.assign({}, state, {
        current: active,
        move: move
      });
    }
    this.setState(state);
  }

  render() {
    const {enableScroll} = this.props;
    const {width, height} = this.state;
    return (
        <div className={styles.container}
             style={[{width: `${width}px`, height: `${height}px`}, this.props.media]}
             onWheel={enableScroll ? this._handleWheel.bind(this) : null}
             onTouchStart={this._handleTouchStart.bind(this)}
             onTouchMove={this._handleTouchMove.bind(this)}
             >

                {this._renderFigureNodes()}

            {
              this.props.navigation &&
              (

                  <button type="button" className={styles.button} onClick={ this._handlePrevFigure.bind(this) }>Previous</button>
                  <button type="button" className={styles.button} onClick={ this._handleNextFigure.bind(this) }>Next</button>

              )
            }

    );
  }

  /**
   * Private methods
   */
  _center() {
    let length = React.Children.count(this.props.children);
    return Math.floor(length / 2);
  }

  _keypress(e) {
    if (e.keyCode === 39) {
      this._handlePrevFigure();
    } else if (e.keyCode === 37) {
      this._handleNextFigure();
    }
  }

  _handleFigureStyle(index, current) {
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;
    let style = {};
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let length = React.Children.count(this.props.children);
    let offset = length % 2 === 0 ? -width/10 : 0;
    // Handle opacity
    let depth = displayQuantityOfSide - Math.abs(current - index);
    let opacity = depth === 1 ? 0.95 : 0.5;
    opacity = depth === 2 ? 0.92 : opacity;
    opacity = depth === 3 ? 0.9 : opacity;
    opacity = current === index ? 1 : opacity;
    // Handle translateX
    if (index === current) {
      style['width'] = `${baseWidth}px`;
      style['transform'] = `translateX(${this.state.move + offset}px) scale($(this.props.currentFigureScale))`;
      style['zIndex'] = `${10 - depth}`;
      style['opacity'] = opacity;
    } else if (index < current) {
      // Left side
      style['width'] = `${baseWidth}px`;
      style['transform'] = `translateX(${this.state.move + offset}px) rotateY(40deg) scale($(this.props.otherFigureScale))`;
      style['zIndex'] = `${10 - depth}`;
      style['opacity'] = opacity;
    } else if (index > current) {
      // Right side
      style['width'] = `${baseWidth}px`;
      style['transform'] = ` translateX(${this.state.move + offset}px) rotateY(-40deg) scale($(this.props.otherFigureScale))`;
      style['zIndex'] = `${10 - depth}`;
      style['opacity'] = opacity;
    }
    return style;
  }

  _handleFigureClick(index, action, e) {
    e.preventDefault();
    if (!this.props.clickable) {
      return;
    }

    this.refs.stage.style['pointerEvents'] = 'none';
    if (this.state.current === index) {
      if (typeof action === 'string') {
        window.open(action, '_blank');
      } else if (typeof action === 'function') {
        action();
      }

      this._removePointerEvents();
    } else {
      const {displayQuantityOfSide} = this.props;
      const {width} = this.state;
      let baseWidth = width / (displayQuantityOfSide * 2 + 1);
      let distance = this._center() - index;
      let move = distance * baseWidth;
      this.setState({current: index, move: move});
    }
  }

  _renderFigureNodes() {
    const {enableHeading} = this.props;

    let figureNodes = React.Children.map(this.props.children, (child, index) => {
      let figureElement = React.cloneElement(child, {className: styles.cover});
      let style = this._handleFigureStyle(index, this.state.current);
      return (
        <figure className={styles.figure}
          key={index}
          style={style}
          onClick={ this._handleFigureClick.bind(this, index, figureElement.props['data-action']) }
          ref={`figure_${index}`}
          >
          {figureElement}
          {
            enableHeading &&

{figureElement.props.alt}

          }

      );
    });
    return figureNodes;
  }

  _removePointerEvents() {
    this.refs.stage.style['pointerEvents'] = 'auto';
  }

  _handlePrevFigure() {
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;
    let current = this.state.current;
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let distance = this._center() - (current - 1);
    let move = distance * baseWidth;

    if (current - 1 >= 0) {
      this.setState({ current: current - 1, move: move });
      TOUCH.lastMove = move;
    }
  }

  _handleNextFigure() {
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;
    let current = this.state.current;
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let distance = this._center() - (current + 1);
    let move = distance * baseWidth;

    if (current + 1 < this.props.children.length) {
      this.setState({ current: current + 1, move: move });
      TOUCH.lastMove = move;
    }
  }

  _handleWheel(e) {
    e.preventDefault();

    let delta = e.deltaY * (-120);
    let count = Math.ceil(Math.abs(delta) / 120);

    if (count > 0) {
      const sign = Math.abs(delta) / delta;
      let func = null;

      if (sign > 0) {
        func = this._handlePrevFigure.bind(this);
      } else if (sign < 0) {
        func = this._handleNextFigure.bind(this);
      }

      if (typeof func === 'function') {
        for (let i = 0; i < count; i++) func();
      }
    }
  }

  _handleTouchStart(e) {
    TOUCH.lastX = e.nativeEvent.touches[0].clientX;
    TOUCH.lastMove = this.state.move;
  }

  _handleTouchMove(e) {
    e.preventDefault();
    const {displayQuantityOfSide} = this.props;
    const {width} = this.state;

    let clientX = e.nativeEvent.touches[0].clientX;
    let lastX = TOUCH.lastX;
    let baseWidth = width / (displayQuantityOfSide * 2 + 1);
    let move = clientX - lastX;
    let totalMove = TOUCH.lastMove - move;
    let sign = Math.abs(move) / move;

    if (Math.abs(totalMove) >= baseWidth) {
      let fn = null;
      if (sign > 0) {
        fn = this._handlePrevFigure.bind(this);
      } else if (sign < 0) {
        fn = this._handleNextFigure.bind(this);
      }
      if (typeof fn === 'function') {
        fn();
      }
    }
  }
};

Coverflow.propTypes = {
  displayQuantityOfSide: React.PropTypes.number.isRequired,
  navigation: React.PropTypes.bool,
  enableHeading: React.PropTypes.bool,
  enableScroll: React.PropTypes.bool,
  active: React.PropTypes.number,
  currentFigureScale: React.PropTypes.number,
  otherFigureScale: React.PropTypes.number
};

Coverflow.defaultProps = {
  navigation: false,
  enableHeading: true,
  enableScroll: true,
  clickable: true,
  currentFigureScale: 1.0,
  otherFigureScale: 1.2
};

Coverflow.displayName = 'Coverflow';

export default Coverflow;
<|endoftext|>


<commit_before>import { Syntax } from './Syntax';
import { toArray } from 'lodash';
import { Lexer, Token } from './Lexer';
import { createMessage } from '../utils';

class AST {
    constructor(text) {
        this.text = text;
        this.lexer = new Lexer();
    }

    ast(text) {
        this.text = text;
        this.tokens = this.lexer.lex(text);

        return this.schema();
    }

    eof() {
        return this.peek().type == Token.EOF;
    }

    schema() {
        const body = [];

        while(!this.eof()) {
            body.push(this.comment());
        }

        return {
            type: Syntax.Schema,
            body
        };
    }

    peek(m1, m2, m3, m4) {
        return this.peekAhead(0, m1, m2, m3, m4);
    }

    expect(m1, m2, m3, m4) {
        const token = this.peek(m1, m2, m3, m4);

        if(token) {
            this.tokens.shift();
            return token;
        }

        return false;
    }

    consume(m1) {
        if(this.tokens.length === 0) {
            this.throwError('Unexpected end of expression "%s". Expected %s', this.peek().value, m1);
        }

        const token = this.expect(m1);

        if(!token) {
            this.throwError('"%s" is unexpected, expecting "%s"', this.peek().value, m1);
            return false;
        }

        return token;
    }

    throwError(...args) {
        throw new Error(createMessage(...args));
    }

    peekAhead(i, m1, m2, m3, m4) {
        if(this.tokens.length > i) {
            const emptyMatches = (!m1 && !m2 && !m3 && !m4);
            const token = this.tokens[i];
            const t = token.value;

            if(emptyMatches || (m1 == t || m2 == t || m3 == t || m4 == t)) {
                return token;
            }
        }
        return false;
    }

    comment() {
        if(this.expect('---', '/*')) {
            const comment = {
                type: Syntax.CommentBlock,
                blocks: []
            };

            while(!this.expect('---', '*/')) {
                comment.blocks.push(this.consume().value);
            }

            return comment;
        }

        return this.typeDeclaration();
    }

    identifier() {
        const token = this.consume();

        return {
            type: Syntax.Identifier,
            name: token.value
        };
    }

    typePrimary() {
        if(this.peek().type != Token.Identifier) {
            this.throwError('unexpected token %s at column %s', this.peek().value, this.peek().start);
            return false;
        }

        return this.identifier();
    }

    typeIdentifier() {
        const primary = this.typePrimary();

        if(!this.expect('.')) {
            return primary;
        }

        return {
            type: Syntax.TypeIdentifier,
            property: this.typeIdentifier(),
            namespace: primary
        };
    }

    typeDeclaration() {
        let ctor,
            name;

        name = this.typeIdentifier();

        if(this.expect(':')) {
            ctor = name;
            name = this.typeIdentifier();
        } else {
            ctor = this.typeIdentifier();
        }

        const type = {
            type: Syntax.TypeDeclaration,
            ctor,
            name
        };

        if(this.expect('{')) {
            type.body = this.typeBody();
        } else if(this.expect('->')) {
            type.body = this.typeShortBody();
        } else {
            type.body = [];
            this.consume(';');
        }

        return type;
    }

    typeShortBody() {
        const body = [];

        do {
            body.push(this.typeProperty());
        } while(!this.eof() && this.expect(','));

        if(this.eof()) {
            return body;
        }

        this.expect(';');

        return body;
    }

    vector() {
        const id = this.identifier();

        if(id.name != 'Vector') {
            this.throwError('Expect %s but got %s', 'vector', id.name);
        }

        this.consume('<');
        const vectorType = this.typeIdentifier();
        this.consume('>');

        return {
            type: Syntax.Vector,
            vectorType
        };
    }

    typeProperty() {
        const ast = {
            key: this.identifier(),
            type: Syntax.TypeProperty,
        };
        this.expect(':');

        ast.returnType = this.returnType();

        return ast;
    }

    returnType() {
        const id = this.peek().value;
        switch(id) {
        case 'Vector':
            return this.vector();
        }

        return this.typeIdentifier();
    }

    integer() {
        if(this.peek().type != Token.NumericLiteral) {
            this.throwError('expected %s but got %s instead', Token.NumericLiteral, this.peek().type);
            return false;
        }

        return {
            type: Syntax.Literal,
            value: this.consume().value
        };
    }

    typeBody() {
        const body = [];

        while(!this.expect('}')) {
            body.push(this.typeProperty());
            this.consume(';');
        }

        return body;
    }
}

export {
    Syntax,
    AST
};
<commit_msg>src/language/AST.js: never drop EOF token and always return false when execute peek(), peekAhead() or expect() when EOF is the last token<commit_after>import { Syntax } from './Syntax';
import { toArray } from 'lodash';
import { Lexer, Token } from './Lexer';
import { createMessage } from '../utils';

class AST {
    constructor(text) {
        this.text = text;
        this.lexer = new Lexer();
    }

    ast(text) {
        this.text = text;
        this.tokens = this.lexer.lex(text);

        return this.schema();
    }

    eof() {
        return this.tokens[0].type == Token.EOF;
    }

    schema() {
        const body = [];

        while(!this.eof()) {
            body.push(this.comment());
        }

        return {
            type: Syntax.Schema,
            body
        };
    }

    peek(m1, m2, m3, m4) {
        return this.peekAhead(0, m1, m2, m3, m4);
    }

    expect(m1, m2, m3, m4) {
        const token = this.peek(m1, m2, m3, m4);

        if(token) {
            this.tokens.shift();
            return token;
        }

        return false;
    }

    consume(m1) {
        if(this.tokens.length === 0) {
            this.throwError('Unexpected end of expression "%s". Expected %s', this.peek().value, m1);
        }

        const token = this.expect(m1);

        if(!token) {
            this.throwError('"%s" is unexpected, expecting "%s"', this.peek().value, m1);
            return false;
        }

        return token;
    }

    throwError(...args) {
        throw new Error(createMessage(...args));
    }

    peekAhead(i, m1, m2, m3, m4) {
        if(!this.eof() && this.tokens.length > i) {
            const emptyMatches = (!m1 && !m2 && !m3 && !m4);
            const token = this.tokens[i];
            const t = token.value;

            if(emptyMatches || (m1 == t || m2 == t || m3 == t || m4 == t)) {
                return token;
            }
        }
        return false;
    }

    comment() {
        if(this.expect('---', '/*')) {
            const comment = {
                type: Syntax.CommentBlock,
                blocks: []
            };

            while(!this.expect('---', '*/')) {
                comment.blocks.push(this.consume().value);
            }

            return comment;
        }

        return this.typeDeclaration();
    }

    identifier() {
        const token = this.consume();

        return {
            type: Syntax.Identifier,
            name: token.value
        };
    }

    typePrimary() {
        if(this.peek().type != Token.Identifier) {
            this.throwError('unexpected token %s at column %s', this.peek().value, this.peek().start);
            return false;
        }

        return this.identifier();
    }

    typeIdentifier() {
        const primary = this.typePrimary();

        if(!this.expect('.')) {
            return primary;
        }

        return {
            type: Syntax.TypeIdentifier,
            property: this.typeIdentifier(),
            namespace: primary
        };
    }

    typeDeclaration() {
        let ctor,
            name;

        name = this.typeIdentifier();

        if(this.expect(':')) {
            ctor = name;
            name = this.typeIdentifier();
        } else {
            ctor = this.typeIdentifier();
        }

        const type = {
            type: Syntax.TypeDeclaration,
            ctor,
            name
        };

        if(this.expect('{')) {
            type.body = this.typeBody();
        } else if(this.expect('->')) {
            type.body = this.typeShortBody();
        } else {
            type.body = [];
            this.consume(';');
        }

        return type;
    }

    typeShortBody() {
        const body = [];

        do {
            body.push(this.typeProperty());
        } while(!this.eof() && this.expect(','));

        if(this.eof()) {
            return body;
        }

        this.expect(';');

        return body;
    }

    vector() {
        const id = this.identifier();

        if(id.name != 'Vector') {
            this.throwError('Expect %s but got %s', 'vector', id.name);
        }

        this.consume('<');
        const vectorType = this.typeIdentifier();
        this.consume('>');

        return {
            type: Syntax.Vector,
            vectorType
        };
    }

    typeProperty() {
        const ast = {
            key: this.identifier(),
            type: Syntax.TypeProperty,
        };
        this.expect(':');

        ast.returnType = this.returnType();

        return ast;
    }

    returnType() {
        const id = this.peek().value;
        switch(id) {
        case 'Vector':
            return this.vector();
        }

        return this.typeIdentifier();
    }

    integer() {
        if(this.peek().type != Token.NumericLiteral) {
            this.throwError('expected %s but got %s instead', Token.NumericLiteral, this.peek().type);
            return false;
        }

        return {
            type: Syntax.Literal,
            value: this.consume().value
        };
    }

    typeBody() {
        const body = [];

        while(!this.expect('}')) {
            body.push(this.typeProperty());
            this.consume(';');
        }

        return body;
    }
}

export {
    Syntax,
    AST
};
<|endoftext|>


<commit_before>
// runs WECO rules on our time series array and
// flags potentials
var one_day = 24 * 60 * 60 * 1000;
function check_weco(serie, options, serie_name) {
  var time_bucket = options.time_bucket;

  var start = options.start || serie[0].x;
  var end = options.end || serie[serie.length - 1].x;
  var very_start = start;

  var day_cutoff = end - one_day;
  var end_cutoff = end - (time_bucket*1000*3);

  var expected = start;
  var missing_val = 0;

  var violations = [
    { value: day_cutoff, active: false, type: "marker" },
    { value: end_cutoff, active: true, early: true, type: "marker" }
  ];

  var zones = {};

  var active_violations = [];

  function check_point(zone, pt, ev, max_len, threshold) {
    if (!zones[zone]) {
      zones[zone] = {
        count: 0,
        arr: [],
        name: zone
      };
    }

    if (ev(pt.y)) {
      zones[zone].count++;
    }

    if (threshold <= zones[zone].count) {
      var violation = {
        value: pt.x,
        recovery: 3,
        type: "zone_" + zone,
        active: pt.x >= day_cutoff,
        early: pt.x >= end_cutoff,
       series: serie_name
      };
      violations.push(violation);
      active_violations.push(violation);
      zones[zone].count = 0;
    } else {
      if (!pt.final) {

        // recover any active violations!
        _.each(active_violations, function(v) {
          if (!v.recovery) {
            var recovery = {
              value: pt.x,
              type: "recover",
              // special recovery keys
              recover_value: v.value,
              recover_type: v.type,
              active: pt.x >= day_cutoff,
             series: serie_name
            };

            violations.push(recovery);
          }

          v.recovery = (v.recovery || 0) - 1
        });

        active_violations = _.filter(active_violations, function(v) {
          return (v.recovery || 0) >= 0;
        });
      }

    }

    zones[zone].arr.push(pt);

    while (zones[zone].arr.length > (max_len || 0)) {
      var old_pt = zones[zone].arr.pop();
      if (ev(old_pt.y)) {
        zones[zone].count--;
      }
    }

    zones[zone].count = Math.max(zones[zone].count, 0);

  }

  function eval_a(val) { return Math.abs(val) >= 10; }
  function eval_b(val) { return Math.abs(val) >= 20; }
  function eval_c(val) { return Math.abs(val) >= 30; }
  function eval_d(val) { return Math.abs(val) >= 40; }

  for (var i = 0; i < serie.length; i++) {
    var pt = serie[i];
    while (expected < pt.x) {
      expected += time_bucket * 1000;

      // we only alert on missing data not inside the early warning window
      if (expected < end_cutoff) {
        missing_val++;
      }
    }

    // 3 in a row is missing
    if (missing_val >= 3) {
      var active = false;
      if (start > day_cutoff) {
        active = true;

      }

      if (very_start === start) {
        var new_start = parseInt(start / one_day, 10) * one_day;
        // we splice to the nearest day if we can't see the start...
        start = new_start;
      }

      // if the missing val is larger than 3 days, i just want to snap it to
      // the most recent day it went missing
      var violation = {value: start, type: "missing", active: active, series: serie_name };
      violations.push(violation);
      active_violations.push(violation);
    }

    missing_val = 0;
    start = expected;

    check_point('a', pt, eval_a, 10, 9);
    check_point('b', pt, eval_b, 5, 4);
    check_point('c', pt, eval_c, 3, 2);
    check_point('d', pt, eval_d, 1, 1);
  }

  var valid_types = {
    marker: 1,
    missing: 1,
    recover: 1,
    zone_c: 1,
    zone_d: 1
  }
  return _.filter(violations, function(v) {
    if (v.type === "recover") {
      return valid_types[v.recover_type];
    }

    return valid_types[v.type];
  });

}

module.exports = {
  find_violations: function(ret, options) {
    var violations = [];
    _.each(ret, function(serie) {
      violations = violations.concat(check_weco(serie.data, options, serie.name));
    });

    return violations;
  },
  normalize_series: function(ret, options) {
    _.each(ret, function(serie) {
      var avg = 0;
      var std = 0;
      var m2 = 0;
      var delta = 0;
      var n = 0;

      _.each(serie.data, function(pt) {
        n += 1;
        var trimmed_y = pt.y;

        std = Math.sqrt(m2 / (n-1)) * 1.134;
        // huberization: trim by 1.5 std then multiply std by 1.134 at the end
        // assumes normal distribution of error
        if (std > 0) {
          trimmed_y = parseInt(Math.min(pt.y, 1.5 * std + avg), 10);
          trimmed_y = parseInt(Math.max(trimmed_y, (-1.5 * std) + avg), 10);
        }

        delta = trimmed_y - avg;

        avg += delta / n;
        m2 += delta*(pt.y - avg);
      });

      std = Math.sqrt(m2 / (n-1)) * 1.134;
      var min, max = 0;
      _.each(serie.data, function(pt) {
        pt.y = (pt.y - avg) / std * 10;

        // check which zone this point falls in
        min = Math.min(pt.y, min);
        max = Math.max(pt.y, max);
      });

      serie.data.push({ x: options.end, y: 0, final: true});
      serie.data.sort(function(a, b) {
        return a.x - b.x;
      });
    });

    return ret;
  }
};
<commit_msg>weco: dont let still active violations off the hook<commit_after>
// runs WECO rules on our time series array and
// flags potentials
var one_day = 24 * 60 * 60 * 1000;
function check_weco(serie, options, serie_name) {
  var time_bucket = options.time_bucket;

  var start = options.start || serie[0].x;
  var end = options.end || serie[serie.length - 1].x;
  var very_start = start;

  var day_cutoff = end - one_day;
  var end_cutoff = end - (time_bucket*1000*3);

  var expected = start;
  var missing_val = 0;

  var violations = [
    { value: day_cutoff, active: false, type: "marker" },
    { value: end_cutoff, active: true, early: true, type: "marker" }
  ];

  var zones = {};

  var active_violations = [];

  function check_point(zone, pt, ev, max_len, threshold) {
    if (!zones[zone]) {
      zones[zone] = {
        count: 0,
        arr: [],
        name: zone
      };
    }

    if (ev(pt.y)) {
      zones[zone].count++;
    }

    if (threshold <= zones[zone].count) {
      var violation = {
        value: pt.x,
        recovery: 3,
        type: "zone_" + zone,
        active: pt.x >= day_cutoff,
        early: pt.x >= end_cutoff,
       series: serie_name
      };
      violations.push(violation);
      active_violations.push(violation);
      zones[zone].count = 0;
    } else {
      // recover any active violations!
      _.each(active_violations, function(v) {
        // if we are currently in active territory
        // and the violation is still open, mark it
        // as active
        if (pt.x >= day_cutoff) {
          v.active = true;
        }

        if (pt.final) {
          return;
        }

        if (!v.recovery) {
          var recovery = {
            value: pt.x,
            type: "recover",
            // special recovery keys
            recover_value: v.value,
            recover_type: v.type,
            active: pt.x >= day_cutoff,
           series: serie_name
          };

          violations.push(recovery);
        }

        v.recovery = (v.recovery || 0) - 1;
      });

      active_violations = _.filter(active_violations, function(v) {
        return (v.recovery || 0) >= 0;
      });

    }

    zones[zone].arr.push(pt);

    while (zones[zone].arr.length > (max_len || 0)) {
      var old_pt = zones[zone].arr.pop();
      if (ev(old_pt.y)) {
        zones[zone].count--;
      }
    }

    zones[zone].count = Math.max(zones[zone].count, 0);

  }

  function eval_a(val) { return Math.abs(val) >= 10; }
  function eval_b(val) { return Math.abs(val) >= 20; }
  function eval_c(val) { return Math.abs(val) >= 30; }
  function eval_d(val) { return Math.abs(val) >= 40; }

  for (var i = 0; i < serie.length; i++) {
    var pt = serie[i];
    while (expected < pt.x) {
      expected += time_bucket * 1000;

      // we only alert on missing data not inside the early warning window
      if (expected < end_cutoff) {
        missing_val++;
      }
    }

    // 3 in a row is missing
    if (missing_val >= 3) {
      var active = false;
      if (start > day_cutoff) {
        active = true;

      }

      if (very_start === start) {
        var new_start = parseInt(start / one_day, 10) * one_day;
        // we splice to the nearest day if we can't see the start...
        start = new_start;
      }

      // if the missing val is larger than 3 days, i just want to snap it to
      // the most recent day it went missing
      var violation = {value: start, type: "missing", active: active, series: serie_name };
      violations.push(violation);
      active_violations.push(violation);
    }

    missing_val = 0;
    start = expected;

    check_point('a', pt, eval_a, 10, 9);
    check_point('b', pt, eval_b, 5, 4);
    check_point('c', pt, eval_c, 3, 2);
    check_point('d', pt, eval_d, 1, 1);
  }

  var valid_types = {
    marker: 1,
    missing: 1,
    recover: 1,
    zone_c: 1,
    zone_d: 1
  }
  return _.filter(violations, function(v) {
    if (v.type === "recover") {
      return valid_types[v.recover_type];
    }

    return valid_types[v.type];
  });

}

module.exports = {
  find_violations: function(ret, options) {
    var violations = [];
    _.each(ret, function(serie) {
      violations = violations.concat(check_weco(serie.data, options, serie.name));
    });

    return violations;
  },
  normalize_series: function(ret, options) {
    _.each(ret, function(serie) {
      var avg = 0;
      var std = 0;
      var m2 = 0;
      var delta = 0;
      var n = 0;

      _.each(serie.data, function(pt) {
        n += 1;
        var trimmed_y = pt.y;

        std = Math.sqrt(m2 / (n-1)) * 1.134;
        // huberization: trim by 1.5 std then multiply std by 1.134 at the end
        // assumes normal distribution of error
        if (std > 0) {
          trimmed_y = parseInt(Math.min(pt.y, 1.5 * std + avg), 10);
          trimmed_y = parseInt(Math.max(trimmed_y, (-1.5 * std) + avg), 10);
        }

        delta = trimmed_y - avg;

        avg += delta / n;
        m2 += delta*(pt.y - avg);
      });

      std = Math.sqrt(m2 / (n-1)) * 1.134;
      var min, max = 0;
      _.each(serie.data, function(pt) {
        pt.y = (pt.y - avg) / std * 10;

        // check which zone this point falls in
        min = Math.min(pt.y, min);
        max = Math.max(pt.y, max);
      });

      serie.data.push({ x: options.end, y: 0, final: true});
      serie.data.sort(function(a, b) {
        return a.x - b.x;
      });
    });

    return ret;
  }
};
<|endoftext|>


<commit_before>'use strict';
const expect = require('chai').expect;
const sinon = require('sinon');
const BootBot = require('../lib/BootBot');

describe('BootBot', () => {
  let server;
  let userId;
  let bot;

  beforeEach(() => {
    const options = {
      accessToken: '1234',
      verifyToken: '5678',
      appSecret: 'foobar'
    };
    userId = 1234;
    bot = new BootBot(options);
    server = sinon.fakeServer.create();
    server.autoRespond = true;
  });

  afterEach(() => {
    server.restore();
  });

  it('creates a bot instance', () => {
    expect(bot instanceof BootBot).to.equal(true);
  });

  it('throws an error if there are missing tokens', () => {
    expect(() => new BootBot()).to.throw(Error);
  });

  it('can send a text message', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const text = 'Hello world!';
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        text
      }
    };

    bot.sendTextMessage(userId, text);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('can send a text message with quick replies', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const text = 'Hello world!';

    // Quick Replies as strings with auto-generated payload
    const quickReplies1 = ['Red', 'Green', 'Blue'];
    const expected1 = {
      recipient: {
        id: userId
      },
      message: {
        text,
        quick_replies: [{
          content_type: 'text',
          title: 'Red',
          payload: 'BOOTBOT_QR_RED'
        }, {
          content_type: 'text',
          title: 'Green',
          payload: 'BOOTBOT_QR_GREEN'
        }, {
          content_type: 'text',
          title: 'Blue',
          payload: 'BOOTBOT_QR_BLUE'
        }]
      }
    };

    bot.sendTextMessage(userId, text, quickReplies1);
    expect(spy.calledWith(expected1)).to.equal(true);

    // Quick Replies as objects with partial information
    const quickReplies2 = [{
      title: 'Purple'
    }, {
      title: 'Yellow',
      payload: 'CUSTOM_YELLOW'
    }, {
      title: 'Image',
      image_url: 'http://google.com/image.png'
    }];
    const expected2 = {
      recipient: {
        id: userId
      },
      message: {
        text,
        quick_replies: [{
          content_type: 'text',
          title: 'Purple',
          payload: 'BOOTBOT_QR_PURPLE'
        }, {
          content_type: 'text',
          title: 'Yellow',
          payload: 'CUSTOM_YELLOW'
        }, {
          content_type: 'text',
          title: 'Image',
          payload: 'BOOTBOT_QR_IMAGE',
          image_url: 'http://google.com/image.png'
        }]
      }
    };

    bot.sendTextMessage(userId, text, quickReplies2);
    expect(spy.calledWith(expected2)).to.equal(true);

    // Quick Replies as custom object
    const quickReplies3 = [{
      content_type: 'location'
    }, {
      content_type: 'bootbot',
      payload: 'THIS_IS_JUST_A_TEST'
    }, {
      foo: 'bar'
    }];
    const expected3 = {
      recipient: {
        id: userId
      },
      message: {
        text,
        quick_replies: [{
          content_type: 'location'
        }, {
          content_type: 'bootbot',
          payload: 'THIS_IS_JUST_A_TEST'
        }, {
          foo: 'bar'
        }]
      }
    };

    bot.sendTextMessage(userId, text, quickReplies3);
    expect(spy.calledWith(expected3)).to.equal(true);
  });

  it('can send a button template', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const text = 'Choose an option';
    const buttons = [
      { type: 'postback', title: 'Red', payload: 'COLOR_RED' },
      { type: 'postback', title: 'Green', payload: 'COLOR_GREEN' },
      { type: 'postback', title: 'Blue', payload: 'COLOR_BLUE' }
    ];
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type: 'template',
          payload: {
            template_type: 'button',
            text,
            buttons
          }
        }
      }
    };

    bot.sendButtonTemplate(userId, text, buttons);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('can send a generic template', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const elements = [
      {
        "title": "Welcome to Peter\'s Hats",
        "image_url": "http://petersapparel.parseapp.com/img/item100-thumb.png",
        "subtitle": "We\'ve got the right hat for everyone.",
        "buttons": [
          {
            "type": "web_url",
            "url": "https://petersapparel.parseapp.com/view_item?item_id=100",
            "title": "View Website"
          },
          {
            "type": "postback",
            "title": "Start Chatting",
            "payload": "USER_DEFINED_PAYLOAD"
          }
        ]
      }
    ];
    const options = {
      imageAspectRatio: 'horizontal'
    };
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type: 'template',
          payload: {
            template_type: 'generic',
            image_aspect_ratio: 'horizontal',
            elements
          }
        }
      }
    };

    bot.sendGenericTemplate(userId, elements, options);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('can send a list template', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const elements = [{
      "title": "Classic T-Shirt Collection",
      "image_url": "https://peterssendreceiveapp.ngrok.io/img/collection.png",
      "subtitle": "See all our colors",
      "default_action": {
        "type": "web_url",
        "url": "https://peterssendreceiveapp.ngrok.io/shop_collection",
        "messenger_extensions": true,
        "webview_height_ratio": "tall",
        "fallback_url": "https://peterssendreceiveapp.ngrok.io/"
      },
      "buttons": [{
        "title": "View",
        "type": "web_url",
        "url": "https://peterssendreceiveapp.ngrok.io/collection",
        "messenger_extensions": true,
        "webview_height_ratio": "tall",
        "fallback_url": "https://peterssendreceiveapp.ngrok.io/"
      }]
    }];
    const buttons = [{
      "title": "View More",
      "type": "postback",
      "payload": "payload"
    }]
    const options = {
      topElementStyle: 'compact'
    };
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type: 'template',
          payload: {
            template_type: 'list',
            top_element_style: 'compact',
            elements,
            buttons
          }
        }
      }
    };

    bot.sendListTemplate(userId, elements, buttons, options);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('can send an attachment', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const type = 'image';
    const url = 'http://petersapparel.parseapp.com/img/item100-thumb.png';
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type,
          payload: {
            url
          }
        }
      }
    };

    bot.sendAttachment(userId, type, url);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('emits a referral event', () => {
    const callback = sinon.spy();
    const event = {
      "sender": {
        "id": "USER_ID"
      },
      "recipient": {
        "id": "PAGE_ID"
      },
      "timestamp": 1458692752478,
      "referral": {
        "ref": "MY_REF",
        "source": "SHORTLINK",
        "type": "OPEN_THREAD"
      }
    };
    bot.on('referral', callback);
    bot._handleEvent('referral', event);
    expect(callback.calledWith(event)).to.equal(true);
  });

  describe('Messenger Profile API', () => {
    it('can set a greeting text', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const text = 'Hello, world!';
      const expected = {
        greeting: [{
          locale: 'default',
          text
        }]
      };

      bot.setGreetingText(text);
      expect(spy.calledWith(expected)).to.equal(true);
    });

    it('can set the Get Started button', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const callback = () => {};
      const expected = {
        get_started: {
          payload: 'BOOTBOT_GET_STARTED'
        }
      };

      // With a callback
      bot.setGetStartedButton(callback);
      expect(spy.calledWith(expected)).to.equal(true);

      const payload = 'CUSTOM_GET_STARTED_PAYLOAD';
      const expected2 = {
        get_started: {
          payload
        }
      };

      // With a payload
      bot.setGetStartedButton(payload);
      expect(spy.calledWith(expected)).to.equal(true);
    });

    it('can delete the Get Started button', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const expected = {
        fields: [ 'get_started' ]
      };

      bot.deleteGetStartedButton();
      expect(spy.calledWith(expected, 'DELETE')).to.equal(true);
    });

    it('can set the Persistent Menu', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const buttons = [
        {
          title: 'My Account',
          type: 'nested',
          call_to_actions: [
            {
              title: 'Pay Bill',
              type: 'postback',
              payload: 'PAYBILL_PAYLOAD'
            },
            {
              title: 'History',
              type: 'postback',
              payload: 'HISTORY_PAYLOAD'
            },
            {
              title: 'Contact Info',
              type: 'postback',
              payload: 'CONTACT_INFO_PAYLOAD'
            }
          ]
        },
        {
          title: 'Go to Website',
          type: 'web_url',
          url: 'http://purple.com'
        }
      ];
      const disableInput = false;
      const expected = {
        persistent_menu: [{
          locale: 'default',
          composer_input_disabled: disableInput,
          call_to_actions: buttons
        }]
      };

      bot.setPersistentMenu(buttons, disableInput);
      expect(spy.calledWith(expected)).to.equal(true);
    });

    it('can delete the Persistent Menu', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const expected = {
        fields: [ 'persistent_menu' ]
      };

      bot.deletePersistentMenu();
      expect(spy.calledWith(expected, 'DELETE')).to.equal(true);
    });
  });

  describe('Checkbox Plugin support', () => {
    it('uses the user_ref param as the recipient when replying to a checkbox authentication event', () => {
      const spy = sinon.spy(bot, 'sendMessage');
      const event = {
        "recipient": {
          "id": "PAGE_ID"
        },
        "timestamp": 1234567890,
        "optin": {
          "ref": "PASS_THROUGH_PARAM",
          "user_ref": "UNIQUE_REF_PARAM"
        }
      };
      const expectedRecipient = { user_ref: 'UNIQUE_REF_PARAM' };
      const expectedMessage = { text: 'hello' };

      bot.on('authentication', (payload, chat) => {
        chat.say('hello');
      });
      bot._handleEvent('authentication', event);
      expect(spy.calledWith(expectedRecipient, expectedMessage)).to.equal(true);
    });

    it('uses the sender ID if the authentication event contains one', () => {
      const spy = sinon.spy(bot, 'sendMessage');
      const event = {
        "sender": {
          "id": "USER_ID"
        },
        "recipient": {
          "id": "PAGE_ID"
        },
        "timestamp": 1234567890,
        "optin": {
          "ref":"PASS_THROUGH_PARAM"
        }
      };
      const expectedRecipient = 'USER_ID';
      const expectedMessage = { text: 'hello' };

      bot.on('authentication', (payload, chat) => {
        chat.say('hello');
      });
      bot._handleEvent('authentication', event);
      expect(spy.calledWith(expectedRecipient, expectedMessage)).to.equal(true);
    });
  });
});
<commit_msg>Added tests for hear, using new handleFacebookData function<commit_after>'use strict';
const expect = require('chai').expect;
const sinon = require('sinon');
const BootBot = require('../lib/BootBot');

describe('BootBot', () => {
  let server;
  let userId;
  let bot;

  beforeEach(() => {
    const options = {
      accessToken: '1234',
      verifyToken: '5678',
      appSecret: 'foobar'
    };
    userId = 1234;
    bot = new BootBot(options);
    server = sinon.fakeServer.create();
    server.autoRespond = true;
  });

  afterEach(() => {
    server.restore();
  });

  it('creates a bot instance', () => {
    expect(bot instanceof BootBot).to.equal(true);
  });

  it('throws an error if there are missing tokens', () => {
    expect(() => new BootBot()).to.throw(Error);
  });

  it('can send a text message', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const text = 'Hello world!';
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        text
      }
    };

    bot.sendTextMessage(userId, text);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('responds to a hear', () => {
    const comment = sinon.spy();
    bot.hear(/hello/, (payload, chat) => {
      comment();
    })
    const data = {
      "object": "page",
      "entry": [
        {
          "id": "PAGE_ID",
          "time": 1458692752478,
          "messaging": [
            {
              "sender": {
                "id": "USER_ID"
              },
              "recipient": {
                "id": "PAGE_ID"
              },
              "timestamp": 1458692752478,
              "message": {
                "mid": "mid.1457764197618:41d102a3e1ae206a38",
                "text": "hello, world!"
              }
            }
          ]
        }
      ]
    }
    bot.handleFacebookData(data)
    expect(comment.calledOnce).to.equal(true);
  });

  it('hear doesn\'t respond on non-match', () => {
    const comment = sinon.spy();
    bot.hear(/hello/, (payload, chat) => {
      comment();
    })
    const data = {
      "object": "page",
      "entry": [
        {
          "id": "PAGE_ID",
          "time": 1458692752478,
          "messaging": [
            {
              "sender": {
                "id": "USER_ID"
              },
              "recipient": {
                "id": "PAGE_ID"
              },
              "timestamp": 1458692752478,
              "message": {
                "mid": "mid.1457764197618:41d102a3e1ae206a38",
                "text": "goodbye, world!"
              }
            }
          ]
        }
      ]
    }
    bot.handleFacebookData(data)
    expect(comment.called).to.equal(false);
  });

  it('can send a text message with quick replies', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const text = 'Hello world!';

    // Quick Replies as strings with auto-generated payload
    const quickReplies1 = ['Red', 'Green', 'Blue'];
    const expected1 = {
      recipient: {
        id: userId
      },
      message: {
        text,
        quick_replies: [{
          content_type: 'text',
          title: 'Red',
          payload: 'BOOTBOT_QR_RED'
        }, {
          content_type: 'text',
          title: 'Green',
          payload: 'BOOTBOT_QR_GREEN'
        }, {
          content_type: 'text',
          title: 'Blue',
          payload: 'BOOTBOT_QR_BLUE'
        }]
      }
    };

    bot.sendTextMessage(userId, text, quickReplies1);
    expect(spy.calledWith(expected1)).to.equal(true);

    // Quick Replies as objects with partial information
    const quickReplies2 = [{
      title: 'Purple'
    }, {
      title: 'Yellow',
      payload: 'CUSTOM_YELLOW'
    }, {
      title: 'Image',
      image_url: 'http://google.com/image.png'
    }];
    const expected2 = {
      recipient: {
        id: userId
      },
      message: {
        text,
        quick_replies: [{
          content_type: 'text',
          title: 'Purple',
          payload: 'BOOTBOT_QR_PURPLE'
        }, {
          content_type: 'text',
          title: 'Yellow',
          payload: 'CUSTOM_YELLOW'
        }, {
          content_type: 'text',
          title: 'Image',
          payload: 'BOOTBOT_QR_IMAGE',
          image_url: 'http://google.com/image.png'
        }]
      }
    };

    bot.sendTextMessage(userId, text, quickReplies2);
    expect(spy.calledWith(expected2)).to.equal(true);

    // Quick Replies as custom object
    const quickReplies3 = [{
      content_type: 'location'
    }, {
      content_type: 'bootbot',
      payload: 'THIS_IS_JUST_A_TEST'
    }, {
      foo: 'bar'
    }];
    const expected3 = {
      recipient: {
        id: userId
      },
      message: {
        text,
        quick_replies: [{
          content_type: 'location'
        }, {
          content_type: 'bootbot',
          payload: 'THIS_IS_JUST_A_TEST'
        }, {
          foo: 'bar'
        }]
      }
    };

    bot.sendTextMessage(userId, text, quickReplies3);
    expect(spy.calledWith(expected3)).to.equal(true);
  });

  it('can send a button template', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const text = 'Choose an option';
    const buttons = [
      { type: 'postback', title: 'Red', payload: 'COLOR_RED' },
      { type: 'postback', title: 'Green', payload: 'COLOR_GREEN' },
      { type: 'postback', title: 'Blue', payload: 'COLOR_BLUE' }
    ];
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type: 'template',
          payload: {
            template_type: 'button',
            text,
            buttons
          }
        }
      }
    };

    bot.sendButtonTemplate(userId, text, buttons);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('can send a generic template', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const elements = [
      {
        "title": "Welcome to Peter\'s Hats",
        "image_url": "http://petersapparel.parseapp.com/img/item100-thumb.png",
        "subtitle": "We\'ve got the right hat for everyone.",
        "buttons": [
          {
            "type": "web_url",
            "url": "https://petersapparel.parseapp.com/view_item?item_id=100",
            "title": "View Website"
          },
          {
            "type": "postback",
            "title": "Start Chatting",
            "payload": "USER_DEFINED_PAYLOAD"
          }
        ]
      }
    ];
    const options = {
      imageAspectRatio: 'horizontal'
    };
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type: 'template',
          payload: {
            template_type: 'generic',
            image_aspect_ratio: 'horizontal',
            elements
          }
        }
      }
    };

    bot.sendGenericTemplate(userId, elements, options);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('can send a list template', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const elements = [{
      "title": "Classic T-Shirt Collection",
      "image_url": "https://peterssendreceiveapp.ngrok.io/img/collection.png",
      "subtitle": "See all our colors",
      "default_action": {
        "type": "web_url",
        "url": "https://peterssendreceiveapp.ngrok.io/shop_collection",
        "messenger_extensions": true,
        "webview_height_ratio": "tall",
        "fallback_url": "https://peterssendreceiveapp.ngrok.io/"
      },
      "buttons": [{
        "title": "View",
        "type": "web_url",
        "url": "https://peterssendreceiveapp.ngrok.io/collection",
        "messenger_extensions": true,
        "webview_height_ratio": "tall",
        "fallback_url": "https://peterssendreceiveapp.ngrok.io/"
      }]
    }];
    const buttons = [{
      "title": "View More",
      "type": "postback",
      "payload": "payload"
    }]
    const options = {
      topElementStyle: 'compact'
    };
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type: 'template',
          payload: {
            template_type: 'list',
            top_element_style: 'compact',
            elements,
            buttons
          }
        }
      }
    };

    bot.sendListTemplate(userId, elements, buttons, options);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('can send an attachment', () => {
    const spy = sinon.spy(bot, 'sendRequest');
    const type = 'image';
    const url = 'http://petersapparel.parseapp.com/img/item100-thumb.png';
    const expected = {
      recipient: {
        id: userId
      },
      message: {
        attachment: {
          type,
          payload: {
            url
          }
        }
      }
    };

    bot.sendAttachment(userId, type, url);
    expect(spy.calledWith(expected)).to.equal(true);
  });

  it('emits a referral event', () => {
    const callback = sinon.spy();
    const event = {
      "sender": {
        "id": "USER_ID"
      },
      "recipient": {
        "id": "PAGE_ID"
      },
      "timestamp": 1458692752478,
      "referral": {
        "ref": "MY_REF",
        "source": "SHORTLINK",
        "type": "OPEN_THREAD"
      }
    };
    bot.on('referral', callback);
    bot._handleEvent('referral', event);
    expect(callback.calledWith(event)).to.equal(true);
  });

  describe('Messenger Profile API', () => {
    it('can set a greeting text', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const text = 'Hello, world!';
      const expected = {
        greeting: [{
          locale: 'default',
          text
        }]
      };

      bot.setGreetingText(text);
      expect(spy.calledWith(expected)).to.equal(true);
    });

    it('can set the Get Started button', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const callback = () => {};
      const expected = {
        get_started: {
          payload: 'BOOTBOT_GET_STARTED'
        }
      };

      // With a callback
      bot.setGetStartedButton(callback);
      expect(spy.calledWith(expected)).to.equal(true);

      const payload = 'CUSTOM_GET_STARTED_PAYLOAD';
      const expected2 = {
        get_started: {
          payload
        }
      };

      // With a payload
      bot.setGetStartedButton(payload);
      expect(spy.calledWith(expected)).to.equal(true);
    });

    it('can delete the Get Started button', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const expected = {
        fields: [ 'get_started' ]
      };

      bot.deleteGetStartedButton();
      expect(spy.calledWith(expected, 'DELETE')).to.equal(true);
    });

    it('can set the Persistent Menu', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const buttons = [
        {
          title: 'My Account',
          type: 'nested',
          call_to_actions: [
            {
              title: 'Pay Bill',
              type: 'postback',
              payload: 'PAYBILL_PAYLOAD'
            },
            {
              title: 'History',
              type: 'postback',
              payload: 'HISTORY_PAYLOAD'
            },
            {
              title: 'Contact Info',
              type: 'postback',
              payload: 'CONTACT_INFO_PAYLOAD'
            }
          ]
        },
        {
          title: 'Go to Website',
          type: 'web_url',
          url: 'http://purple.com'
        }
      ];
      const disableInput = false;
      const expected = {
        persistent_menu: [{
          locale: 'default',
          composer_input_disabled: disableInput,
          call_to_actions: buttons
        }]
      };

      bot.setPersistentMenu(buttons, disableInput);
      expect(spy.calledWith(expected)).to.equal(true);
    });

    it('can delete the Persistent Menu', () => {
      const spy = sinon.spy(bot, 'sendProfileRequest');
      const expected = {
        fields: [ 'persistent_menu' ]
      };

      bot.deletePersistentMenu();
      expect(spy.calledWith(expected, 'DELETE')).to.equal(true);
    });
  });

  describe('Checkbox Plugin support', () => {
    it('uses the user_ref param as the recipient when replying to a checkbox authentication event', () => {
      const spy = sinon.spy(bot, 'sendMessage');
      const event = {
        "recipient": {
          "id": "PAGE_ID"
        },
        "timestamp": 1234567890,
        "optin": {
          "ref": "PASS_THROUGH_PARAM",
          "user_ref": "UNIQUE_REF_PARAM"
        }
      };
      const expectedRecipient = { user_ref: 'UNIQUE_REF_PARAM' };
      const expectedMessage = { text: 'hello' };

      bot.on('authentication', (payload, chat) => {
        chat.say('hello');
      });
      bot._handleEvent('authentication', event);
      expect(spy.calledWith(expectedRecipient, expectedMessage)).to.equal(true);
    });

    it('uses the sender ID if the authentication event contains one', () => {
      const spy = sinon.spy(bot, 'sendMessage');
      const event = {
        "sender": {
          "id": "USER_ID"
        },
        "recipient": {
          "id": "PAGE_ID"
        },
        "timestamp": 1234567890,
        "optin": {
          "ref":"PASS_THROUGH_PARAM"
        }
      };
      const expectedRecipient = 'USER_ID';
      const expectedMessage = { text: 'hello' };

      bot.on('authentication', (payload, chat) => {
        chat.say('hello');
      });
      bot._handleEvent('authentication', event);
      expect(spy.calledWith(expectedRecipient, expectedMessage)).to.equal(true);
    });
  });
});
<|endoftext|>


<commit_before>// shadow dom css isolation simulator polyfill
// MIT License, ben@latenightsketches.com
(function(){
  // A buffer must be made to bridge the elements to negated CSS selectors
  var BUFFER_ATTR = 'data-css-selector';
  // Cache loaded stylesheet data
  var parsedSheets;
  // Current inserted style element
  var processedStyleEl;
  // List of elements currently living in the shadows
  var shadowChildren = [];

  // Main public method
  // @param {string} nodeName - Element type to isolate styles of child DOM
  document.isolateShadowStyles = function(nodeName){
    shadowChildren = shadowChildren.concat(findChildren(nodeName));
    if(parsedSheets === undefined){
      parsedSheets = 'loading';
      loadAllStyleSheets(function(data){
        parsedSheets = data;
        updateShadowCss(shadowChildren, parsedSheets);
      });
    }else if(parsedSheets !== 'loading'){
      updateShadowCss(shadowChildren, parsedSheets);
    };
  };

  // Watch for changes to shadowed elements
  var observer = new MutationObserver(function(mutations){
    //updateShadowStyles();
    mutations.forEach(function(mutation){
      if(mutation.type === 'attributes' && mutation.attributeName !== BUFFER_ATTR){
        updateShadowCss(shadowChildren, parsedSheets);
      }else if(mutation.type === 'childList'){
        Array.prototype.forEach.call(mutation.addedNodes, function(added){
          shadowChildren.push(added);
        });
        Array.prototype.forEach.call(mutation.removedNodes, function(removed){
          var index = shadowChildren.indexOf(removed);
          if(index > -1){
            shadowChildren.splice(index, 1);
          };
        });
        updateShadowCss(shadowChildren, parsedSheets);
      };
    });
  });

  // Modify document styles to place selected element child DOM in a simulated
  // shadow.
  // @param {[element]} children - Which elements to simulate child DOM in
  //                               shadow
  // @param {[obj]}    parsedCss - Output from loadAllStyleSheets()
  var updateShadowCss = function(children, parsedCss){
    Array.prototype.forEach.call(children, function(child){
      child.removeAttribute && child.removeAttribute(BUFFER_ATTR);
    });
    var output = '';
    parsedCss.forEach(function(sheetMeta){
      var stylesheet = css.parse(sheetMeta.data);
      stylesheet.stylesheet.rules.forEach(function(rule, ruleIndex){
        if(rule.type === 'rule'){
          rule.selectors.forEach(function(selector, selectorIndex){
            if(selector.indexOf('::shadow') > -1){
              // Shadow DOM is simply Child DOM
              selector = selector.replace('::shadow', '');
              stylesheet.stylesheet.rules[ruleIndex].
                selectors[selectorIndex] = selector;
            }else{
              // Negate selector for all Shadow DOM
              Array.prototype.forEach.call(children, function(child){
                observer.observe(child, {attributes: true, childList: true});
                if(child.matches && child.matches(selector)){
                  var selectorId;
                  // generate selector unique id if doesnt exist
                  if(selector.indexOf(':not([' + BUFFER_ATTR + '*=') === -1){
                    selectorId = randomString(10);
                    selector = selector +
                      ':not([' + BUFFER_ATTR + '*="' + selectorId + '"])';
                    stylesheet.stylesheet.rules[ruleIndex].
                      selectors[selectorIndex] = selector;
                  }else{
                    // selectorId is at end, capped by '"])'
                    selectorId = selector.substr(-13, 10);
                  };
                  // add unique id to child if not included
                  var attrVal = child.getAttribute(BUFFER_ATTR);
                  if(!attrVal){
                    child.setAttribute(BUFFER_ATTR, selectorId);
                  }else if(attrVal.indexOf(selectorId) === -1){
                    child.setAttribute(BUFFER_ATTR, attrVal + selectorId);
                  };
                };
              });
            };
          });
        };
      });
      output += css.stringify(stylesheet);

      if(sheetMeta.el){
        // Remove old stylesheet element
        sheetMeta.el.parentNode.removeChild(sheetMeta.el);
        delete sheetMeta.el;
      };
    });

    // Remove old processed styles
    if(processedStyleEl){
      processedStyleEl.parentNode.removeChild(processedStyleEl);
    };
    // Insert updated styles
    processedStyleEl = insertStyleElement(output);
  };

  // Load all the page's stylesheets and then call css event on document
  // Requests are made synchronously to allow script to run before page render
  // @param {function} callback - One parameter: array output {el, data}
  var loadAllStyleSheets = function(callback) {
    var parsedCss = [];
    //read style tags
    var styleElements = document.querySelectorAll('style');
    Array.prototype.forEach.call(styleElements, function(style){
      parsedCss.push({
        data: style.innerHTML,
        el: style
      });
    });
    //read linked stylesheets
    var linkCount = 0, linkReturn = 0;
    var linkElements = document.querySelectorAll("link[rel=stylesheet]");
    Array.prototype.forEach.call(linkElements, function(link){
      linkCount++;
      var path = link.href.slice(0, link.href.lastIndexOf("/") + 1);
      var request = new XMLHttpRequest();
      var onComplete = function(){
        linkReturn++;
        if(linkCount === linkReturn){
          callback(parsedCss);
        };
      };

      request.onload = function() {
        if(request.status >= 200 && request.status < 400){
          // Success!
          resp = request.responseText;
          parsedCss.push({
            data: request.responseText,
            el: link
          });
        }else{
          // We reached our target server, but it returned an error
        };
        onComplete();
      };

      request.onerror = function() {
        // There was a connection error of some sort
        onComplete();
      };
      // Perform synchronous request (should already be cached)
      request.open('GET', link.href, false);
      request.send(null);
    });
  };

  // @param {element} el - Child DOM element of simulated shadow
  // @return {elemet} - Ancestor element
  var findShadowRoot = function(el){
    var parent = el.parentNode;
    while(!el.parentNode.simulatedShadow){
      parent = parent.parentNode;
    };
    return parent;
  };

  // @param {string} selector - Element node name
  // @return {array} - Child Elements
  var findChildren = function(selector) {
    var roots = document.querySelectorAll(selector);
    var children = [];
    Array.prototype.forEach.call(roots, function(rootEl){
      rootEl.simulatedShadow = true;
      observer.observe(rootEl, {attributes: true, childList: true});
      var found = rootEl.querySelectorAll('*');
      Array.prototype.forEach.call(found, function(child){
        children.push(child);
      });
    });
    return children;
  };

  // @param {string} data - CSS Text
  // @return {element} - Inserted style element
  var insertStyleElement = function(data){
    var head = document.head || document.getElementsByTagName('head')[0];
    var newStyle = document.createElement('style');

    newStyle.type = 'text/css';
    if(newStyle.styleSheet){
      newStyle.styleSheet.cssText = data;
    }else{
      newStyle.appendChild(document.createTextNode(data));
    };

    head.appendChild(newStyle);
    return newStyle;
  };

  // @param {integer} length
  // @return {string}
  var randomString = function(length){
    if(length === undefined){
      length = 10;
    };
    var text = "",
        possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789";
    for( var i=0; i < length; i++ ){
      text += possible.charAt(Math.floor(Math.random() * possible.length));
    };
    return text;
  };

})();
<commit_msg>Only update styles on elements that change<commit_after>// shadow dom css isolation simulator polyfill
// MIT License, ben@latenightsketches.com
(function(){
  // A buffer must be made to bridge the elements to negated CSS selectors
  var BUFFER_ATTR = 'data-css-selector';
  // Cache loaded stylesheet data
  var parsedSheets;
  // Current inserted style element
  var processedStyleEl;

  // Main public method
  // @param {string} nodeName - Element type to isolate styles of child DOM
  document.isolateShadowStyles = function(nodeName){
    var shadowChildren = findChildren(nodeName);
    if(parsedSheets === undefined){
      parsedSheets = 'loading';
      loadAllStyleSheets(function(data){
        parsedSheets = data;
        updateShadowCss(shadowChildren, parsedSheets);
      });
    }else if(parsedSheets !== 'loading'){
      updateShadowCss(shadowChildren, parsedSheets);
    };
  };

  // Watch for changes to shadowed elements
  var observer = new MutationObserver(function(mutations){
    mutations.forEach(function(mutation){
      if(mutation.type === 'attributes' && mutation.attributeName !== BUFFER_ATTR){
        updateShadowCss([mutation.target], parsedSheets);
      }else if(mutation.type === 'childList'){
        updateShadowCss(mutation.addedNodes, parsedSheets);
      };
    });
  });

  // Modify document styles to place selected element child DOM in a simulated
  // shadow.
  // @param {[element]} children - Which elements to simulate child DOM in
  //                               shadow
  // @param {[obj]}    parsedCss - Output from loadAllStyleSheets()
  var updateShadowCss = function(children, parsedCss){
    var output = '';
    parsedCss.forEach(function(sheetMeta){
      var stylesheet = css.parse(sheetMeta.data);
      stylesheet.stylesheet.rules.forEach(function(rule, ruleIndex){
        if(rule.type === 'rule'){
          rule.selectors.forEach(function(selector, selectorIndex){
            if(selector.indexOf('::shadow') > -1){
              // Shadow DOM is simply Child DOM
              selector = selector.replace('::shadow', '');
              stylesheet.stylesheet.rules[ruleIndex].
                selectors[selectorIndex] = selector;
            }else{
              var selectorKey = selector + '@' +
                                  rule.position.start.line + ':' +
                                  rule.position.start.column;
              var replacedIndex = sheetMeta.replacedSelectors.indexOf(selectorKey);
              if(replacedIndex > -1){
                var selectorId = sheetMeta.selectorIds[replacedIndex];
                selector = selector +
                  ':not([' + BUFFER_ATTR + '*="' + selectorId + '"])';
                stylesheet.stylesheet.rules[ruleIndex].
                  selectors[selectorIndex] = selector;
              };
              // Negate selector for all Shadow DOM
              Array.prototype.forEach.call(children, function(child){
                observer.observe(child, {attributes: true, childList: true});
                if(child.matches && child.matches(selector)){
                  var selectorId;
                  if(replacedIndex > -1){
                    selectorId = sheetMeta.selectorIds[replacedIndex];
                  }else if(selector.indexOf(':not([' + BUFFER_ATTR + '*=') === -1){
                    // generate selector unique id if doesnt exist
                    selectorId = randomString(10);
                    selector = selector +
                      ':not([' + BUFFER_ATTR + '*="' + selectorId + '"])';
                    stylesheet.stylesheet.rules[ruleIndex].
                      selectors[selectorIndex] = selector;
                    sheetMeta.replacedSelectors.push(selectorKey);
                    sheetMeta.selectorIds.push(selectorId);
                  }else{
                    // selectorId is at end, capped by '"])'
                    selectorId = selector.substr(-13, 10);
                  };
                  // add unique id to child if not included
                  var attrVal = child.getAttribute(BUFFER_ATTR);
                  if(!attrVal){
                    child.setAttribute(BUFFER_ATTR, selectorId);
                  }else if(attrVal.indexOf(selectorId) === -1){
                    child.setAttribute(BUFFER_ATTR, attrVal + selectorId);
                  };
                };
              });
            };
          });
        };
      });
      output += css.stringify(stylesheet);

      if(sheetMeta.el){
        // Remove old stylesheet element
        sheetMeta.el.parentNode.removeChild(sheetMeta.el);
        delete sheetMeta.el;
      };
    });

    // Remove old processed styles
    if(processedStyleEl){
      processedStyleEl.parentNode.removeChild(processedStyleEl);
    };
    // Insert updated styles
    processedStyleEl = insertStyleElement(output);
  };

  // Load all the page's stylesheets and then call css event on document
  // Requests are made synchronously to allow script to run before page render
  // @param {function} callback - One parameter: array output {el, data}
  var loadAllStyleSheets = function(callback) {
    var parsedCss = [];
    //read style tags
    var styleElements = document.querySelectorAll('style');
    Array.prototype.forEach.call(styleElements, function(style){
      parsedCss.push({
        data: style.innerHTML,
        el: style,
        replacedSelectors: [],
        selectorIds: []
      });
    });
    //read linked stylesheets
    var linkCount = 0, linkReturn = 0;
    var linkElements = document.querySelectorAll("link[rel=stylesheet]");
    Array.prototype.forEach.call(linkElements, function(link){
      linkCount++;
      var path = link.href.slice(0, link.href.lastIndexOf("/") + 1);
      var request = new XMLHttpRequest();
      var onComplete = function(){
        linkReturn++;
        if(linkCount === linkReturn){
          callback(parsedCss);
        };
      };

      request.onload = function() {
        if(request.status >= 200 && request.status < 400){
          // Success!
          resp = request.responseText;
          parsedCss.push({
            data: request.responseText,
            el: link,
            replacedSelectors: [],
            selectorIds: []
          });
        }else{
          // We reached our target server, but it returned an error
        };
        onComplete();
      };

      request.onerror = function() {
        // There was a connection error of some sort
        onComplete();
      };
      // Perform synchronous request (should already be cached)
      request.open('GET', link.href, false);
      request.send(null);
    });
  };

  // @param {string} selector - Element node name
  // @return {array} - Child Elements
  var findChildren = function(selector) {
    var roots = document.querySelectorAll(selector);
    var children = [];
    Array.prototype.forEach.call(roots, function(rootEl){
      observer.observe(rootEl, {attributes: true, childList: true});
      var found = rootEl.querySelectorAll('*');
      Array.prototype.forEach.call(found, function(child){
        children.push(child);
      });
    });
    return children;
  };

  // @param {string} data - CSS Text
  // @return {element} - Inserted style element
  var insertStyleElement = function(data){
    var head = document.head || document.getElementsByTagName('head')[0];
    var newStyle = document.createElement('style');

    newStyle.type = 'text/css';
    if(newStyle.styleSheet){
      newStyle.styleSheet.cssText = data;
    }else{
      newStyle.appendChild(document.createTextNode(data));
    };

    head.appendChild(newStyle);
    return newStyle;
  };

  // @param {integer} length
  // @return {string}
  var randomString = function(length){
    if(length === undefined){
      length = 10;
    };
    var text = "",
        possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789";
    for( var i=0; i < length; i++ ){
      text += possible.charAt(Math.floor(Math.random() * possible.length));
    };
    return text;
  };

})();
<|endoftext|>


<commit_before>'use strict';

angular.module('app')
    .controller('rgiDocumentTableCtrl', function (
        _,
        $scope,
        $rootScope,
        rgiDialogFactory,
        rgiAssessmentSrvc,
        rgiDocumentSrvc,
        rgiHttpResponseProcessorSrvc,
        rgiNotifier
    ) {
        var currentPage, totalPages, limit = 100;

        $scope.busy = false;
        $scope.assessment_filter_options = [];
        $scope.assessment_filter = '';

        rgiAssessmentSrvc.query({}, function (assessments) {
            if(assessments.reason) {
                rgiNotifier.error('No assessments');
            } else {
                assessments.forEach(function(assessment) {
                    $scope.assessment_filter_options.push({
                        value: assessment.assessment_ID,
                        text: assessment.country + ' ' + assessment.year + ' ' + assessment.version
                    });
                });
            }
        });

        $scope.$watch('assessment_filter', function(assessment) {
            currentPage = 0;
            totalPages = 0;
            var searchOptions = {skip: currentPage, limit: limit};

            if(assessment) {
                searchOptions.assessments = assessment;
            }

            rgiDocumentSrvc.queryCached(searchOptions, function (response) {
                if(response.reason) {
                    rgiNotifier.error('Load document data failure');
                } else {
                    $scope.count = response.count;
                    $scope.documents = response.data;

                    if($scope.documents.length === 0) {
                        rgiNotifier.error('No documents uploaded');
                    }

                    totalPages = Math.ceil(response.count / limit);
                    currentPage = 1;
                }
            }, rgiHttpResponseProcessorSrvc.getDefaultHandler('Load document data failure'));
        });

        $scope.loadMoreDocs = function() {
            if ($scope.busy) {
                return;
            }

            $scope.busy = true;

            if(currentPage < totalPages) {
                var searchOptions = {skip: currentPage, limit: limit};

                if($scope.assessment_filter) {
                    searchOptions.assessments = $scope.assessment_filter;
                }

                rgiDocumentSrvc.query(searchOptions, function (response) {
                    if(response.reason) {
                        rgiNotifier.error('Documents loading failure');
                    } else {
                        $scope.documents = _.union($scope.documents, response.data);
                        currentPage = currentPage + 1;
                        $scope.busy = false;
                    }
                }, rgiHttpResponseProcessorSrvc.getDefaultHandler('Load document data failure'));
            }
        };

        $scope.deleteDocument = function(doc) {
            rgiDialogFactory.deleteDocument($scope, doc);
        };
    });
<commit_msg>add 'Unlink Document' feature<commit_after>'use strict';

angular.module('app')
    .controller('rgiDocumentTableCtrl', ['_', '$scope', '$rootScope', 'rgiDialogFactory', 'rgiAssessmentSrvc', 'rgiDocumentSrvc', 'rgiHttpResponseProcessorSrvc', 'rgiNotifier', function (
        _,
        $scope,
        $rootScope,
        rgiDialogFactory,
        rgiAssessmentSrvc,
        rgiDocumentSrvc,
        rgiHttpResponseProcessorSrvc,
        rgiNotifier
    ) {
        var currentPage, totalPages, limit = 100;

        $scope.busy = false;
        $scope.assessment_filter_options = [];
        $scope.assessment_filter = '';

        rgiAssessmentSrvc.query({}, function (assessments) {
            if(assessments.reason) {
                rgiNotifier.error('No assessments');
            } else {
                assessments.forEach(function(assessment) {
                    $scope.assessment_filter_options.push({
                        value: assessment.assessment_ID,
                        text: assessment.country + ' ' + assessment.year + ' ' + assessment.version
                    });
                });
            }
        });

        $scope.$watch('assessment_filter', function(assessment) {
            currentPage = 0;
            totalPages = 0;
            var searchOptions = {skip: currentPage, limit: limit};

            if(assessment) {
                searchOptions.assessments = assessment;
            }

            rgiDocumentSrvc.queryCached(searchOptions, function (response) {
                if(response.reason) {
                    rgiNotifier.error('Load document data failure');
                } else {
                    $scope.count = response.count;
                    $scope.documents = response.data;

                    if($scope.documents.length === 0) {
                        rgiNotifier.error('No documents uploaded');
                    }

                    totalPages = Math.ceil(response.count / limit);
                    currentPage = 1;
                }
            }, rgiHttpResponseProcessorSrvc.getDefaultHandler('Load document data failure'));
        });

        $scope.loadMoreDocs = function() {
            if ($scope.busy) {
                return;
            }

            $scope.busy = true;

            if(currentPage < totalPages) {
                var searchOptions = {skip: currentPage, limit: limit};

                if($scope.assessment_filter) {
                    searchOptions.assessments = $scope.assessment_filter;
                }

                rgiDocumentSrvc.query(searchOptions, function (response) {
                    if(response.reason) {
                        rgiNotifier.error('Documents loading failure');
                    } else {
                        $scope.documents = _.union($scope.documents, response.data);
                        currentPage = currentPage + 1;
                        $scope.busy = false;
                    }
                }, rgiHttpResponseProcessorSrvc.getDefaultHandler('Load document data failure'));
            }
        };

        $scope.deleteDocument = function(doc) {
            rgiDialogFactory.deleteDocument($scope, doc);
        };

        $scope.unlink = function(doc) {
            rgiDialogFactory.unlinkDocument($scope, doc);
        };
    }]);
<|endoftext|>