<commit_before>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Children, Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import CSSClassnames, { namespace } from '../utils/CSSClassnames';

import Box from './Box';

const CLASS_ROOT = CSSClassnames.BUTTON;

function getHoverModifier(hoverIndicator) {
  if (hoverIndicator) {
    if (typeof hoverIndicator === 'object') {
      if (hoverIndicator.background) {
        if (typeof hoverIndicator.background === 'string') {
          const prefix = `${namespace}background-hover-color-index-`;
          return `${prefix}${hoverIndicator.background}`;
        }
        return `${CLASS_ROOT}--hover-background`;
      }
    } else if (typeof hoverIndicator === 'string') {
      return (`${CLASS_ROOT}--hover-${hoverIndicator}`);
    }
  }
}

export default class Button extends Component {

  constructor () {
    super();
    this._onClick = this._onClick.bind(this);
    this._onMouseDown = this._onMouseDown.bind(this);
    this._onMouseUp = this._onMouseDown.bind(this);
    this._onFocus = this._onFocus.bind(this);
    this._onBlur = this._onBlur.bind(this);
    this.state = {
      mouseActive: false,
      focus: false
    };
  }

  _onClick (event) {
    const { method, onClick, path} = this.props;
    const { router } = this.context;

    event.preventDefault();

    if ('push' === method) {
      (router.history || router).push(path);
    } else if ('replace' === method) {
      (router.history || router).replace(path);
    }

    if (onClick) {
      onClick(...arguments);
    }
  }

  _onMouseDown (event) {
    const { onMouseDown } = this.props;
    this.setState({ mouseActive: true });
    if (onMouseDown) {
      onMouseDown(event);
    }
  }

  _onMouseUp (event) {
    const { onMouseUp } = this.props;
    this.setState({ mouseActive: false });
    if (onMouseUp) {
      onMouseUp(event);
    }
  }

  _onFocus (event) {
    const { onFocus } = this.props;
    const { mouseActive } = this.state;
    if (mouseActive === false) {
      this.setState({ focus: true });
    }
    if (onFocus) {
      onFocus(event);
    }
  }

  _onBlur (event) {
    const { onBlur } = this.props;
    this.setState({ focus: false });
    if (onBlur) {
      onBlur(event);
    }
  }

  render () {
    const {
      a11yTitle, accent, align, box, children, className, critical, fill,
      hoverIndicator, href, icon, label, onClick, path, plain, primary, reverse,
      secondary, type, ...props
    } = this.props;
    delete props.method;
    const { router } = this.context;

    let buttonIcon;
    if (icon) {
      buttonIcon = <span className={`${CLASS_ROOT}__icon`}>{icon};
    }

    let buttonLabel;
    if (label) {
      buttonLabel = <span className={`${CLASS_ROOT}__label`}>{label};
    }

    let adjustedHref;
    if (router && router.createPath) {
      adjustedHref = (path && router) ?
        router.createPath(path) : href;
    } else {
      adjustedHref = (path && router && router.history) ?
        router.history.createHref(
          { pathname: path }
        ) : href;
    }

    const classes = classnames(
      CLASS_ROOT,
      {
        [`${CLASS_ROOT}--box`]: box,
        [`${CLASS_ROOT}--critical`]: critical,
        [`${CLASS_ROOT}--focus`]: this.state.focus,
        [`${CLASS_ROOT}--primary`]: primary,
        [`${CLASS_ROOT}--secondary`]: secondary,
        [`${CLASS_ROOT}--accent`]: accent,
        [`${CLASS_ROOT}--disabled`]:
          !onClick &&
          !adjustedHref &&
          !['reset', 'submit'].includes(type),
        [`${CLASS_ROOT}--fill`]: fill,
        [`${CLASS_ROOT}--plain`]: (
          plain || box || Children.count(children) > 0 || (icon && ! label)
        ),
        [`${CLASS_ROOT}--align-${align}`]: align,
        [getHoverModifier(hoverIndicator)]: hoverIndicator
      },
      className
    );

    let adjustedOnClick = (path && router ? this._onClick : onClick);

    let Tag = adjustedHref ? 'a' : 'button';
    let buttonType;
    if (!adjustedHref) {
      buttonType = type;
    }

    let boxProps;
    if (box) {
      // Let the root element of the Button be a Box element with tag prop
      boxProps = {
        tag: Tag
      };
      Tag = Box;
    }

    const first = reverse ? buttonLabel : buttonIcon;
    const second = reverse ? buttonIcon : buttonLabel;

    return (
      <Tag {...props} {...boxProps} href={adjustedHref} type={buttonType}
        className={classes} aria-label={a11yTitle}
        onClick={adjustedOnClick}
        disabled={
          !onClick &&
          !adjustedHref &&
          !['reset', 'submit'].includes(type)
        }
        onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}
        onFocus={this._onFocus} onBlur={this._onBlur}>
        {first}
        {second}
        {children}
      </Tag>
    );
  }
}

Button.propTypes = {
  a11yTitle: PropTypes.string,
  accent: PropTypes.bool,
  align: PropTypes.oneOf(['start', 'center', 'end']),
  box: PropTypes.bool,
  critical: PropTypes.bool,
  fill: PropTypes.bool,
  hoverIndicator: PropTypes.oneOfType([
    PropTypes.oneOf(['background']),
    PropTypes.shape({
      background: PropTypes.oneOfType([
        PropTypes.bool,
        PropTypes.string
      ])
    })
  ]),
  href: PropTypes.string,
  icon: PropTypes.element,
  label: PropTypes.node,
  method: PropTypes.oneOf(['push', 'replace']),
  onClick: PropTypes.func,
  path: PropTypes.string,
  plain: PropTypes.bool,
  primary: PropTypes.bool,
  reverse: PropTypes.bool,
  secondary: PropTypes.bool,
  type: PropTypes.oneOf(['button', 'reset', 'submit'])
};

Button.defaultProps = {
  method: 'push',
  type: 'button'
};

Button.contextTypes = {
  router: PropTypes.object
};
<commit_msg>Allow routed Button links to open in new tab via CMD + Click (#1373)<commit_after>// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Children, Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import CSSClassnames, { namespace } from '../utils/CSSClassnames';

import Box from './Box';

const CLASS_ROOT = CSSClassnames.BUTTON;

function getHoverModifier(hoverIndicator) {
  if (hoverIndicator) {
    if (typeof hoverIndicator === 'object') {
      if (hoverIndicator.background) {
        if (typeof hoverIndicator.background === 'string') {
          const prefix = `${namespace}background-hover-color-index-`;
          return `${prefix}${hoverIndicator.background}`;
        }
        return `${CLASS_ROOT}--hover-background`;
      }
    } else if (typeof hoverIndicator === 'string') {
      return (`${CLASS_ROOT}--hover-${hoverIndicator}`);
    }
  }
}

export default class Button extends Component {

  constructor () {
    super();
    this._onClick = this._onClick.bind(this);
    this._onMouseDown = this._onMouseDown.bind(this);
    this._onMouseUp = this._onMouseDown.bind(this);
    this._onFocus = this._onFocus.bind(this);
    this._onBlur = this._onBlur.bind(this);
    this.state = {
      mouseActive: false,
      focus: false
    };
  }

  _onClick (event) {
    const { method, onClick, path} = this.props;
    const { router } = this.context;
    const modifierKey = event.ctrlKey || event.metaKey;

    if (modifierKey && !disabled && !onClick) {
      return true;
    }

    event.preventDefault();

    if ('push' === method) {
      (router.history || router).push(path);
    } else if ('replace' === method) {
      (router.history || router).replace(path);
    }

    if (onClick) {
      onClick(...arguments);
    }
  }

  _onMouseDown (event) {
    const { onMouseDown } = this.props;
    this.setState({ mouseActive: true });
    if (onMouseDown) {
      onMouseDown(event);
    }
  }

  _onMouseUp (event) {
    const { onMouseUp } = this.props;
    this.setState({ mouseActive: false });
    if (onMouseUp) {
      onMouseUp(event);
    }
  }

  _onFocus (event) {
    const { onFocus } = this.props;
    const { mouseActive } = this.state;
    if (mouseActive === false) {
      this.setState({ focus: true });
    }
    if (onFocus) {
      onFocus(event);
    }
  }

  _onBlur (event) {
    const { onBlur } = this.props;
    this.setState({ focus: false });
    if (onBlur) {
      onBlur(event);
    }
  }

  render () {
    const {
      a11yTitle, accent, align, box, children, className, critical, fill,
      hoverIndicator, href, icon, label, onClick, path, plain, primary, reverse,
      secondary, type, ...props
    } = this.props;
    delete props.method;
    const { router } = this.context;

    let buttonIcon;
    if (icon) {
      buttonIcon = <span className={`${CLASS_ROOT}__icon`}>{icon};
    }

    let buttonLabel;
    if (label) {
      buttonLabel = <span className={`${CLASS_ROOT}__label`}>{label};
    }

    let adjustedHref;
    if (router && router.createPath) {
      adjustedHref = (path && router) ?
        router.createPath(path) : href;
    } else {
      adjustedHref = (path && router && router.history) ?
        router.history.createHref(
          { pathname: path }
        ) : href;
    }

    const classes = classnames(
      CLASS_ROOT,
      {
        [`${CLASS_ROOT}--box`]: box,
        [`${CLASS_ROOT}--critical`]: critical,
        [`${CLASS_ROOT}--focus`]: this.state.focus,
        [`${CLASS_ROOT}--primary`]: primary,
        [`${CLASS_ROOT}--secondary`]: secondary,
        [`${CLASS_ROOT}--accent`]: accent,
        [`${CLASS_ROOT}--disabled`]:
          !onClick &&
          !adjustedHref &&
          !['reset', 'submit'].includes(type),
        [`${CLASS_ROOT}--fill`]: fill,
        [`${CLASS_ROOT}--plain`]: (
          plain || box || Children.count(children) > 0 || (icon && ! label)
        ),
        [`${CLASS_ROOT}--align-${align}`]: align,
        [getHoverModifier(hoverIndicator)]: hoverIndicator
      },
      className
    );

    let adjustedOnClick = (path && router ? this._onClick : onClick);

    let Tag = adjustedHref ? 'a' : 'button';
    let buttonType;
    if (!adjustedHref) {
      buttonType = type;
    }

    let boxProps;
    if (box) {
      // Let the root element of the Button be a Box element with tag prop
      boxProps = {
        tag: Tag
      };
      Tag = Box;
    }

    const first = reverse ? buttonLabel : buttonIcon;
    const second = reverse ? buttonIcon : buttonLabel;

    return (
      <Tag {...props} {...boxProps} href={adjustedHref} type={buttonType}
        className={classes} aria-label={a11yTitle}
        onClick={adjustedOnClick}
        disabled={
          !onClick &&
          !adjustedHref &&
          !['reset', 'submit'].includes(type)
        }
        onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}
        onFocus={this._onFocus} onBlur={this._onBlur}>
        {first}
        {second}
        {children}
      </Tag>
    );
  }
}

Button.propTypes = {
  a11yTitle: PropTypes.string,
  accent: PropTypes.bool,
  align: PropTypes.oneOf(['start', 'center', 'end']),
  box: PropTypes.bool,
  critical: PropTypes.bool,
  fill: PropTypes.bool,
  hoverIndicator: PropTypes.oneOfType([
    PropTypes.oneOf(['background']),
    PropTypes.shape({
      background: PropTypes.oneOfType([
        PropTypes.bool,
        PropTypes.string
      ])
    })
  ]),
  href: PropTypes.string,
  icon: PropTypes.element,
  label: PropTypes.node,
  method: PropTypes.oneOf(['push', 'replace']),
  onClick: PropTypes.func,
  path: PropTypes.string,
  plain: PropTypes.bool,
  primary: PropTypes.bool,
  reverse: PropTypes.bool,
  secondary: PropTypes.bool,
  type: PropTypes.oneOf(['button', 'reset', 'submit'])
};

Button.defaultProps = {
  method: 'push',
  type: 'button'
};

Button.contextTypes = {
  router: PropTypes.object
};
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name Teem.controller:NeedsCtrl
 * @description
 * # Needs Ctrl
 * Show Needs for a given project
 */

angular.module('Teem')
  .config(['$routeProvider', function($routeProvider) {
    $routeProvider
      .when('/communities/:comId/projects/:id/needs', {
        templateUrl: 'needs/index.html',
        controller: 'NeedsCtrl'
      });
  }])
  .directive(
    'needDisplay',
    function(SessionSvc, ProjectsSvc, $route){
      return {
        require: '^needList',
        scope: {
          need: '='
        },
        link: function (scope, element, attrs, needsCtrl) {
          scope.toggleCompleted = function (need, event) {

            if (!scope.project.isContributor()){
              return;
            }

            if (need.completed === 'add'){
              scope.focusElem(event);

              return;
            }
            var completed = (need.completed === 'true') ? true : false;
            need.completed = (!completed).toString();
            if (completed) {
              need.completionDate = new Date().toString();
            } else {
              need.completionDate = '';
            }

          };

          scope.updateNeed = function(need) {
            if (need.completed === 'add'){
              need.completed = 'false';
              needsCtrl.addNeed(need);
              scope.need = {completed: 'add', text: ''};
            }
            else if (need.text === ''){
              needsCtrl.removeNeed(need);
            }
          };

          SessionSvc.onLoad(function(){
            ProjectsSvc.findByUrlId($route.current.params.id).then(
              function(project){
                scope.project = project;
              }
            );
          });

          scope.keyEventsHandler = function(event){
            if (event.which === 13) {
              event.target.blur();
            }
            if ((event.which === 8) && (scope.need.text === '')) {
              event.preventDefault();
              scope.updateNeed(scope.need);
            }
          };

          scope.keyDown = function(event){
            if (event.which === 13) {
              scope.sendComment();
            }
          };

          scope.focusElem = function(event){
            console.log(event.target.parentNode.parentNode.children);
            event.target.parentNode.parentNode.children[1].children[0].focus();
          };

          scope.toggleCommentsVisibility = function(n){
            needsCtrl.toggleCommentsVisibility(n);
          };

          scope.newComment = {
            text: ''
          };

          scope.areCommentsVisible = needsCtrl.areCommentsVisible;

          scope.sendComment = function(){
            SessionSvc.loginRequired(function() {
              ProjectsSvc.findByUrlId($route.current.params.id).then(function(project){
                project.addNeedComment(scope.need, scope.newComment.text);
                scope.newComment.text = '';
              });
            });
          };

          scope.hour = needsCtrl.hour;

          scope.newComments = function(need){
            if (!need.comments || !scope.project){
              return false;
            }

            var prevAccess = new Date(scope.project.getTimestampAccess().needs.prev);
            var lastComment = new Date(need.comments[need.comments.length -1].time);
            return prevAccess < lastComment;
          };

          scope.isNewNeed = function(need){
            if (!scope.project){
              return false;
            }
            var prevAccess = new Date(scope.project.getTimestampAccess().needs.prev);
            var needTime = new Date(need.time);
            return prevAccess < needTime;
          };
        },
        templateUrl: 'needs/need.html',
        transclude: true
      };
    }
  ).directive(
    'needList',
    function () {
      return {
        templateUrl: 'needs/list.html',
        transclude: true,
        scope: {
          project: '=',
          needs: '='
        },
        controller: function($scope, $route, SessionSvc, ProjectsSvc, time) {
          this.addNeed = function (need) {
            console.dir(need);
            if (need.text !== undefined && need.text !== ''){
              need.author = SessionSvc.users.current();
              need.time = (new Date()).toJSON();

              $scope.needs.push(need);
              $scope.project.setTimestampAccess('needs', true);
            }
          };
          this.removeNeed = function (need) {
            var i = $scope.needs.indexOf(need);

            $scope.needs.splice(i,1);
          };

          this.comments = {};

          var comments = this.comments;

          this.toggleCommentsVisibility = function toggleCommentsVisibility(need) {
            comments.visible = (comments.visible === need) ? null : need;
          };

          this.areCommentsVisible = function areCommentsVisible(need) {
            return comments.visible === need;
          };

          this.hour = function(comment) {
            return time.hour(new Date(comment.time));
          };
        }
      };
    });
<commit_msg>check if timestamp exists for needs and needs coments<commit_after>'use strict';

/**
 * @ngdoc function
 * @name Teem.controller:NeedsCtrl
 * @description
 * # Needs Ctrl
 * Show Needs for a given project
 */

angular.module('Teem')
  .config(['$routeProvider', function($routeProvider) {
    $routeProvider
      .when('/communities/:comId/projects/:id/needs', {
        templateUrl: 'needs/index.html',
        controller: 'NeedsCtrl'
      });
  }])
  .directive(
    'needDisplay',
    function(SessionSvc, ProjectsSvc, $route){
      return {
        require: '^needList',
        scope: {
          need: '='
        },
        link: function (scope, element, attrs, needsCtrl) {
          scope.toggleCompleted = function (need, event) {

            if (!scope.project.isContributor()){
              return;
            }

            if (need.completed === 'add'){
              scope.focusElem(event);

              return;
            }
            var completed = (need.completed === 'true') ? true : false;
            need.completed = (!completed).toString();
            if (completed) {
              need.completionDate = new Date().toString();
            } else {
              need.completionDate = '';
            }

          };

          scope.updateNeed = function(need) {
            if (need.completed === 'add'){
              need.completed = 'false';
              needsCtrl.addNeed(need);
              scope.need = {completed: 'add', text: ''};
            }
            else if (need.text === ''){
              needsCtrl.removeNeed(need);
            }
          };

          SessionSvc.onLoad(function(){
            ProjectsSvc.findByUrlId($route.current.params.id).then(
              function(project){
                scope.project = project;
              }
            );
          });

          scope.keyEventsHandler = function(event){
            if (event.which === 13) {
              event.target.blur();
            }
            if ((event.which === 8) && (scope.need.text === '')) {
              event.preventDefault();
              scope.updateNeed(scope.need);
            }
          };

          scope.keyDown = function(event){
            if (event.which === 13) {
              scope.sendComment();
            }
          };

          scope.focusElem = function(event){
            console.log(event.target.parentNode.parentNode.children);
            event.target.parentNode.parentNode.children[1].children[0].focus();
          };

          scope.toggleCommentsVisibility = function(n){
            needsCtrl.toggleCommentsVisibility(n);
          };

          scope.newComment = {
            text: ''
          };

          scope.areCommentsVisible = needsCtrl.areCommentsVisible;

          scope.sendComment = function(){
            SessionSvc.loginRequired(function() {
              ProjectsSvc.findByUrlId($route.current.params.id).then(function(project){
                project.addNeedComment(scope.need, scope.newComment.text);
                scope.newComment.text = '';
              });
            });
          };

          scope.hour = needsCtrl.hour;

          scope.newComments = function(need){
            if (!need.comments || !scope.project || !scope.project.getTimestampAccess().needs){
              return false;
            }

            var prevAccess = new Date(scope.project.getTimestampAccess().needs.prev);
            var lastComment = new Date(need.comments[need.comments.length -1].time);
            return prevAccess < lastComment;
          };

          scope.isNewNeed = function(need){
            if (!scope.project || !scope.project.getTimestampAccess().needs){
              return false;
            }
            var prevAccess = new Date(scope.project.getTimestampAccess().needs.prev);
            var needTime = new Date(need.time);
            return prevAccess < needTime;
          };
        },
        templateUrl: 'needs/need.html',
        transclude: true
      };
    }
  ).directive(
    'needList',
    function () {
      return {
        templateUrl: 'needs/list.html',
        transclude: true,
        scope: {
          project: '=',
          needs: '='
        },
        controller: function($scope, $route, SessionSvc, ProjectsSvc, time) {
          this.addNeed = function (need) {
            console.dir(need);
            if (need.text !== undefined && need.text !== ''){
              need.author = SessionSvc.users.current();
              need.time = (new Date()).toJSON();

              $scope.needs.push(need);
              $scope.project.setTimestampAccess('needs', true);
            }
          };
          this.removeNeed = function (need) {
            var i = $scope.needs.indexOf(need);

            $scope.needs.splice(i,1);
          };

          this.comments = {};

          var comments = this.comments;

          this.toggleCommentsVisibility = function toggleCommentsVisibility(need) {
            comments.visible = (comments.visible === need) ? null : need;
          };

          this.areCommentsVisible = function areCommentsVisible(need) {
            return comments.visible === need;
          };

          this.hour = function(comment) {
            return time.hour(new Date(comment.time));
          };
        }
      };
    });
<|endoftext|>


<commit_before>define([
    'angular'
], function () {
    var hasLS = !!window.localStorage;

    return ['youtubePlayer', 'youtubeAPI', '$rootScope', function (ytPlayer, ytAPI, $rootScope) {
        var nowPlaying  = null,
            st          = {
                UNKNOWN: -1,
                INITIAL: 0,
                PLAYING: 1,
                PAUSING: 2,
                STOPPED: 3,
                BUFFERING: 4
            },
            state       = st.INITIAL,
            currentTime = 0,
            that        = this;

        this.playlist  = getSavedList();
        this.st        = st;

        $rootScope.$on('youtubePlayer:infoDelivery', function (e, data) {
            if (data.info.hasOwnProperty('currentTime')) {
                currentTime = data.info.currentTime;
            }
        });
        $rootScope.$on('youtubePlayer:onStateChange', function (e, data) {
            var currentItem,
                stopAt,
                playNext;
            if (data.info === YT.PlayerState.PLAYING) {
                state = st.PLAYING;
                setNowPlaying(nowPlaying);
            }
            if (data.info === YT.PlayerState.PAUSED) {
                state = st.PAUSING;
                setNowPlaying(false);
            }
            if (data.info === YT.PlayerState.ENDED) {
                state = st.STOPPED;
                currentItem = that.getNowPlaying();
                stopAt = getStopAt();
                playNext = getPlayNext();

                if (currentItem.repeatTrack)
                    that.play(nowPlaying);

                else if (typeof playNext === 'number') {
                    that.play(playNext);
                    that.playlist[playNext].playNext = false;
                }

                else if (stopAt === nowPlaying) {
                    setNowPlaying(null);
                    currentItem.stopHere = false;
                }

                else if (that.hasNext())
                    that.next();

                else
                    setNowPlaying(null);
            }
            if (data.info === YT.PlayerState.BUFFERING) {
                state = st.BUFFERING;
            }
            $rootScope.$broadcast('playList:stateChanged', state);
        });

        // Register keyboard sortcuts
        document.addEventListener('keyup', function (e) {
            var actions = {
                // Space
                32: function () {
                    that.togglePlay();
                },
                // Left Arrow
                37: function () {
                    var seekTo = currentTime - 5 >= 0 ?
                                    currentTime - 5 :
                                    0;
                    ytPlayer.seek(seekTo);
                },
                // Right Arrow
                39: function () {
                    ytPlayer.seek(currentTime + 5);
                }
            };

            if (e.target.tagName.toLowerCase() !== 'input' &&
                actions.hasOwnProperty(e.keyCode)) {
                e.preventDefault();
                actions[e.keyCode]();
            }
        });

        function getSavedList () {
            var list = [],
                lsVal;
            if (hasLS) {
                lsVal = localStorage.playlist;
                if (lsVal) list = JSON.parse(lsVal);
            }

            return list;
        }

        function saveList () {
            var lsVal;
            if (hasLS)
                localStorage.playlist = JSON.stringify(that.playlist);
        }

        function setNowPlaying (idx) {
            if (typeof idx === 'number')
                nowPlaying = idx;
            else if (idx === null) {
                nowPlaying = null;
                $rootScope.$broadcast('notify', {
					text: 'The playlist has ended.'
				});
			}

        }

        function addItem (idx, item) {
            that.playlist.splice(idx, 0, item);
            saveList();
        }

        function removeItem (idx) {
            that.playlist.splice(idx,1);

            // Fix the 'nowPlaying' var, if necessary
            if (idx < nowPlaying) {
                nowPlaying -= 1;
            }

            saveList();
        }

        function getPlayNext () {
            var idx;
            that.playlist.forEach(function (item, i) {
                if (item.playNext)
                    idx = i;
            });
            return idx;
        }

        function getStopAt () {
            var idx;
            that.playlist.forEach(function (item, i) {
                if (item.stopHere)
                    idx = i;
            });
            return idx;
        }

        this.getState = function () {
            return state;
        };

        this.save = function () {
            saveList();
        };

        this.setNowPlaying = function (idx) {
            return setNowPlaying(idx);
        };

        this.getNowPlaying = function () {
            return that.playlist[nowPlaying];
        };

        this.getNowPlayingIdx = function () {
            return nowPlaying;
        };

        this.add = function (videoId, idx) {
            var that = this;

            ytAPI.getVideo(videoId).then(function (resp) {
                var item = resp.data.items[0],
                    thumb = item.snippet.thumbnails.default.url,
                    title = item.snippet.title,
                    trimmed = title.length >= 30 ? title.substr(0,27) + '...' :
                                title,
                    text = 'Track "' + trimmed + '" has been added to playlist';

                addItem(idx, item);
                if (that.playlist.length === 1 ||
                    (idx === that.playlist.length - 1 && state === st.STOPPED))
                        that.play(idx);

                $rootScope.$broadcast('notify', {
                    thumb: thumb,
                    text: text
                });
            });
        };

        this.addFirst = function (videoId) {
            this.add(videoId, 0);
        };

        this.addLast = function (videoId) {
            this.add(videoId, this.playlist.length);
        };

        this.remove = function (idx) {
            var fixPlay = (idx === nowPlaying),
                isLast  = (idx === this.playlist.length - 1);
            removeItem(idx);

            if (fixPlay) {
                if (isLast) {
                    this.stop();
                    this.clear();
                }
                else {
                    this.play(idx, true);
                }
            }
            else
                this.play();
        };

        this.togglePlay = function () {
            if (state === st.PLAYING) this.pause();
            else this.play();
        };

        this.play = function (index, force) {
            var idx = typeof index === 'number' ? index : (nowPlaying || 0),
                videoId = this.playlist[idx].id;

            if (idx === nowPlaying && !force) {
                return ytPlayer.play();
            }
            else {
                return ytPlayer.loadVideo(videoId).then(function () {
                    ytPlayer.play();
                    setNowPlaying(idx);
                });
            }
        };

        this.pause = function () {
            ytPlayer.pause();
            setNowPlaying(false);
        };

        this.stop = function () {
            ytPlayer.seek(0);
            ytPlayer.stop();
            setNowPlaying(null);
        };

        this.clear = function () {
            ytPlayer.clear();
        };

        this.next = function () {
            if (this.hasNext())
                this.play(nowPlaying + 1);
        };

        this.hasNext = function () {
            return nowPlaying < this.playlist.length - 1;
        };

        this.prev = function () {
            if (nowPlaying !== 0)
                this.play(nowPlaying - 1);
        };

        this.playNext = function (idx) {
            var that = this;
            that.playlist.forEach(function (item) {
                item.playNext = false;
            });
            if (typeof idx === 'number')
                that.playlist[idx].playNext = true;

            saveList();
        };

        this.stopAt = function (idx) {
            var that = this;
            that.playlist.forEach(function (item) {
                item.stopHere = false;
            });
            if (typeof idx === 'number')
                that.playlist[idx].stopHere = true;

            saveList();
        };

        this.repeatTrack = function (idx) {
            var that = this;
            that.playlist.forEach(function (item) {
                item.repeatTrack = false;
            });
            if (typeof idx === 'number')
                that.playlist[idx].repeatTrack = true;

            saveList();
        };
    }];
});

<commit_msg>#9: Fixed whitespace in files<commit_after>define([
    'angular'
], function () {
    var hasLS = !!window.localStorage;

    return ['youtubePlayer', 'youtubeAPI', '$rootScope', function (ytPlayer, ytAPI, $rootScope) {
        var nowPlaying  = null,
            st          = {
                UNKNOWN: -1,
                INITIAL: 0,
                PLAYING: 1,
                PAUSING: 2,
                STOPPED: 3,
                BUFFERING: 4
            },
            state       = st.INITIAL,
            currentTime = 0,
            that        = this;

        this.playlist  = getSavedList();
        this.st        = st;

        $rootScope.$on('youtubePlayer:infoDelivery', function (e, data) {
            if (data.info.hasOwnProperty('currentTime')) {
                currentTime = data.info.currentTime;
            }
        });
        $rootScope.$on('youtubePlayer:onStateChange', function (e, data) {
            var currentItem,
                stopAt,
                playNext;
            if (data.info === YT.PlayerState.PLAYING) {
                state = st.PLAYING;
                setNowPlaying(nowPlaying);
            }
            if (data.info === YT.PlayerState.PAUSED) {
                state = st.PAUSING;
                setNowPlaying(false);
            }
            if (data.info === YT.PlayerState.ENDED) {
                state = st.STOPPED;
                currentItem = that.getNowPlaying();
                stopAt = getStopAt();
                playNext = getPlayNext();

                if (currentItem.repeatTrack)
                    that.play(nowPlaying);

                else if (typeof playNext === 'number') {
                    that.play(playNext);
                    that.playlist[playNext].playNext = false;
                }

                else if (stopAt === nowPlaying) {
                    setNowPlaying(null);
                    currentItem.stopHere = false;
                }

                else if (that.hasNext())
                    that.next();

                else
                    setNowPlaying(null);
            }
            if (data.info === YT.PlayerState.BUFFERING) {
                state = st.BUFFERING;
            }
            $rootScope.$broadcast('playList:stateChanged', state);
        });

        // Register keyboard sortcuts
        document.addEventListener('keyup', function (e) {
            var actions = {
                // Space
                32: function () {
                    that.togglePlay();
                },
                // Left Arrow
                37: function () {
                    var seekTo = currentTime - 5 >= 0 ?
                                    currentTime - 5 :
                                    0;
                    ytPlayer.seek(seekTo);
                },
                // Right Arrow
                39: function () {
                    ytPlayer.seek(currentTime + 5);
                }
            };

            if (e.target.tagName.toLowerCase() !== 'input' &&
                actions.hasOwnProperty(e.keyCode)) {
                e.preventDefault();
                actions[e.keyCode]();
            }
        });

        function getSavedList () {
            var list = [],
                lsVal;
            if (hasLS) {
                lsVal = localStorage.playlist;
                if (lsVal) list = JSON.parse(lsVal);
            }

            return list;
        }

        function saveList () {
            var lsVal;
            if (hasLS)
                localStorage.playlist = JSON.stringify(that.playlist);
        }

        function setNowPlaying (idx) {
            if (typeof idx === 'number')
                nowPlaying = idx;
            else if (idx === null) {
                nowPlaying = null;
                $rootScope.$broadcast('notify', {
                    text: 'The playlist has ended.'
                });
            }

        }

        function addItem (idx, item) {
            that.playlist.splice(idx, 0, item);
            saveList();
        }

        function removeItem (idx) {
            that.playlist.splice(idx,1);

            // Fix the 'nowPlaying' var, if necessary
            if (idx < nowPlaying) {
                nowPlaying -= 1;
            }

            saveList();
        }

        function getPlayNext () {
            var idx;
            that.playlist.forEach(function (item, i) {
                if (item.playNext)
                    idx = i;
            });
            return idx;
        }

        function getStopAt () {
            var idx;
            that.playlist.forEach(function (item, i) {
                if (item.stopHere)
                    idx = i;
            });
            return idx;
        }

        this.getState = function () {
            return state;
        };

        this.save = function () {
            saveList();
        };

        this.setNowPlaying = function (idx) {
            return setNowPlaying(idx);
        };

        this.getNowPlaying = function () {
            return that.playlist[nowPlaying];
        };

        this.getNowPlayingIdx = function () {
            return nowPlaying;
        };

        this.add = function (videoId, idx) {
            var that = this;

            ytAPI.getVideo(videoId).then(function (resp) {
                var item = resp.data.items[0],
                    thumb = item.snippet.thumbnails.default.url,
                    title = item.snippet.title,
                    trimmed = title.length >= 30 ? title.substr(0,27) + '...' :
                                title,
                    text = 'Track "' + trimmed + '" has been added to playlist';

                addItem(idx, item);
                if (that.playlist.length === 1 ||
                    (idx === that.playlist.length - 1 && state === st.STOPPED))
                        that.play(idx);

                $rootScope.$broadcast('notify', {
                    thumb: thumb,
                    text: text
                });
            });
        };

        this.addFirst = function (videoId) {
            this.add(videoId, 0);
        };

        this.addLast = function (videoId) {
            this.add(videoId, this.playlist.length);
        };

        this.remove = function (idx) {
            var fixPlay = (idx === nowPlaying),
                isLast  = (idx === this.playlist.length - 1);
            removeItem(idx);

            if (fixPlay) {
                if (isLast) {
                    this.stop();
                    this.clear();
                }
                else {
                    this.play(idx, true);
                }
            }
            else
                this.play();
        };

        this.togglePlay = function () {
            if (state === st.PLAYING) this.pause();
            else this.play();
        };

        this.play = function (index, force) {
            var idx = typeof index === 'number' ? index : (nowPlaying || 0),
                videoId = this.playlist[idx].id;

            if (idx === nowPlaying && !force) {
                return ytPlayer.play();
            }
            else {
                return ytPlayer.loadVideo(videoId).then(function () {
                    ytPlayer.play();
                    setNowPlaying(idx);
                });
            }
        };

        this.pause = function () {
            ytPlayer.pause();
            setNowPlaying(false);
        };

        this.stop = function () {
            ytPlayer.seek(0);
            ytPlayer.stop();
            setNowPlaying(null);
        };

        this.clear = function () {
            ytPlayer.clear();
        };

        this.next = function () {
            if (this.hasNext())
                this.play(nowPlaying + 1);
        };

        this.hasNext = function () {
            return nowPlaying < this.playlist.length - 1;
        };

        this.prev = function () {
            if (nowPlaying !== 0)
                this.play(nowPlaying - 1);
        };

        this.playNext = function (idx) {
            var that = this;
            that.playlist.forEach(function (item) {
                item.playNext = false;
            });
            if (typeof idx === 'number')
                that.playlist[idx].playNext = true;

            saveList();
        };

        this.stopAt = function (idx) {
            var that = this;
            that.playlist.forEach(function (item) {
                item.stopHere = false;
            });
            if (typeof idx === 'number')
                that.playlist[idx].stopHere = true;

            saveList();
        };

        this.repeatTrack = function (idx) {
            var that = this;
            that.playlist.forEach(function (item) {
                item.repeatTrack = false;
            });
            if (typeof idx === 'number')
                that.playlist[idx].repeatTrack = true;

            saveList();
        };
    }];
});

<|endoftext|>


<commit_before>/**
 *
 * @author Andriy Oblivantsev <eslider@gmail.com>
 */
/**
 * Form helper plugin for jQuery
 *
 * @author Andriy Oblivantsev <eslider@gmail.com>
 * @copyright 02.02.2015 by WhereGroup GmbH & Co. KG
 *
 */
$.fn.formData = function(values) {
    var form = $(this);
    var inputs = $(':input', form).get();
    var hasNewValues = typeof values == 'object';
    var textElements = $(".form-group.text", form);

    if (hasNewValues) {

        $.each(textElements, function() {
            var textElementContainer = $(this);
            var textElement = $('.text', textElementContainer);
            var declaration = textElementContainer.data('item');
            if(declaration.hasOwnProperty('text')) {
                if (typeof declaration.text === 'function') {
                    textElement.html(declaration.text(values));
                } else {
                    var html = "";
                    try {
                        console.error("Using Javascript code in the configuration is deprecated",declaration.text);
                        var data = values;
                        eval('html=' + declaration.text + ';');
                    } catch (e) {
                        console.error("The defenition", declaration, " of ", textElement, ' is erroneous.', e);
                    }
                    textElement.html(html);
                }
            }
        });

        $.each(inputs, function() {
            var input = $(this);
            var value = values[this.name];
            var declaration = input.data('declaration');

            if(values.hasOwnProperty(this.name)) {

                switch (this.type) {
                    case 'select-multiple':
                        //var declaration = input.data('declaration');
                        var type = declaration.fieldType ? declaration.fieldType : 'text';
                        config = this.config ? this.config : {};
                        if(type == 'text' && value ) {
                            var separator = declaration.separator ? declaration.separator : ',';
                            var vals = $.isArray(value) ? value : value.split(separator);
                            $.each(vals, function(i, optionValue) {
                                $("option[value='" + optionValue + "']", input).prop("selected", true);
                            });
                            if(input.select2){
                                input.select2(config);
                            }
                        } else {
                            input.val(value);
                        }
                        break;

                    case 'checkbox':
                        input.prop('checked', value !== null && value);
                        break;
                    case 'radio':
                        if (value === null) {
                            input.prop('checked', false);
                        } else if (input.val() == value) {
                            input.prop("checked", true);
                        }
                        break;
                    case 'hidden':
                        input.val(value);
                        input.trigger('change');
                        break;

                    case 'text':
                        if(input.hasClass('hasDatepicker')) {
                            var dateFormat = input.datepicker("option", "dateFormat");

                            if(value === '' || value === 0 || value === '0') {
                                value = null;
                            }
                            // if(value !== null) {
                                // value = $.datepicker.formatDate(dateFormat, $.datepicker.parseDate(dateFormat, value))
                            // }

                            input.datepicker("setDate", value);
                            input.datepicker("refresh");
                        } else {
                            input.val(value);
                        }
                        break;
                    default:
                        input.val(value);
                }
                input.trigger('filled', {
                    data:   values,
                    value:  value
                });
                input.trigger("changeValue");

            }
        });
        return form;
    } else {
        values = {};
        var firstInput;
        $.each(inputs, function() {
            var input = $(this);
            var value;
            var declaration = input.data('declaration');

            if(this.name == ""){
                return;
            }

            switch (this.type) {
                case 'checkbox':
                case 'radio':
                    if(values.hasOwnProperty(this.name) && values[this.name] != null){
                        return;
                    }
                    value = input.is(':checked') ? input.val() : null;
                    break;
                default:
                    value = input.val();
            }

            if(value === ""){
                value = null;
            }

            if(values !== false && declaration){
                if(declaration.hasOwnProperty('mandatory') && declaration.mandatory ){
                    var isDataReady = false;
                    if(typeof declaration.mandatory === "function"){
                        isDataReady = declaration.mandatory(input, declaration, value);
                    } else{
                        isDataReady = input.data('warn')(value);
                    }
                    if(!isDataReady && !firstInput && input.is(":visible")){
                        firstInput = input;
                        input.focus();
                    }
                }
                values[this.name] = value;
            }else{
                values[this.name] = value;
            }

        });
        return values;
    }
};

$.fn.disableForm = function() {
    var form = this;
    var inputs = $(" :input", form);
    form.attr('readonly', true);
    form.css('cursor', 'wait');
    $.each(inputs, function(idx, el) {
        var $el = $(el);
        if($el.is(':checkbox') || $el.is(':radio') || $el.is('select')) {
            $el.attr('disabled', 'disabled');
        } else {
            $el.attr('readonly', 'true');
        }
    })
};

$.fn.enableForm = function() {
    var form = this;
    var inputs = $(" :input", form);
    form.css('cursor', '');
    $.each(inputs, function(idx, el) {
        var $el = $(el);
        if($el.is(':checkbox') || $el.is(':radio') || $el.is('select')) {
            $el.removeAttr('disabled', 'disabled');
        } else {
            $el.removeAttr('readonly', 'true');
        }
    })
};

<commit_msg>Revert "Add possibilty to add config to select2"<commit_after>/**
 *
 * @author Andriy Oblivantsev <eslider@gmail.com>
 */
/**
 * Form helper plugin for jQuery
 *
 * @author Andriy Oblivantsev <eslider@gmail.com>
 * @copyright 02.02.2015 by WhereGroup GmbH & Co. KG
 *
 */
$.fn.formData = function(values) {
    var form = $(this);
    var inputs = $(':input', form).get();
    var hasNewValues = typeof values == 'object';
    var textElements = $(".form-group.text", form);

    if (hasNewValues) {

        $.each(textElements, function() {
            var textElementContainer = $(this);
            var textElement = $('.text', textElementContainer);
            var declaration = textElementContainer.data('item');
            if(declaration.hasOwnProperty('text')) {
                if (typeof declaration.text === 'function') {
                    textElement.html(declaration.text(values));
                } else {
                    var html = "";
                    try {
                        console.error("Using Javascript code in the configuration is deprecated",declaration.text);
                        var data = values;
                        eval('html=' + declaration.text + ';');
                    } catch (e) {
                        console.error("The defenition", declaration, " of ", textElement, ' is erroneous.', e);
                    }
                    textElement.html(html);
                }
            }
        });

        $.each(inputs, function() {
            var input = $(this);
            var value = values[this.name];
            var declaration = input.data('declaration');

            if(values.hasOwnProperty(this.name)) {

                switch (this.type) {
                    case 'select-multiple':
                        //var declaration = input.data('declaration');
                        var type = declaration.fieldType ? declaration.fieldType : 'text';

                        if(type == 'text' && value ) {
                            var separator = declaration.separator ? declaration.separator : ',';
                            var vals = $.isArray(value) ? value : value.split(separator);
                            $.each(vals, function(i, optionValue) {
                                $("option[value='" + optionValue + "']", input).prop("selected", true);
                            });
                            if(input.select2){
                                input.select2();
                            }
                        } else {
                            input.val(value);
                        }
                        break;

                    case 'checkbox':
                        input.prop('checked', value !== null && value);
                        break;
                    case 'radio':
                        if (value === null) {
                            input.prop('checked', false);
                        } else if (input.val() == value) {
                            input.prop("checked", true);
                        }
                        break;
                    case 'hidden':
                        input.val(value);
                        input.trigger('change');
                        break;

                    case 'text':
                        if(input.hasClass('hasDatepicker')) {
                            var dateFormat = input.datepicker("option", "dateFormat");

                            if(value === '' || value === 0 || value === '0') {
                                value = null;
                            }
                            // if(value !== null) {
                                // value = $.datepicker.formatDate(dateFormat, $.datepicker.parseDate(dateFormat, value))
                            // }

                            input.datepicker("setDate", value);
                            input.datepicker("refresh");
                        } else {
                            input.val(value);
                        }
                        break;
                    default:
                        input.val(value);
                }
                input.trigger('filled', {
                    data:   values,
                    value:  value
                });
                input.trigger("changeValue");

            }
        });
        return form;
    } else {
        values = {};
        var firstInput;
        $.each(inputs, function() {
            var input = $(this);
            var value;
            var declaration = input.data('declaration');

            if(this.name == ""){
                return;
            }

            switch (this.type) {
                case 'checkbox':
                case 'radio':
                    if(values.hasOwnProperty(this.name) && values[this.name] != null){
                        return;
                    }
                    value = input.is(':checked') ? input.val() : null;
                    break;
                default:
                    value = input.val();
            }

            if(value === ""){
                value = null;
            }

            if(values !== false && declaration){
                if(declaration.hasOwnProperty('mandatory') && declaration.mandatory ){
                    var isDataReady = false;
                    if(typeof declaration.mandatory === "function"){
                        isDataReady = declaration.mandatory(input, declaration, value);
                    } else{
                        isDataReady = input.data('warn')(value);
                    }
                    if(!isDataReady && !firstInput && input.is(":visible")){
                        firstInput = input;
                        input.focus();
                    }
                }
                values[this.name] = value;
            }else{
                values[this.name] = value;
            }

        });
        return values;
    }
};

$.fn.disableForm = function() {
    var form = this;
    var inputs = $(" :input", form);
    form.attr('readonly', true);
    form.css('cursor', 'wait');
    $.each(inputs, function(idx, el) {
        var $el = $(el);
        if($el.is(':checkbox') || $el.is(':radio') || $el.is('select')) {
            $el.attr('disabled', 'disabled');
        } else {
            $el.attr('readonly', 'true');
        }
    })
};

$.fn.enableForm = function() {
    var form = this;
    var inputs = $(" :input", form);
    form.css('cursor', '');
    $.each(inputs, function(idx, el) {
        var $el = $(el);
        if($el.is(':checkbox') || $el.is(':radio') || $el.is('select')) {
            $el.removeAttr('disabled', 'disabled');
        } else {
            $el.removeAttr('readonly', 'true');
        }
    })
};

<|endoftext|>


<commit_before>import { isFunction } from '~'
import SimpleMap from '~/simple-map'

const delegatedEvents = new SimpleMap()

const unbubbleEvents = {
  onmousemove: 1,
  ontouchmove: 1,
  onmouseleave: 1,
  onmouseenter: 1,
  onload: 1,
  onunload: 1,
  onscroll: 1,
  onfocus: 1,
  onblur: 1,
  onrowexit: 1,
  onbeforeunload: 1,
  onstop: 1,
  ondragdrop: 1,
  ondragenter: 1,
  ondragexit: 1,
  ondraggesture: 1,
  ondragover: 1,
  oncontextmenu: 1,
  onerror: 1,
  onabort: 1,
  oncanplay: 1,
  oncanplaythrough: 1,
  ondurationchange: 1,
  onemptied: 1,
  onended: 1,
  onloadeddata: 1,
  onloadedmetadata: 1,
  onloadstart: 1,
  onencrypted: 1,
  onpause: 1,
  onplay: 1,
  onplaying: 1,
  onprogress: 1,
  onratechange: 1,
  onseeking: 1,
  onseeked: 1,
  onstalled: 1,
  onsuspend: 1,
  ontimeupdate: 1,
  onvolumechange: 1,
  onwaiting: 1
}

class EventHook {
  constructor (eventName, handler) {
    this.eventName = getEventName(eventName)
    this.handler = handler
  }

  hook (node) {
    const eventName = this.eventName
    let delegatedRoots = delegatedEvents.get(eventName)
    if (unbubbleEvents[eventName] === 1) {
      if (!delegatedRoots) {
        delegatedRoots = new SimpleMap()
      }
      const event = attachEventToNode(node, eventName, delegatedRoots)
      delegatedEvents.set(eventName, delegatedRoots)
      if (isFunction(this.handler)) {
        delegatedRoots.set(node, {
          eventHandler: this.handler,
          event
        })
      }
    } else {
      if (!delegatedRoots) {
        delegatedRoots = {
          items: new SimpleMap()
        }
        delegatedRoots.event = attachEventToDocument(document, eventName, delegatedRoots)
        delegatedEvents.set(eventName, delegatedRoots)
      }
      if (isFunction(this.handler)) {
        delegatedRoots.items.set(node, this.handler)
      }
    }
  }

  unhook (node) {
    const eventName = this.eventName
    let delegatedRoots = delegatedEvents.get(eventName)
    if (unbubbleEvents[eventName] === 1 && delegatedRoots) {
      let event = delegatedRoots.get(node)
      node.removeEventListener(parseEventName(eventName), event.event, false)
      delegatedRoots.remove(node)
      if (delegatedRoots.size() === 0) {
        delegatedEvents.remove(eventName)
      }
    } else if (delegatedRoots && delegatedRoots.items) {
      let items = delegatedRoots.items
      if (items.remove(node) && items.size() === 0) {
        document.removeEventListener(parseEventName(eventName), delegatedRoots.event, false)
        delegatedEvents.remove(eventName)
      }
    }
  }
}

function getEventName (name) {
  if (name === 'onDoubleClick') {
    name = 'ondblclick'
  } else if (name === 'onTouchTap') {
    name = 'onclick'
  }
  return name.toLowerCase()
}

function parseEventName (name) {
  return name.substr(2)
}

function stopPropagation () {
  this.cancelBubble = true
  this.stopImmediatePropagation()
}

function dispatchEvent (event, target, items, count, eventData) {
  const eventsToTrigger = items.get(target)
  if (eventsToTrigger) {
    count--
    eventData.currentTarget = target
    eventsToTrigger(event)
    if (event.cancelBubble) {
      return
    }
  }
  if (count > 0) {
    const parentDom = target.parentNode
    if (parentDom === null || (event.type === 'click' && parentDom.nodeType === 1 && parentDom.disabled)) {
      return
    }
    dispatchEvent(event, parentDom, items, count, eventData)
  }
}

function attachEventToDocument (doc, eventName, delegatedRoots) {
  const eventHandler = (event) => {
    const count = delegatedRoots.items.size()
    if (count > 0) {
      const eventData = {
        currentTarget: event.target
      }
      Object.defineProperties(event, {
        currentTarget: {
          configurable: true,
          get () {
            return eventData.currentTarget
          }
        },
        stopPropagation: {
          value: stopPropagation
        }
      })
      dispatchEvent(event, event.target, delegatedRoots.items, count, eventData)
    }
  }
  doc.addEventListener(parseEventName(eventName), eventHandler, false)
  return eventHandler
}

function attachEventToNode (node, eventName, delegatedRoots) {
  const eventHandler = (event) => {
    const eventToTrigger = delegatedRoots.get(node)
    if (eventToTrigger && eventToTrigger.eventHandler) {
      const eventData = {
        currentTarget: event.target
      }
      Object.defineProperties(event, {
        currentTarget: {
          configurable: true,
          get () {
            return eventData.currentTarget
          }
        }
      })
      eventData.currentTarget = event.target
      eventToTrigger.eventHandler(event)
    }
  }
  node.addEventListener(parseEventName(eventName), eventHandler, false)
  return eventHandler
}

export default EventHook
<commit_msg>fix:修正事件hook，不必每次diff都将事件重新绑定<commit_after>import { isFunction } from '~'
import SimpleMap from '~/simple-map'

const delegatedEvents = new SimpleMap()

const unbubbleEvents = {
  onmousemove: 1,
  ontouchmove: 1,
  onmouseleave: 1,
  onmouseenter: 1,
  onload: 1,
  onunload: 1,
  onscroll: 1,
  onfocus: 1,
  onblur: 1,
  onrowexit: 1,
  onbeforeunload: 1,
  onstop: 1,
  ondragdrop: 1,
  ondragenter: 1,
  ondragexit: 1,
  ondraggesture: 1,
  ondragover: 1,
  oncontextmenu: 1,
  onerror: 1,
  onabort: 1,
  oncanplay: 1,
  oncanplaythrough: 1,
  ondurationchange: 1,
  onemptied: 1,
  onended: 1,
  onloadeddata: 1,
  onloadedmetadata: 1,
  onloadstart: 1,
  onencrypted: 1,
  onpause: 1,
  onplay: 1,
  onplaying: 1,
  onprogress: 1,
  onratechange: 1,
  onseeking: 1,
  onseeked: 1,
  onstalled: 1,
  onsuspend: 1,
  ontimeupdate: 1,
  onvolumechange: 1,
  onwaiting: 1
}

class EventHook {
  type = 'EventHook'
  constructor (eventName, handler) {
    this.eventName = getEventName(eventName)
    this.handler = handler
  }

  hook (node, prop, prev) {
    if (prev && prev.type === 'EventHook' &&
      prev.handler === this.handler &&
      prev.eventName === this.eventName) {
      return
    }
    const eventName = this.eventName
    let delegatedRoots = delegatedEvents.get(eventName)
    if (unbubbleEvents[eventName] === 1) {
      if (!delegatedRoots) {
        delegatedRoots = new SimpleMap()
      }
      const event = attachEventToNode(node, eventName, delegatedRoots)
      delegatedEvents.set(eventName, delegatedRoots)
      if (isFunction(this.handler)) {
        delegatedRoots.set(node, {
          eventHandler: this.handler,
          event
        })
      }
    } else {
      if (!delegatedRoots) {
        delegatedRoots = {
          items: new SimpleMap()
        }
        delegatedRoots.event = attachEventToDocument(document, eventName, delegatedRoots)
        delegatedEvents.set(eventName, delegatedRoots)
      }
      if (isFunction(this.handler)) {
        delegatedRoots.items.set(node, this.handler)
      }
    }
  }

  unhook (node, prop, next) {
    if (next && next.type === 'EventHook' &&
      next.handler === this.handler &&
      next.eventName === next.eventName) {
      return
    }
    const eventName = this.eventName
    let delegatedRoots = delegatedEvents.get(eventName)
    if (unbubbleEvents[eventName] === 1 && delegatedRoots) {
      let event = delegatedRoots.get(node)
      node.removeEventListener(parseEventName(eventName), event.event, false)
      delegatedRoots.remove(node)
      if (delegatedRoots.size() === 0) {
        delegatedEvents.remove(eventName)
      }
    } else if (delegatedRoots && delegatedRoots.items) {
      let items = delegatedRoots.items
      if (items.remove(node) && items.size() === 0) {
        document.removeEventListener(parseEventName(eventName), delegatedRoots.event, false)
        delegatedEvents.remove(eventName)
      }
    }
  }
}

function getEventName (name) {
  if (name === 'onDoubleClick') {
    name = 'ondblclick'
  } else if (name === 'onTouchTap') {
    name = 'onclick'
  }
  return name.toLowerCase()
}

function parseEventName (name) {
  return name.substr(2)
}

function stopPropagation () {
  this.cancelBubble = true
  this.stopImmediatePropagation()
}

function dispatchEvent (event, target, items, count, eventData) {
  const eventsToTrigger = items.get(target)
  if (eventsToTrigger) {
    count--
    eventData.currentTarget = target
    eventsToTrigger(event)
    if (event.cancelBubble) {
      return
    }
  }
  if (count > 0) {
    const parentDom = target.parentNode
    if (parentDom === null || (event.type === 'click' && parentDom.nodeType === 1 && parentDom.disabled)) {
      return
    }
    dispatchEvent(event, parentDom, items, count, eventData)
  }
}

function attachEventToDocument (doc, eventName, delegatedRoots) {
  const eventHandler = (event) => {
    const count = delegatedRoots.items.size()
    if (count > 0) {
      const eventData = {
        currentTarget: event.target
      }
      Object.defineProperties(event, {
        currentTarget: {
          configurable: true,
          get () {
            return eventData.currentTarget
          }
        },
        stopPropagation: {
          value: stopPropagation
        }
      })
      dispatchEvent(event, event.target, delegatedRoots.items, count, eventData)
    }
  }
  doc.addEventListener(parseEventName(eventName), eventHandler, false)
  return eventHandler
}

function attachEventToNode (node, eventName, delegatedRoots) {
  const eventHandler = (event) => {
    const eventToTrigger = delegatedRoots.get(node)
    if (eventToTrigger && eventToTrigger.eventHandler) {
      const eventData = {
        currentTarget: event.target
      }
      Object.defineProperties(event, {
        currentTarget: {
          configurable: true,
          get () {
            return eventData.currentTarget
          }
        }
      })
      eventData.currentTarget = event.target
      eventToTrigger.eventHandler(event)
    }
  }
  node.addEventListener(parseEventName(eventName), eventHandler, false)
  return eventHandler
}

export default EventHook
<|endoftext|>


<commit_before>if (!Function.prototype.bind) {
	Function.prototype.bind = function(context) {
		var self = this;
		var fn = function() {
			return self.apply(context, arguments);
		};

		fn.cleanup = function() {
			self = fn = context = null;
		};

		return fn;
	};
}

if (!Function.prototype.include) {
	Function.prototype.include = function(mixin) {
		var key;

		// include class level methods
		if (mixin.self) {
			for (key in mixin.self) {
				if (mixin.self.hasOwnProperty(key) && !this[key]) {
					this[key] = mixin.self[key];
				}
			}
		}

		// include instance level methods
		if (mixin.prototype) {
			for (key in mixin.prototype) {
				if (mixin.prototype.hasOwnProperty(key) && !this.prototype[key]) {
					this.prototype[key] = mixin.prototype[key];
				}
			}
		}

		if (mixin.included) {
			mixin.included(this);
		}

		mixin = null;
	};
}

if (!Function.prototype.extend) {
	Function.prototype.extend = function(descriptor) {
		descriptor = descriptor || {};
		var key, i, length;

		// Constructor function for our new class
		var Klass = function() {
			this.initialize.apply(this, arguments);
		};

		// Temp class referencing this prototype to avoid calling initialize() when inheriting
		var ProxyClass = function() {};
		ProxyClass.prototype = this.prototype;

		// "inherit" class level methods
		for (key in this) {
			if (this.hasOwnProperty(key)) {
				Klass[key] = this[key];
			}
		}

		// new class level methods
		if (descriptor.self) {
			for (key in descriptor.self) {
				if (descriptor.self.hasOwnProperty(key)) {
					Klass[key] = descriptor.self[key];
				}
			}
		}

		// set up true prototypal inheritance
		Klass.prototype = new ProxyClass();

		// new instance level methods
		if (descriptor.prototype) {
			for (key in descriptor.prototype) {
				if (descriptor.prototype.hasOwnProperty(key)) {
					Klass.prototype[key] = descriptor.prototype[key];
				}
			}
		}

		// apply mixins
		if (descriptor.includes) {
			// force includes to be an array
			descriptor.includes = (descriptor.includes instanceof Array) ? descriptor.includes : [descriptor.includes];

			for (i = 0, length = descriptor.includes.length; i < length; i++) {
				Klass.include(descriptor.includes[i]);
			}
		}

		// ensure new prototype has an initialize method
		Klass.prototype.initialize = Klass.prototype.initialize || function() {};

		// set reference to constructor function in new prototype
		Klass.prototype.constructor = Klass;

		ProxyClass = descriptor = null;

		return Klass;
	};
}
<commit_msg>Adding support for mixins including other mixins.<commit_after>if (!Function.prototype.bind) {
	Function.prototype.bind = function(context) {
		var self = this;
		var fn = function() {
			return self.apply(context, arguments);
		};

		fn.cleanup = function() {
			self = fn = context = null;
		};

		return fn;
	};
}

if (!Function.prototype.include) {
	Function.prototype.include = function(mixin) {
		var key;

		// include class level methods
		if (mixin.self) {
			for (key in mixin.self) {
				if (mixin.self.hasOwnProperty(key) && !this[key]) {
					this[key] = mixin.self[key];
				}
			}
		}

		// include instance level methods
		if (mixin.prototype) {
			for (key in mixin.prototype) {
				if (mixin.prototype.hasOwnProperty(key) && !this.prototype[key]) {
					this.prototype[key] = mixin.prototype[key];
				}
			}
		}

		// include other mixins
		if (mixin.includes) {
			mixin.includes = (mixin.includes instanceof Array) ? mixin.includes : [mixin.includes];

			for (var i = 0, length = mixin.includes.length; i < length; i++) {
				this.include(mixin.includes[i]);
			}
		}

		if (mixin.included) {
			mixin.included(this);
		}

		mixin = null;
	};
}

if (!Function.prototype.extend) {
	Function.prototype.extend = function(descriptor) {
		descriptor = descriptor || {};
		var key, i, length;

		// Constructor function for our new class
		var Klass = function() {
			this.initialize.apply(this, arguments);
		};

		// Temp class referencing this prototype to avoid calling initialize() when inheriting
		var ProxyClass = function() {};
		ProxyClass.prototype = this.prototype;

		// "inherit" class level methods
		for (key in this) {
			if (this.hasOwnProperty(key)) {
				Klass[key] = this[key];
			}
		}

		// new class level methods
		if (descriptor.self) {
			for (key in descriptor.self) {
				if (descriptor.self.hasOwnProperty(key)) {
					Klass[key] = descriptor.self[key];
				}
			}
		}

		// set up true prototypal inheritance
		Klass.prototype = new ProxyClass();

		// new instance level methods
		if (descriptor.prototype) {
			for (key in descriptor.prototype) {
				if (descriptor.prototype.hasOwnProperty(key)) {
					Klass.prototype[key] = descriptor.prototype[key];
				}
			}
		}

		// apply mixins
		if (descriptor.includes) {
			// force includes to be an array
			descriptor.includes = (descriptor.includes instanceof Array) ? descriptor.includes : [descriptor.includes];

			for (i = 0, length = descriptor.includes.length; i < length; i++) {
				Klass.include(descriptor.includes[i]);
			}
		}

		// ensure new prototype has an initialize method
		Klass.prototype.initialize = Klass.prototype.initialize || function() {};

		// set reference to constructor function in new prototype
		Klass.prototype.constructor = Klass;

		ProxyClass = descriptor = null;

		return Klass;
	};
}
<|endoftext|>


<commit_before>const { mergeDefault } = require('../util/util');
const quotes = ['"', "'", '“”', '‘’'];

/**
 * A class to handle argument collection and parameter resolution
 */
class TextPrompt {

	/**
	 * @typedef {Object} TextPromptOptions
	 * @property {number} [promptLimit=Infinity] The number of re-prompts before this TextPrompt gives up
	 * @property {number} [promptTime=30000] The time-limit for re-prompting
	 * @property {boolean} [quotedStringSupport=false] Whether this prompt should respect quoted strings
	 * @memberof TextPrompt
	 */

	/**
	 * @since 0.5.0
	 * @param {KlasaMessage} msg The message this prompt is for
	 * @param {ParsedUsage} usage The usage for this prompt
	 * @param {TextPromptOptions} options The options of this prompt
	 */
	constructor(msg, usage, options) {
		options = mergeDefault(msg.client.options.customPromptDefaults, options);

		/**
		 * The client this TextPrompt was created with
		 * @since 0.5.0
		 * @name TextPrompt#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: msg.client });

		/**
		 * The message this prompt is for
		 * @since 0.5.0
		 * @type {KlasaMessage}
		 */
		this.message = msg;

		/**
		 * The usage for this prompt
		 * @since 0.5.0
		 * @type {ParsedUsage|CommandUsage}
		 */
		this.usage = usage;

		/**
		 * If the command reprompted for missing args
		 * @since 0.0.1
		 * @type {boolean}
		 */
		this.reprompted = false;

		/**
		 * The flag arguments resolved by this class
		 * @since 0.5.0
		 * @type {Object}
		 */
		this.flags = {};

		/**
		 * The string arguments derived from the usageDelim of the command
		 * @since 0.0.1
		 * @type {string[]}
		 */
		this.args = [];

		/**
		 * The parameters resolved by this class
		 * @since 0.0.1
		 * @type {any[]}
		 */
		this.params = [];

		/**
		 * The time-limit for re-prompting
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptTime = options.promptTime;

		/**
		 * The number of re-prompts before this TextPrompt gives up
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptLimit = options.promptLimit;

		/**
		 * Whether this prompt should respect quoted strings
		 * @since 0.5.0
		 * @type {boolean}
		 */
		this.quotedStringSupport = options.quotedStringSupport;

		/**
		 * Whether the current usage is a repeating arg
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._repeat = false;

		/**
		 * Whether the current usage is required
		 * @since 0.0.1
		 * @type {number}
		 * @private
		 */
		this._required = 0;

		/**
		 * How many time this class has reprompted
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._prompted = 0;

		/**
		 * A cache of the current usage while validating
		 * @since 0.0.1
		 * @type {Tag}
		 * @private
		 */
		this._currentUsage = {};
	}

	/**
	 * Runs the custom prompt.
	 * @since 0.5.0
	 * @param {string} prompt The message to initially prompt with
	 * @returns {Promise<any[]>} The parameters resolved
	 */
	async run(prompt) {
		const message = await this.message.prompt(prompt, this.promptTime);
		this._setup(message.content);
		return this.validateArgs();
	}

	/**
	 * Collects missing required arguments.
	 * @since 0.5.0
	 * @param {string} prompt The reprompt error
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async reprompt(prompt) {
		this._prompted++;
		if (this.typing) this.message.channel.stopTyping();
		const message = await this.message.prompt(
			this.message.language.get('MONITOR_COMMAND_HANDLER_REPROMPT', `<@!${this.message.author.id}>`, prompt, this.promptTime / 1000),
			this.promptTime
		);

		if (message.content.toLowerCase() === 'abort') throw this.message.language.get('MONITOR_COMMAND_HANDLER_ABORTED');

		if (this.typing) this.message.channel.startTyping();
		this.args[this.args.lastIndexOf(null)] = message.content;
		this.reprompted = true;

		if (this.usage.parsedUsage[this.params.length + 1] && this.usage.parsedUsage[this.params.length + 1].type === 'repeat') {
			return this.repeatingPrompt(prompt);
		}

		return this.validateArgs();
	}

	/**
	 * Collects repeating arguments.
	 * @since 0.5.0
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async repeatingPrompt() {
		if (this.typing) this.message.channel.stopTyping();
		let message;

		try {
			message = await this.message.prompt(
				this.message.language.get('MONITOR_COMMAND_HANDLER_REPEATING_REPROMPT', `<@!${this.message.author.id}>`, this._currentUsage.possibles[0].name, this.promptTime / 1000),
				this.promptTime
			);
		} catch (err) {
			return this.validateArgs();
		}

		if (message.content.toLowerCase() === 'cancel') return this.validateArgs();

		if (this.typing) this.message.channel.startTyping();
		this.args.push(message.content);
		this.reprompted = true;

		return this.repeatingPrompt();
	}

	/**
	 * Validates and resolves args into parameters
	 * @since 0.0.1
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async validateArgs() {
		if (this.params.length >= this.usage.parsedUsage.length && this.params.length >= this.args.length) {
			return this.finalize();
		} else if (this.usage.parsedUsage[this.params.length]) {
			this._currentUsage = this.usage.parsedUsage[this.params.length];
			this._required = this._currentUsage.required;
		} else if (this._currentUsage.repeat) {
			this._required = 0;
			this._repeat = true;
		} else {
			return this.finalize();
		}

		if (this._currentUsage.possibles.length !== 1) return this.multiPossibles(0);

		const possible = this._currentUsage.possibles[0];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.pushParam(undefined);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}
			const { response } = this._currentUsage;
			const error = typeof response === 'function' ? response(this.message, possible) : response;
			if (this._required === 1) return this.handleError(response || err);
			return this.handleError(error || (this.args[this.params.length] === undefined ?
				this.message.language.get('COMMANDMESSAGE_MISSING_REQUIRED', possible.name) :
				err)
			);
		}
	}

	/**
	 * Validates and resolves args into parameters, when multiple types of usage is defined
	 * @since 0.0.1
	 * @param {number} index The id of the possible usage currently being checked
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async multiPossibles(index) {
		if (index >= this._currentUsage.possibles.length) {
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}
			const { response } = this._currentUsage;
			if (response) {
				const error = typeof response === 'function' ? response(this.message, possible) : response;
				return this.handleError(error);
			}
			return this.handleError(this.message.language.get('COMMANDMESSAGE_NOMATCH', this._currentUsage.possibles.map(poss => poss.name).join(', ')));
		}

		const possible = this._currentUsage.possibles[index];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.multiPossibles(++index);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (this._required === 1 && index === this._currentUsage.possibles.length - 1) return this.handleError(err);
			return this.multiPossibles(++index);
		}
	}

	/**
	 * Pushes a parameter into this.params, and resets the re-prompt count.
	 * @since 0.5.0
	 * @param {any} param The resolved parameter
	 * @returns {any[]}
	 * @private
	 */
	pushParam(param) {
		this._prompted = 0;
		this.params.push(param);
		return this.validateArgs();
	}

	/**
	 * Decides if the prompter should reprompt or throw the error found while validating.
	 * @since 0.5.0
	 * @param {string} err The error found while validating
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async handleError(err) {
		this.args.splice(this.params.length, 1, null);
		if (this.promptLimit && this._prompted < this.promptLimit) return this.reprompt(err);
		throw err;
	}

	/**
	 * Finalizes parameters and arguments for this prompt.
	 * @since 0.5.0
	 * @returns {any[]}
	 * @private
	 */
	finalize() {
		for (let i = this.params.length - 1; i >= 0 && this.params[i] === undefined; i--) this.params.pop();
		for (let i = this.args.length - 1; i >= 0 && this.args[i] === undefined; i--) this.args.pop();
		return this.params;
	}

	/**
	 * Splits the original message string into arguments.
	 * @since 0.5.0
	 * @param {string} original The original message string
	 * @returns {void}
	 * @private
	 */
	_setup(original) {
		const { content, flags } = this.constructor.getFlags(original, this.usage.usageDelim);
		this.flags = flags;
		this.args = this.quotedStringSupport ?
			this.constructor.getQuotedStringArgs(content, this.usage.usageDelim).map(arg => arg.trim()) :
			this.constructor.getArgs(content, this.usage.usageDelim).map(arg => arg.trim());
	}

	/**
	 * Parses a message into string args
	 * @since 0.5.0
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {Object}
	 * @private
	 */
	static getFlags(content, delim) {
		const flags = {};
		content = content.replace(TextPrompt.flagRegex, (match, fl, ...quote) => {
			flags[fl] = (quote.slice(0, -2).find(el => el) || fl).replace(/\\/g, '');
			return '';
		}).replace(new RegExp(`(?:(\\s)\\s+|(${delim})(?:${delim})+)`, 'g'), '$1').trim();
		return { content, flags };
	}

	/**
	 * Parses a message into string args
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getArgs(content, delim) {
		const args = content.replace(new RegExp(`(${delim})(?:${delim})+`, 'g'), '$1').split(delim !== '' ? delim : undefined);
		return args.length === 1 && args[0] === '' ? [] : args;
	}

	/**
	 * Parses a message into string args taking into account quoted strings
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getQuotedStringArgs(content, delim) {
		if (!delim || delim === '') return [content];

		const args = [];

		for (let i = 0; i < content.length; i++) {
			let current = '';
			if (content.slice(i, i + delim.length) === delim) {
				i += delim.length - 1;
				continue;
			}
			const quote = quotes.find(qt => qt.includes(content[i]));
			if (quote) {
				const qts = quote.split('');
				while (i + 1 < content.length && (content[i] === '\\' || !qts.includes(content[i + 1]))) current += content[++i] !== '\\' ? content[i] : '';
				i++;
				args.push(current);
			} else {
				current += content[i];
				while (i + 1 < content.length && content.slice(i + 1, i + delim.length + 1) !== delim) current += content[++i];
				args.push(current);
			}
		}

		return args.length === 1 && args[0] === '' ? [] : args;
	}

}

/**
 * Regular Expression to match flags with quoted string support.
 * @since 0.5.0
 * @type {RegExp}
 * @static
 */
TextPrompt.flagRegex = new RegExp(`--([\\w-]{2,})(?:=(?:${quotes.map(qu => `[${qu}]((?:[^${qu}\\\\]|\\\\.)*)[${qu}]`).join('|')}|([\\w-]+)))?`, 'g');

module.exports = TextPrompt;
<commit_msg>fix semi optional union logic<commit_after>const { mergeDefault } = require('../util/util');
const quotes = ['"', "'", '“”', '‘’'];

/**
 * A class to handle argument collection and parameter resolution
 */
class TextPrompt {

	/**
	 * @typedef {Object} TextPromptOptions
	 * @property {number} [promptLimit=Infinity] The number of re-prompts before this TextPrompt gives up
	 * @property {number} [promptTime=30000] The time-limit for re-prompting
	 * @property {boolean} [quotedStringSupport=false] Whether this prompt should respect quoted strings
	 * @memberof TextPrompt
	 */

	/**
	 * @since 0.5.0
	 * @param {KlasaMessage} msg The message this prompt is for
	 * @param {ParsedUsage} usage The usage for this prompt
	 * @param {TextPromptOptions} options The options of this prompt
	 */
	constructor(msg, usage, options) {
		options = mergeDefault(msg.client.options.customPromptDefaults, options);

		/**
		 * The client this TextPrompt was created with
		 * @since 0.5.0
		 * @name TextPrompt#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: msg.client });

		/**
		 * The message this prompt is for
		 * @since 0.5.0
		 * @type {KlasaMessage}
		 */
		this.message = msg;

		/**
		 * The usage for this prompt
		 * @since 0.5.0
		 * @type {ParsedUsage|CommandUsage}
		 */
		this.usage = usage;

		/**
		 * If the command reprompted for missing args
		 * @since 0.0.1
		 * @type {boolean}
		 */
		this.reprompted = false;

		/**
		 * The flag arguments resolved by this class
		 * @since 0.5.0
		 * @type {Object}
		 */
		this.flags = {};

		/**
		 * The string arguments derived from the usageDelim of the command
		 * @since 0.0.1
		 * @type {string[]}
		 */
		this.args = [];

		/**
		 * The parameters resolved by this class
		 * @since 0.0.1
		 * @type {any[]}
		 */
		this.params = [];

		/**
		 * The time-limit for re-prompting
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptTime = options.promptTime;

		/**
		 * The number of re-prompts before this TextPrompt gives up
		 * @since 0.5.0
		 * @type {number}
		 */
		this.promptLimit = options.promptLimit;

		/**
		 * Whether this prompt should respect quoted strings
		 * @since 0.5.0
		 * @type {boolean}
		 */
		this.quotedStringSupport = options.quotedStringSupport;

		/**
		 * Whether the current usage is a repeating arg
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._repeat = false;

		/**
		 * Whether the current usage is required
		 * @since 0.0.1
		 * @type {number}
		 * @private
		 */
		this._required = 0;

		/**
		 * How many time this class has reprompted
		 * @since 0.0.1
		 * @type {boolean}
		 * @private
		 */
		this._prompted = 0;

		/**
		 * A cache of the current usage while validating
		 * @since 0.0.1
		 * @type {Tag}
		 * @private
		 */
		this._currentUsage = {};
	}

	/**
	 * Runs the custom prompt.
	 * @since 0.5.0
	 * @param {string} prompt The message to initially prompt with
	 * @returns {Promise<any[]>} The parameters resolved
	 */
	async run(prompt) {
		const message = await this.message.prompt(prompt, this.promptTime);
		this._setup(message.content);
		return this.validateArgs();
	}

	/**
	 * Collects missing required arguments.
	 * @since 0.5.0
	 * @param {string} prompt The reprompt error
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async reprompt(prompt) {
		this._prompted++;
		if (this.typing) this.message.channel.stopTyping();
		const message = await this.message.prompt(
			this.message.language.get('MONITOR_COMMAND_HANDLER_REPROMPT', `<@!${this.message.author.id}>`, prompt, this.promptTime / 1000),
			this.promptTime
		);

		if (message.content.toLowerCase() === 'abort') throw this.message.language.get('MONITOR_COMMAND_HANDLER_ABORTED');

		if (this.typing) this.message.channel.startTyping();
		this.args[this.args.lastIndexOf(null)] = message.content;
		this.reprompted = true;

		if (this.usage.parsedUsage[this.params.length + 1] && this.usage.parsedUsage[this.params.length + 1].type === 'repeat') {
			return this.repeatingPrompt(prompt);
		}

		return this.validateArgs();
	}

	/**
	 * Collects repeating arguments.
	 * @since 0.5.0
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async repeatingPrompt() {
		if (this.typing) this.message.channel.stopTyping();
		let message;

		try {
			message = await this.message.prompt(
				this.message.language.get('MONITOR_COMMAND_HANDLER_REPEATING_REPROMPT', `<@!${this.message.author.id}>`, this._currentUsage.possibles[0].name, this.promptTime / 1000),
				this.promptTime
			);
		} catch (err) {
			return this.validateArgs();
		}

		if (message.content.toLowerCase() === 'cancel') return this.validateArgs();

		if (this.typing) this.message.channel.startTyping();
		this.args.push(message.content);
		this.reprompted = true;

		return this.repeatingPrompt();
	}

	/**
	 * Validates and resolves args into parameters
	 * @since 0.0.1
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async validateArgs() {
		if (this.params.length >= this.usage.parsedUsage.length && this.params.length >= this.args.length) {
			return this.finalize();
		} else if (this.usage.parsedUsage[this.params.length]) {
			this._currentUsage = this.usage.parsedUsage[this.params.length];
			this._required = this._currentUsage.required;
		} else if (this._currentUsage.repeat) {
			this._required = 0;
			this._repeat = true;
		} else {
			return this.finalize();
		}

		if (this._currentUsage.possibles.length !== 1) return this.multiPossibles(0);

		const possible = this._currentUsage.possibles[0];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.pushParam(undefined);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}

			const { response } = this._currentUsage;
			const error = typeof response === 'function' ? response(this.message, possible) : response;

			if (this._required === 1) return this.handleError(response || err);
			return this.handleError(error || (this.args[this.params.length] === undefined ?
				this.message.language.get('COMMANDMESSAGE_MISSING_REQUIRED', possible.name) :
				err)
			);
		}
	}

	/**
	 * Validates and resolves args into parameters, when multiple types of usage is defined
	 * @since 0.0.1
	 * @param {number} index The id of the possible usage currently being checked
	 * @returns {Promise<any[]>} The resolved parameters
	 * @private
	 */
	async multiPossibles(index) {
		const possible = this._currentUsage.possibles[index];
		const custom = this.usage.customResolvers[possible.type];

		if (possible.name in this.flags) this.args.splice(this.params.length, 0, this.flags[possible.name]);
		if (!this.client.argResolver[possible.type] && !custom) {
			this.client.emit('warn', `Unknown Argument Type encountered: ${possible.type}`);
			return this.multiPossibles(++index);
		}

		try {
			const res = await this.client.argResolver[custom ? 'custom' : possible.type](this.args[this.params.length], possible, this.message, custom);
			return this.pushParam(res);
		} catch (err) {
			if (index < this._currentUsage.possibles.length - 1) return this.multiPossibles(++index);
			if (!this._required) {
				this.args.splice(this.params.length, 0, undefined);
				return this.pushParam(undefined);
			}

			const { response } = this._currentUsage;
			const error = typeof response === 'function' ? response(this.message, possible) : response;

			if (this._required === 1) return this.handleError(response || err);
			return this.handleError(error || this.message.language.get('COMMANDMESSAGE_NOMATCH', this._currentUsage.possibles.map(poss => poss.name).join(', ')));
		}
	}

	/**
	 * Pushes a parameter into this.params, and resets the re-prompt count.
	 * @since 0.5.0
	 * @param {any} param The resolved parameter
	 * @returns {any[]}
	 * @private
	 */
	pushParam(param) {
		this._prompted = 0;
		this.params.push(param);
		return this.validateArgs();
	}

	/**
	 * Decides if the prompter should reprompt or throw the error found while validating.
	 * @since 0.5.0
	 * @param {string} err The error found while validating
	 * @returns {Promise<any[]>}
	 * @private
	 */
	async handleError(err) {
		this.args.splice(this.params.length, 1, null);
		if (this.promptLimit && this._prompted < this.promptLimit) return this.reprompt(err);
		throw err;
	}

	/**
	 * Finalizes parameters and arguments for this prompt.
	 * @since 0.5.0
	 * @returns {any[]}
	 * @private
	 */
	finalize() {
		for (let i = this.params.length - 1; i >= 0 && this.params[i] === undefined; i--) this.params.pop();
		for (let i = this.args.length - 1; i >= 0 && this.args[i] === undefined; i--) this.args.pop();
		return this.params;
	}

	/**
	 * Splits the original message string into arguments.
	 * @since 0.5.0
	 * @param {string} original The original message string
	 * @returns {void}
	 * @private
	 */
	_setup(original) {
		const { content, flags } = this.constructor.getFlags(original, this.usage.usageDelim);
		this.flags = flags;
		this.args = this.quotedStringSupport ?
			this.constructor.getQuotedStringArgs(content, this.usage.usageDelim).map(arg => arg.trim()) :
			this.constructor.getArgs(content, this.usage.usageDelim).map(arg => arg.trim());
	}

	/**
	 * Parses a message into string args
	 * @since 0.5.0
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {Object}
	 * @private
	 */
	static getFlags(content, delim) {
		const flags = {};
		content = content.replace(TextPrompt.flagRegex, (match, fl, ...quote) => {
			flags[fl] = (quote.slice(0, -2).find(el => el) || fl).replace(/\\/g, '');
			return '';
		}).replace(new RegExp(`(?:(\\s)\\s+|(${delim})(?:${delim})+)`, 'g'), '$1').trim();
		return { content, flags };
	}

	/**
	 * Parses a message into string args
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getArgs(content, delim) {
		const args = content.replace(new RegExp(`(${delim})(?:${delim})+`, 'g'), '$1').split(delim !== '' ? delim : undefined);
		return args.length === 1 && args[0] === '' ? [] : args;
	}

	/**
	 * Parses a message into string args taking into account quoted strings
	 * @since 0.0.1
	 * @param {string} content The remaining content
	 * @param {string} delim The delimiter
	 * @returns {string[]}
	 * @private
	 */
	static getQuotedStringArgs(content, delim) {
		if (!delim || delim === '') return [content];

		const args = [];

		for (let i = 0; i < content.length; i++) {
			let current = '';
			if (content.slice(i, i + delim.length) === delim) {
				i += delim.length - 1;
				continue;
			}
			const quote = quotes.find(qt => qt.includes(content[i]));
			if (quote) {
				const qts = quote.split('');
				while (i + 1 < content.length && (content[i] === '\\' || !qts.includes(content[i + 1]))) current += content[++i] !== '\\' ? content[i] : '';
				i++;
				args.push(current);
			} else {
				current += content[i];
				while (i + 1 < content.length && content.slice(i + 1, i + delim.length + 1) !== delim) current += content[++i];
				args.push(current);
			}
		}

		return args.length === 1 && args[0] === '' ? [] : args;
	}

}

/**
 * Regular Expression to match flags with quoted string support.
 * @since 0.5.0
 * @type {RegExp}
 * @static
 */
TextPrompt.flagRegex = new RegExp(`--([\\w-]{2,})(?:=(?:${quotes.map(qu => `[${qu}]((?:[^${qu}\\\\]|\\\\.)*)[${qu}]`).join('|')}|([\\w-]+)))?`, 'g');

module.exports = TextPrompt;
<|endoftext|>