<commit_before>import React, { Component } from 'react';
import ReactDOM from 'react-dom';

const getDisplayName = (WrappedComponent) => {
  const component = WrappedComponent.WrappedComponent || WrappedComponent;
  return component.displayName || component.name || 'Component';
};

const round = (x, steps) => Math.ceil(x / steps) * steps;

export default ({ config, store }) => (WrappedComponent) => class BlockResizeableDecorator extends Component {
  static displayName = `BlockDraggable(${getDisplayName(WrappedComponent)})`;
  static WrappedComponent = WrappedComponent.WrappedComponent || WrappedComponent;
  static defaultProps = {
    horizontal: 'relative',
    vertical: false,
    resizeSteps: 1,
    ...config
  };
  state = {
    hoverPosition: {},
    clicked: false,
  };

  setEntityData = (data) => {
    this.props.blockProps.setResizeData(data);
  }

  mouseLeave = () => {
    if (!this.state.clicked) {
      this.setState({ hoverPosition: {} });
    }
  }

  mouseMove = (evt) => {
    const { vertical, horizontal } = this.props;

    const hoverPosition = this.state.hoverPosition;
    const tolerance = 6;
    // TODO figure out if and how to achieve this without fetching the DOM node
    // eslint-disable-next-line react/no-find-dom-node
    const pane = ReactDOM.findDOMNode(this);
    const b = pane.getBoundingClientRect();
    const x = evt.clientX - b.left;
    const y = evt.clientY - b.top;

    const isTop = vertical && vertical !== 'auto' ? y < tolerance : false;
    const isLeft = horizontal ? x < tolerance : false;
    const isRight = horizontal ? x >= b.width - tolerance : false;
    const isBottom = vertical && vertical !== 'auto' ? y >= b.height - tolerance && y < b.height : false;

    const canResize = isTop || isLeft || isRight || isBottom;

    const newHoverPosition = {
      isTop, isLeft, isRight, isBottom, canResize
    };
    const hasNewHoverPositions = Object.keys(newHoverPosition).filter(
      (key) => hoverPosition[key] !== newHoverPosition[key]
    );
    if (hasNewHoverPositions.length) {
      this.setState({ hoverPosition: newHoverPosition });
    }
  }

  // Handle mousedown for resizing
  mouseDown = (event) => {
    // No mouse-hover-position data? Nothing to resize!
    if (!this.state.hoverPosition.canResize) {
      return;
    }
    const { resizeSteps, vertical, horizontal } = this.props;
    const { hoverPosition } = this.state;
    const { isTop, isLeft, isRight, isBottom } = hoverPosition;

    // TODO figure out how to achieve this without fetching the DOM node
    // eslint-disable-next-line react/no-find-dom-node
    const pane = ReactDOM.findDOMNode(this);
    const startX = event.clientX;
    const startY = event.clientY;
    const startWidth = parseInt(document.defaultView.getComputedStyle(pane).width, 10);
    const startHeight = parseInt(document.defaultView.getComputedStyle(pane).height, 10);

    // Do the actual drag operation
    const doDrag = (dragEvent) => {
      let width = (startWidth + dragEvent.clientX) - startX;
      let height = (startHeight + dragEvent.clientY) - startY;
      const block = store.getEditorRef().refs.editor;
      width = block.clientWidth < width ? block.clientWidth : width;
      height = block.clientHeight < height ? block.clientHeight : height;

      const widthPerc = (100 / block.clientWidth) * width;
      const heightPerc = (100 / block.clientHeight) * height;

      const newState = {};
      if ((isLeft || isRight) && horizontal === 'relative') {
        newState.width = resizeSteps ? round(widthPerc, resizeSteps) : widthPerc;
      } else if ((isLeft || isRight) && horizontal === 'absolute') {
        newState.width = resizeSteps ? round(width, resizeSteps) : width;
      }

      if ((isTop || isBottom) && vertical === 'relative') {
        newState.height = resizeSteps ? round(heightPerc, resizeSteps) : heightPerc;
      } else if ((isTop || isBottom) && vertical === 'absolute') {
        newState.height = resizeSteps ? round(height, resizeSteps) : height;
      }

      this.setState(newState);
    };

    // Finished dragging
    const stopDrag = () => {
      // TODO clean up event listeners
      document.removeEventListener('mousemove', doDrag, false);
      document.removeEventListener('mouseup', stopDrag, false);

      const { width, height } = this.state;
      this.setState({ clicked: false });
      // TODO check if timeout is necessary
      setTimeout(() => {
        this.setEntityData({ width, height });
      });
    };

    // TODO clean up event listeners
    document.addEventListener('mousemove', doDrag, false);
    document.addEventListener('mouseup', stopDrag, false);

    this.setState({ clicked: true });
  }

  render() {
    const {
      blockProps,
      vertical,
      horizontal,
      style,
      // using destructuring to make sure unused props are not passed down to the block
      resizeSteps, // eslint-disable-line no-unused-vars
      ...elementProps
    } = this.props;
    const { width, height, hoverPosition } = this.state;
    const { isTop, isLeft, isRight, isBottom } = hoverPosition;

    const styles = { position: 'relative', ...style };

    if (horizontal === 'auto') {
      styles.width = 'auto';
    } else if (horizontal === 'relative') {
      styles.width = `${(width || blockProps.resizeData.width || 40)}%`;
    } else if (horizontal === 'absolute') {
      styles.width = `${(width || blockProps.resizeData.width || 40)}px`;
    }

    if (vertical === 'auto') {
      styles.height = 'auto';
    } else if (vertical === 'relative') {
      styles.height = `${(height || blockProps.resizeData.height || 40)}%`;
    } else if (vertical === 'absolute') {
      styles.height = `${(height || blockProps.resizeData.height || 40)}px`;
    }

    // Handle cursor
    if ((isRight && isBottom) || (isLeft && isTop)) {
      styles.cursor = 'nwse-resize';
    } else if ((isRight && isTop) || (isBottom && isLeft)) {
      styles.cursor = 'nesw-resize';
    } else if (isRight || isLeft) {
      styles.cursor = 'ew-resize';
    } else if (isBottom || isTop) {
      styles.cursor = 'ns-resize';
    } else {
      styles.cursor = 'default';
    }

    const interactionProps = store.getReadOnly()
      ? {}
      : {
        onMouseDown: this.mouseDown,
        onMouseMove: this.mouseMove,
        onMouseLeave: this.mouseLeave,
      };

    return (
      <WrappedComponent
        {...elementProps}
        {...interactionProps}
        ref={(element) => { this.wrapper = element; }}
        style={styles}
      />
    );
  }
};
<commit_msg>make sure block props are passed down<commit_after>import React, { Component } from 'react';
import ReactDOM from 'react-dom';

const getDisplayName = (WrappedComponent) => {
  const component = WrappedComponent.WrappedComponent || WrappedComponent;
  return component.displayName || component.name || 'Component';
};

const round = (x, steps) => Math.ceil(x / steps) * steps;

export default ({ config, store }) => (WrappedComponent) => class BlockResizeableDecorator extends Component {
  static displayName = `BlockDraggable(${getDisplayName(WrappedComponent)})`;
  static WrappedComponent = WrappedComponent.WrappedComponent || WrappedComponent;
  static defaultProps = {
    horizontal: 'relative',
    vertical: false,
    resizeSteps: 1,
    ...config
  };
  state = {
    hoverPosition: {},
    clicked: false,
  };

  setEntityData = (data) => {
    this.props.blockProps.setResizeData(data);
  }

  mouseLeave = () => {
    if (!this.state.clicked) {
      this.setState({ hoverPosition: {} });
    }
  }

  mouseMove = (evt) => {
    const { vertical, horizontal } = this.props;

    const hoverPosition = this.state.hoverPosition;
    const tolerance = 6;
    // TODO figure out if and how to achieve this without fetching the DOM node
    // eslint-disable-next-line react/no-find-dom-node
    const pane = ReactDOM.findDOMNode(this);
    const b = pane.getBoundingClientRect();
    const x = evt.clientX - b.left;
    const y = evt.clientY - b.top;

    const isTop = vertical && vertical !== 'auto' ? y < tolerance : false;
    const isLeft = horizontal ? x < tolerance : false;
    const isRight = horizontal ? x >= b.width - tolerance : false;
    const isBottom = vertical && vertical !== 'auto' ? y >= b.height - tolerance && y < b.height : false;

    const canResize = isTop || isLeft || isRight || isBottom;

    const newHoverPosition = {
      isTop, isLeft, isRight, isBottom, canResize
    };
    const hasNewHoverPositions = Object.keys(newHoverPosition).filter(
      (key) => hoverPosition[key] !== newHoverPosition[key]
    );
    if (hasNewHoverPositions.length) {
      this.setState({ hoverPosition: newHoverPosition });
    }
  }

  // Handle mousedown for resizing
  mouseDown = (event) => {
    // No mouse-hover-position data? Nothing to resize!
    if (!this.state.hoverPosition.canResize) {
      return;
    }
    const { resizeSteps, vertical, horizontal } = this.props;
    const { hoverPosition } = this.state;
    const { isTop, isLeft, isRight, isBottom } = hoverPosition;

    // TODO figure out how to achieve this without fetching the DOM node
    // eslint-disable-next-line react/no-find-dom-node
    const pane = ReactDOM.findDOMNode(this);
    const startX = event.clientX;
    const startY = event.clientY;
    const startWidth = parseInt(document.defaultView.getComputedStyle(pane).width, 10);
    const startHeight = parseInt(document.defaultView.getComputedStyle(pane).height, 10);

    // Do the actual drag operation
    const doDrag = (dragEvent) => {
      let width = (startWidth + dragEvent.clientX) - startX;
      let height = (startHeight + dragEvent.clientY) - startY;
      const block = store.getEditorRef().refs.editor;
      width = block.clientWidth < width ? block.clientWidth : width;
      height = block.clientHeight < height ? block.clientHeight : height;

      const widthPerc = (100 / block.clientWidth) * width;
      const heightPerc = (100 / block.clientHeight) * height;

      const newState = {};
      if ((isLeft || isRight) && horizontal === 'relative') {
        newState.width = resizeSteps ? round(widthPerc, resizeSteps) : widthPerc;
      } else if ((isLeft || isRight) && horizontal === 'absolute') {
        newState.width = resizeSteps ? round(width, resizeSteps) : width;
      }

      if ((isTop || isBottom) && vertical === 'relative') {
        newState.height = resizeSteps ? round(heightPerc, resizeSteps) : heightPerc;
      } else if ((isTop || isBottom) && vertical === 'absolute') {
        newState.height = resizeSteps ? round(height, resizeSteps) : height;
      }

      this.setState(newState);
    };

    // Finished dragging
    const stopDrag = () => {
      // TODO clean up event listeners
      document.removeEventListener('mousemove', doDrag, false);
      document.removeEventListener('mouseup', stopDrag, false);

      const { width, height } = this.state;
      this.setState({ clicked: false });
      // TODO check if timeout is necessary
      setTimeout(() => {
        this.setEntityData({ width, height });
      });
    };

    // TODO clean up event listeners
    document.addEventListener('mousemove', doDrag, false);
    document.addEventListener('mouseup', stopDrag, false);

    this.setState({ clicked: true });
  }

  render() {
    const {
      blockProps,
      vertical,
      horizontal,
      style,
      // using destructuring to make sure unused props are not passed down to the block
      resizeSteps, // eslint-disable-line no-unused-vars
      ...elementProps
    } = this.props;
    const { width, height, hoverPosition } = this.state;
    const { isTop, isLeft, isRight, isBottom } = hoverPosition;

    const styles = { position: 'relative', ...style };

    if (horizontal === 'auto') {
      styles.width = 'auto';
    } else if (horizontal === 'relative') {
      styles.width = `${(width || blockProps.resizeData.width || 40)}%`;
    } else if (horizontal === 'absolute') {
      styles.width = `${(width || blockProps.resizeData.width || 40)}px`;
    }

    if (vertical === 'auto') {
      styles.height = 'auto';
    } else if (vertical === 'relative') {
      styles.height = `${(height || blockProps.resizeData.height || 40)}%`;
    } else if (vertical === 'absolute') {
      styles.height = `${(height || blockProps.resizeData.height || 40)}px`;
    }

    // Handle cursor
    if ((isRight && isBottom) || (isLeft && isTop)) {
      styles.cursor = 'nwse-resize';
    } else if ((isRight && isTop) || (isBottom && isLeft)) {
      styles.cursor = 'nesw-resize';
    } else if (isRight || isLeft) {
      styles.cursor = 'ew-resize';
    } else if (isBottom || isTop) {
      styles.cursor = 'ns-resize';
    } else {
      styles.cursor = 'default';
    }

    const interactionProps = store.getReadOnly()
      ? {}
      : {
        onMouseDown: this.mouseDown,
        onMouseMove: this.mouseMove,
        onMouseLeave: this.mouseLeave,
      };

    return (
      <WrappedComponent
        {...elementProps}
        {...interactionProps}
        blockProps={blockProps}
        ref={(element) => { this.wrapper = element; }}
        style={styles}
      />
    );
  }
};
<|endoftext|>


<commit_before>// no default signalhost, will use location.origin if available
exports.signalhost = '';<commit_msg>Removed defaults<commit_after><|endoftext|>


<commit_before>$.fn.highlightTerm = function(val) {
    // If an item starts with `val`, wrap the matched text with boldness.
    val = val.replace(/[^\w\s]/gi, '');
    var pat = new RegExp(val, 'gi');
    this.each(function() {
        var $this = $(this),
            txt = $this.html(),
            matchedTxt = txt.replace(pat, '$&');
        if (txt != matchedTxt) {
            $this.html(matchedTxt);
        }
    });
};

/*
 * searchSuggestions
 * Grants search suggestions to an input of type text/search.
 * Required:
 * $results - a container for the search suggestions, typically UL.
 * processCallback - callback function that deals with the XHR call & populates
                   - the $results element.
 * Optional:
 * searchType - possible values are 'AMO', 'MKT'
*/
$.fn.searchSuggestions = function($results, processCallback, searchType) {
    var $self = this,
        $form = $self.closest('form');

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

    var cat = $results.attr('data-cat');

    if (searchType == 'AMO') {
        // Some base elements that we don't want to keep creating on the fly.
        var msg;
        if (cat == 'personas') {
            msg = gettext('Search personas for {0}');
        } else if (cat == 'apps') {
            msg = gettext('Search apps for {0}');
        } else {
            msg = gettext('Search add-ons for {0}');
        }
        var base = template('
' +
                            '
{msg}

' +
                            '
');
        $results.html(base({'msg': msg}));
    } else if (searchType == 'MKT') {
        $results.html('

');
    }

    // Control keys that shouldn't trigger new requests.
    var ignoreKeys = [
        z.keys.SHIFT, z.keys.CONTROL, z.keys.ALT, z.keys.PAUSE,
        z.keys.CAPS_LOCK, z.keys.ESCAPE, z.keys.ENTER,
        z.keys.PAGE_UP, z.keys.PAGE_DOWN,
        z.keys.LEFT, z.keys.UP, z.keys.RIGHT, z.keys.DOWN,
        z.keys.HOME, z.keys.END,
        z.keys.COMMAND, z.keys.WINDOWS_RIGHT, z.keys.COMMAND_RIGHT,
        z.keys.WINDOWS_LEFT_OPERA, z.keys.WINDOWS_RIGHT_OPERA, z.keys.APPLE
    ];

    var gestureKeys = [z.keys.ESCAPE, z.keys.UP, z.keys.DOWN];

    function pageUp() {
        // Select the first element.
        $results.find('.sel').removeClass('sel');
        $results.removeClass('sel');
        $results.find('a:first').addClass('sel');
    }
    function pageDown() {
        // Select the last element.
        $results.find('.sel').removeClass('sel');
        $results.removeClass('sel');
        $results.find('a:last').addClass('sel');
    }

    function dismissHandler() {
        if ($results.hasClass('locked')) {
            return;
        }
        $results.removeClass('visible sel');
        $results.find('.sel').removeClass('sel');
        if (searchType == 'MKT') {
            $('#site-header').removeClass('suggestions');
        }
    }

    function gestureHandler(e) {
        // Bail if the results are hidden or if we have a non-gesture key
        // or if we have a alt/ctrl/meta/shift keybinding.
        if (!$results.hasClass('visible') ||
            $.inArray(e.which, gestureKeys) < 0 ||
            e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
            $results.trigger('keyIgnored');
            return;
        }
        e.preventDefault();
        if (e.which == z.keys.ESCAPE) {
            dismissHandler();
        } else if (e.which == z.keys.UP || e.which == z.keys.DOWN) {
            var $sel = $results.find('.sel'),
                $elems = $results.find('a'),
                i = $elems.index($sel.get(0));
            if ($sel.length && i >= 0) {
                if (e.which == z.keys.UP) {
                    // Clamp the value so it goes to the previous row
                    // but never goes beyond the first row.
                    i = Math.max(0, i - 1);
                } else {
                    // Clamp the value so it goes to the next row
                    // but never goes beyond the last row.
                    i = Math.min(i + 1, $elems.length - 1);
                }
            } else {
                i = 0;
            }
            $sel.removeClass('sel');
            $elems.eq(i).addClass('sel');
            $results.addClass('sel').trigger('selectedRowUpdate', [i]);
        }
    }

    function inputHandler(e) {
        var val = escape_($self.val());
        if (val.length < 3) {
            $results.filter('.visible').removeClass('visible');
            return;
        }

        // Required data to send to the callback.
        var settings = {
            '$results': $results,
            '$form': $form,
            'searchTerm': val
        };

        // Optional data for callback.
        if (searchType == 'AMO' || searchType == 'MKT') {
            settings['category'] = cat;
        }

        if ((e.type === 'keyup' && typeof e.which === 'undefined') ||
            $.inArray(e.which, ignoreKeys) >= 0) {
            $results.trigger('inputIgnored');
        } else {
            // XHR call and populate suggestions.
            processCallback(settings);
        }
    }

    var pollVal;

    if (z.capabilities.touch) {
        $self.focus(function() {
            pollVal = setInterval(function() {
                gestureHandler($self);
                inputHandler($self);
                return;
            }, 500);
        });
    }

    $self.keydown(gestureHandler)
         .bind('keyup paste', _.throttle(inputHandler, 250))
         .blur(function() {
           clearInterval(pollVal);
           _.delay(dismissHandler, 250);
       });

    $results.delegate('li, p', 'hover', function() {
        $results.find('.sel').removeClass('sel');
        $results.addClass('sel');
        $(this).find('a').addClass('sel');
    }).delegate('a', 'click', _pd(function() {
        $results.addClass('locked');
        $form.submit();
    }));

    $results.bind('highlight', function(e, val) {
        // If an item starts with `val`, wrap the matched text with boldness.
        $results.find('ul a span').highlightTerm(val);
        $results.addClass('visible');
        if (!$results.find('.sel').length) {
            pageUp();
        }
    });

    $results.bind('dismiss', dismissHandler);

    $form.submit(function(e) {
        clearInterval(pollVal);
        var $sel = $results.find('.sel');
        if ($sel.length && $sel.eq(0).attr('href') != '#') {
            e.stopPropagation();
            e.preventDefault();
            window.location = $sel.get(0).href;
        }
        $results.removeClass('locked');
        dismissHandler();
    });

    $(document).keyup(function(e) {
        if (fieldFocused(e)) {
            return;
        }
        if (e.which == 83) {
            $self.focus();
        }
    });

    return this;
};
<commit_msg>race condition tweaking<commit_after>$.fn.highlightTerm = function(val) {
    // If an item starts with `val`, wrap the matched text with boldness.
    val = val.replace(/[^\w\s]/gi, '');
    var pat = new RegExp(val, 'gi');
    this.each(function() {
        var $this = $(this),
            txt = $this.html(),
            matchedTxt = txt.replace(pat, '$&');
        if (txt != matchedTxt) {
            $this.html(matchedTxt);
        }
    });
};

/*
 * searchSuggestions
 * Grants search suggestions to an input of type text/search.
 * Required:
 * $results - a container for the search suggestions, typically UL.
 * processCallback - callback function that deals with the XHR call & populates
                   - the $results element.
 * Optional:
 * searchType - possible values are 'AMO', 'MKT'
*/
$.fn.searchSuggestions = function($results, processCallback, searchType) {
    var $self = this,
        $form = $self.closest('form');

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

    var cat = $results.attr('data-cat');

    if (searchType == 'AMO') {
        // Some base elements that we don't want to keep creating on the fly.
        var msg;
        if (cat == 'personas') {
            msg = gettext('Search personas for {0}');
        } else if (cat == 'apps') {
            msg = gettext('Search apps for {0}');
        } else {
            msg = gettext('Search add-ons for {0}');
        }
        var base = template('
' +
                            '
{msg}

' +
                            '
');
        $results.html(base({'msg': msg}));
    } else if (searchType == 'MKT') {
        $results.html('

');
    }

    // Control keys that shouldn't trigger new requests.
    var ignoreKeys = [
        z.keys.SHIFT, z.keys.CONTROL, z.keys.ALT, z.keys.PAUSE,
        z.keys.CAPS_LOCK, z.keys.ESCAPE, z.keys.ENTER,
        z.keys.PAGE_UP, z.keys.PAGE_DOWN,
        z.keys.LEFT, z.keys.UP, z.keys.RIGHT, z.keys.DOWN,
        z.keys.HOME, z.keys.END,
        z.keys.COMMAND, z.keys.WINDOWS_RIGHT, z.keys.COMMAND_RIGHT,
        z.keys.WINDOWS_LEFT_OPERA, z.keys.WINDOWS_RIGHT_OPERA, z.keys.APPLE
    ];

    var gestureKeys = [z.keys.ESCAPE, z.keys.UP, z.keys.DOWN];

    function pageUp() {
        // Select the first element.
        $results.find('.sel').removeClass('sel');
        $results.removeClass('sel');
        $results.find('a:first').addClass('sel');
    }
    function pageDown() {
        // Select the last element.
        $results.find('.sel').removeClass('sel');
        $results.removeClass('sel');
        $results.find('a:last').addClass('sel');
    }

    function dismissHandler() {
        if ($results.hasClass('locked')) {
            return;
        }
        $results.removeClass('visible sel');
        $results.find('.sel').removeClass('sel');
        if (searchType == 'MKT') {
            $('#site-header').removeClass('suggestions');
        }
    }

    function gestureHandler(e) {
        // Bail if the results are hidden or if we have a non-gesture key
        // or if we have a alt/ctrl/meta/shift keybinding.
        if (!$results.hasClass('visible') ||
            $.inArray(e.which, gestureKeys) < 0 ||
            e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
            $results.trigger('keyIgnored');
            return;
        }
        e.preventDefault();
        if (e.which == z.keys.ESCAPE) {
            dismissHandler();
        } else if (e.which == z.keys.UP || e.which == z.keys.DOWN) {
            var $sel = $results.find('.sel'),
                $elems = $results.find('a'),
                i = $elems.index($sel.get(0));
            if ($sel.length && i >= 0) {
                if (e.which == z.keys.UP) {
                    // Clamp the value so it goes to the previous row
                    // but never goes beyond the first row.
                    i = Math.max(0, i - 1);
                } else {
                    // Clamp the value so it goes to the next row
                    // but never goes beyond the last row.
                    i = Math.min(i + 1, $elems.length - 1);
                }
            } else {
                i = 0;
            }
            $sel.removeClass('sel');
            $elems.eq(i).addClass('sel');
            $results.addClass('sel').trigger('selectedRowUpdate', [i]);
        }
    }

    function inputHandler(e) {
        var val = escape_($self.val());
        if (val.length < 3) {
            $results.filter('.visible').removeClass('visible');
            return;
        }

        // Required data to send to the callback.
        var settings = {
            '$results': $results,
            '$form': $form,
            'searchTerm': val
        };

        // Optional data for callback.
        if (searchType == 'AMO' || searchType == 'MKT') {
            settings['category'] = cat;
        }

        if ((e.type === 'keyup' && typeof e.which === 'undefined') ||
            $.inArray(e.which, ignoreKeys) >= 0) {
            $results.trigger('inputIgnored');
        } else {
            // XHR call and populate suggestions.
            processCallback(settings);
        }
    }

    var pollVal;

    if (z.capabilities.touch) {
        $self.focus(function() {
            pollVal = setInterval(function() {
                gestureHandler($self);
                inputHandler($self);
                return;
            }, 150);
        });
    }

    $self.keydown(gestureHandler)
         .bind('keyup paste', _.throttle(inputHandler, 250))
         .blur(function() {
           clearInterval(pollVal);
           _.delay(dismissHandler, 250);
       });

    $results.delegate('li, p', 'hover', function() {
        $results.find('.sel').removeClass('sel');
        $results.addClass('sel');
        $(this).find('a').addClass('sel');
    }).delegate('a', 'click', _pd(function() {
        $results.addClass('locked');
        $form.submit();
    }));

    $results.bind('highlight', function(e, val) {
        // If an item starts with `val`, wrap the matched text with boldness.
        $results.find('ul a span').highlightTerm(val);
        $results.addClass('visible');
        if (!$results.find('.sel').length) {
            pageUp();
        }
    });

    $results.bind('dismiss', dismissHandler);

    $form.submit(function(e) {
        clearInterval(pollVal);
        var $sel = $results.find('.sel');
        if ($sel.length && $sel.eq(0).attr('href') != '#') {
            e.stopPropagation();
            e.preventDefault();
            window.location = $sel.get(0).href;
        }
        $results.removeClass('locked');
        dismissHandler();
    });

    $(document).keyup(function(e) {
        if (fieldFocused(e)) {
            return;
        }
        if (e.which == 83) {
            $self.focus();
        }
    });

    return this;
};
<|endoftext|>


<commit_before>var fs = require('fs'),
	sys = require('sys'),
    exec = require('child_process').exec;

var deployer = exports.deployer = function (project, callback) {
	var tempFile = __dirname + '/tmp/' + Math.random() + '.js';
	fs.writeFile(tempFile, project.lastHandler.code, function(err) {
		if (err) throw err;
		console.log(sys.inspect('node '+tempFile));
		node = exec('node '+tempFile, function (error, stdout, stderr) {
			sys.print('stdout: ' + stdout);
			sys.print('stderr: ' + stderr);
			if (error !== null) {
				console.log('exec error: ' + error);
			}
			callback(stdout, stderr);
		});
	});

}

<commit_msg>Remove the temp file after deployment.<commit_after>var fs = require('fs'),
	sys = require('sys'),
    exec = require('child_process').exec;

var deployer = exports.deployer = function (project, callback) {
	var tempFile = __dirname + '/tmp/' + Math.random() + '.js';
	fs.writeFile(tempFile, project.lastHandler.code, function(err) {
		if (err) throw err;
		node = exec('node ' + tempFile, function (error, stdout, stderr) {
			if (error !== null) {
				console.log('exec error: ' + error);
			}
			fs.unlink(tempFile, function (err) {
				if (err)
					console.log("Unable to delete " + tempFile + ".");
				callback(stdout, stderr);
			});
		});
	});

}

<|endoftext|>


<commit_before>
import sagaHelper from "redux-saga-testing";
import { takeLatest } from "redux-saga";
import { put, select, call, cps, take } from "redux-saga/effects";

import { Meteor } from "meteor/meteor";

import { initial } from "../../reducer";
import actions from "../../actions";
import types from "../../types";

import { chargeTransaction } from "../";

describe("early return", () => {
  const it = sagaHelper(chargeTransaction({ state: "HAPPY" }));
  it("exits on wrong state", result => {
    expect(result).toBeUndefined();
  });
});

describe("successful charge without saved payment", () => {
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("sets the loading state", ({ PUT: { action }}) => {
    expect(action).toEqual(actions.loading());
  });

  it("reads from the store again if no saved account and not a schedule", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("reads from the store again if not a saved account", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("waits for the url to return if not found", result => {
    expect(result).toEqual(take(types.SET_TRANSACTION_DETAILS));
    return { url: "https://example.com/TOKEN" };
  });

  it("tries to submit a transaction with the correct token", result => {
    expect(result.CALL.args[0].variables).toEqual({
      token: "TOKEN",
      name: null,
      id: undefined
    });
    return true;
  });

  it("puts a success state", result => {
    expect(result).toEqual(put(actions.setState("success")));
  });

  it("ends after a normal charge", result => {
    expect(result).toBeUndefined();
  });

});

describe("successful charge saved payment and schedules", () => {
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
      savedAccount: { id: 1 },
      schedule: { start: "20200101" },
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("sets the loading state", ({ PUT: { action }}) => {
    expect(action).toEqual(actions.loading());
  });

  it("tries to call create order mutation", result => {
    const variables = result.CALL.args[0].variables;
    expect(variables).toEqual({
      data: JSON.stringify({
        billing: {
          "first-name": null,
          "last-name": null,
          email: null,
          address1: null,
          address2: "",
          city: null,
          state: null,
          postal: null,
        },
        "merchant-defined-field-2": null,
        plan: { payments: 0, amount: 0 },
        "start-date": "20200101",
        "merchant-defined-field-3": "20200101",
        "merchant-defined-field-1": "",
        "merchant-defined-field-4": "",
        savedAccount: 1,
        savedAccountName: undefined,
      }),
      instant: true,
      id: undefined,
    });

    return true;
  });

  it("puts a success state", result => {
    expect(result).toEqual(put(actions.setState("success")));
  });

  it("ends after a normal charge", result => {
    expect(result).toBeUndefined();
  });

});

window.ga = jest.fn();
describe("successful charge using a saved payment", () => {
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
      savedAccount: { id: 1 },
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("sets the loading state", ({ PUT: { action }}) => {
    expect(action).toEqual(actions.loading());
  });

  it("reads from the store again if no saved account and not a schedule", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("tries to submit person details to ensure a url", result => {
    expect(result.CALL.args[0].variables).toEqual({
      data: JSON.stringify({
        amount: 0,
        billing: {
          'first-name': null,
          'last-name': null,
          email: null,
          address1: null,
          address2: '',
          city: null,
          state: null,
          postal: null
        },
        'merchant-defined-field-2': null,
        savedAccount: 1,
        savedAccountName: undefined
      }),
      id: null,
      instant: false,
    });
    return { data: { response: { url: "http://example.com/TOKEN" }}};
  });

  // BELOW IS TESTED IN ./submitPersonDetails
  it("submits a form", result => true);
  it("sets a url", result => {});

  it("reads from the store again after submitting person details", result => {
    expect(result.SELECT).toBeTruthy();
    return { ...{ give: { ...initalState.give, ...{  url: "http://example.com/TOKEN" }} } };
  });

  it("tries to submit a transaction with the correct token", result => {
    expect(result.CALL.args[0].variables).toEqual({
      token: "TOKEN",
      id: undefined,
      name: null
    });
    return true;
  });

  it("puts a success state", result => {
    ga.mockClear();
    expect(result).toEqual(put(actions.setState("success")));
  });

  it("ends after a normal charge", result => {
    expect(window.ga.mock.calls).toMatchSnapshot();
    expect(window.ga).toHaveBeenCalledTimes(2)
    delete window.ga;
    expect(result).toBeUndefined();
  });
});

describe("validating cards on saved payment creation", () => {
  const giveData = {
    personal: {},
    billing: {},
    payment: {
      type: "cc"
    },
  };
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
      savedAccount: {},
      data: giveData,
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("skips over loading state", result => {});
  it("skips over setting store the first time", result => {
    return {};
  });
  it("passes give data", result => ({
    give: {
      url: "URL-Mc-URLface",
      data: giveData,
    }
  }));

  it("calls validation method", result => {
    // validate calls select() first.
    // if validate wasn't being called, the next yield in the file
    // is a graphql call, so this would fail.
    expect(result.SELECT).toBeDefined();
    return { validationError: true };
  });
});
<commit_msg>fixed tests<commit_after>
import sagaHelper from "redux-saga-testing";
import { takeLatest } from "redux-saga";
import { put, select, call, cps, take } from "redux-saga/effects";

import { Meteor } from "meteor/meteor";

import { initial } from "../../reducer";
import actions from "../../actions";
import types from "../../types";
import { validate } from "../";

import { chargeTransaction } from "../";

describe("early return", () => {
  const it = sagaHelper(chargeTransaction({ state: "HAPPY" }));
  it("exits on wrong state", result => {
    expect(result).toBeUndefined();
  });
});

describe("successful charge without saved payment", () => {
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("sets the loading state", ({ PUT: { action }}) => {
    expect(action).toEqual(actions.loading());
  });

  it("reads from the store again if no saved account and not a schedule", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("reads from the store again if not a saved account", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("waits for the url to return if not found", result => {
    expect(result).toEqual(take(types.SET_TRANSACTION_DETAILS));
    return { url: "https://example.com/TOKEN" };
  });

  // STEP THROUGH VALIDATION
  it("requires initial seed data", () => ({ give: { ...initial } }));
  it("formats the data in the store", result => {
    return { data: { response: { url: "http://test.com/TOKEN" } } };
  });
  it("tries to submit payment details with the data and url", result => {
    result.next();
    return null;
  });
  it("tries to submit payment details with the data and url", result => {
    return { };
  });
  // DONE VALIDATING

  it("tries to submit a transaction with the correct token", result => {
    expect(result.CALL.args[0].variables).toEqual({
      token: "TOKEN",
      name: null,
      id: undefined
    });
    return true;
  });

  it("puts a success state", result => {
    expect(result).toEqual(put(actions.setState("success")));
  });

  it("ends after a normal charge", result => {
    expect(result).toBeUndefined();
  });

});

describe("successful charge saved payment and schedules", () => {
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
      savedAccount: { id: 1 },
      schedule: { start: "20200101" },
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("sets the loading state", ({ PUT: { action }}) => {
    expect(action).toEqual(actions.loading());
  });

  it("tries to call create order mutation", result => {
    const variables = result.CALL.args[0].variables;
    expect(variables).toEqual({
      data: JSON.stringify({
        billing: {
          "first-name": null,
          "last-name": null,
          email: null,
          address1: null,
          address2: "",
          city: null,
          state: null,
          postal: null,
        },
        "merchant-defined-field-2": null,
        plan: { payments: 0, amount: 0 },
        "start-date": "20200101",
        "merchant-defined-field-3": "20200101",
        "merchant-defined-field-1": "",
        "merchant-defined-field-4": "",
        savedAccount: 1,
        savedAccountName: undefined,
      }),
      instant: true,
      id: undefined,
    });

    return true;
  });

  it("puts a success state", result => {
    expect(result).toEqual(put(actions.setState("success")));
  });

  it("ends after a normal charge", result => {
    expect(result).toBeUndefined();
  });

});

window.ga = jest.fn();
describe("successful charge using a saved payment", () => {
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
      savedAccount: { id: 1 },
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("sets the loading state", ({ PUT: { action }}) => {
    expect(action).toEqual(actions.loading());
  });

  it("reads from the store again if no saved account and not a schedule", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("tries to submit person details to ensure a url", result => {
    expect(result.CALL.args[0].variables).toEqual({
      data: JSON.stringify({
        amount: 0,
        billing: {
          'first-name': null,
          'last-name': null,
          email: null,
          address1: null,
          address2: '',
          city: null,
          state: null,
          postal: null
        },
        'merchant-defined-field-2': null,
        savedAccount: 1,
        savedAccountName: undefined
      }),
      id: null,
      instant: false,
    });
    return { data: { response: { url: "http://example.com/TOKEN" }}};
  });

  // BELOW IS TESTED IN ./submitPersonDetails
  it("submits a form", result => true);
  it("sets a url", result => {});

  it("reads from the store again after submitting person details", result => {
    expect(result.SELECT).toBeTruthy();
    return { ...{ give: { ...initalState.give, ...{  url: "http://example.com/TOKEN" }} } };
  });

  it("tries to submit a transaction with the correct token", result => {
    expect(result.CALL.args[0].variables).toEqual({
      token: "TOKEN",
      id: undefined,
      name: null
    });
    return true;
  });

  it("puts a success state", result => {
    ga.mockClear();
    expect(result).toEqual(put(actions.setState("success")));
  });

  it("ends after a normal charge", result => {
    expect(window.ga.mock.calls).toMatchSnapshot();
    expect(window.ga).toHaveBeenCalledTimes(2)
    delete window.ga;
    expect(result).toBeUndefined();
  });
});

describe("validating cards on saved payment creation", () => {
  const giveData = {
    personal: {},
    billing: {},
    payment: {
      type: "cc"
    },
  };
  const it = sagaHelper(chargeTransaction({ state: "submit" }));
  const initalState = {
    give: {
      ...initial,
      savedAccount: {},
      data: giveData,
    },
  };

  it("reads the state from the store", result => {
    expect(result.SELECT).toBeTruthy();
    return initalState;
  });

  it("skips over loading state", result => {});
  it("skips over setting store the first time", result => {
    return {};
  });
  it("passes give data", result => ({
    give: {
      url: "URL-Mc-URLface",
      data: giveData,
    }
  }));

  it("calls validation method", result => {
    // validate calls select() first.
    // if validate wasn't being called, the next yield in the file
    // is a graphql call, so this would fail.
    expect(result.SELECT).toBeDefined();
    return { validationError: true };
  });
});
<|endoftext|>


<commit_before>"use strict";

/*
 * # Level 1 Headers are Chapters (they get title pages)
 *
 * ## Level 2 Headers represent pages ("Time Of Day: Location")
 *
 * [ID: Text](path) at the end represent choices
 *
 * ### Level 3 Headers are optional paragraphs, that show depending on choices
 * and terminated by an empty level 3 header (###)
 *
 * Standard markdown formatting otherwise applies.
 */

function parseStory( storyCode )
{
    let chapters = [];

    let linesOfStoryCode = storyCode.split("\n");
    let currentChapter = "Untitled";

    let doNothing = function() {};
    let startNewChoiceLink = doNothing;
    let startNewChoiceParagraph = doNothing;
    let startNewPage = doNothing;
    let startNewChapter = doNothing;
    let loadIntoParagraph = doNothing;

    linesOfStoryCode.forEach( function ( line ) {

        let trimmedLine = line.trim();

        if ( trimmedLine.startsWith("[") ) {

            startNewChoiceLink( trimmedLine );

        } else if ( trimmedLine.startsWith("###") ) {

            startNewChoiceParagraph( trimmedLine );

        } else if ( trimmedLine.startsWith("##") ) {

            startNewPage( trimmedLine );

        } else if ( trimmedLine.startsWith("#") ) {

            startNewChapter( trimmedLine );

        } else if ( trimmedLine.startsWith("[") ) {

            startNewChoiceLink( trimmedLine );

        } else {

            loadIntoParagraph( line );

        }

    });

}
<commit_msg>Story parser work.<commit_after>"use strict";

/*
 * # Level 1 Headers are Chapters (they get title pages)
 *
 * ## Level 2 Headers represent pages ("Time Of Day: Location")
 *
 * [ID: Text](path) at the end represent choices
 *
 * ### Level 3 Headers are optional paragraphs, that show depending on choices
 * and terminated by an empty level 3 header (###)
 *
 * Standard markdown formatting otherwise applies.
 */

function parseStory( storyCode )
{
    let story = {
        "chapters" : [],

        "current chapter" : {
            "day": "",
            "pages": [],
        },

        "current page" : {
            "time" : "",
            "location": "",
            "paragraphs": [],
            "choice links": [],
        },

        "current paragraph": {
            "is choice": false,
            "choice id": "",
            "content": "",
        }
    };

    // Go line by line.
    storyCode.split("\n").forEach( function ( line ) {

        let trimmedLine = line.trim();

        if ( trimmedLine.startsWith("[") ) {

            story = addChoiceLink( story, trimmedLine );

        } else if ( trimmedLine.startsWith("###") ) {

            story = endCurrentChoiceParagraph( story );
            story = startNewChoiceParagraph( story, trimmedLine );

        } else if ( trimmedLine.startsWith("##") ) {

            story = endCurrentPage( story );
            story = startNewPage( story, trimmedLine );

        } else if ( trimmedLine.startsWith("#") ) {

            story = endCurrentChapter( story );
            story = startNewChapter( story, trimmedLine );

        } else {

            story = loadContentInPageOrChoiceParagraph( story, line );

        }

    });

    return story;
}

function startNewChapter( story, trimmedLine )
{
}

function endCurrentChapter( story )
{
}

function addChoiceLink( story, trimmedLine )
{
}

function startNewPage( story, trimmedLine )
{
}

function endCurrentPage( story )
{
}

function startNewChoiceParagraph( story, trimmedLine )
{
}

function endCurrentChoiceParagraph( story )
{
}

function loadContentInPageOrChoiceParagraph( story, line )
{
}

<|endoftext|>


<commit_before>var util  = require('util');
var spawn = require('child_process').spawn;

spawnTest('./tests/regression-tests/test1.js', 'test1_consolelog', 0);
spawnTest('./tests/regression-tests/test2.js', 'test2_consolelog', 0);

function spawnTest(path, name, expectedExitCode) {
    var p = spawn('node', [path]);

    p.stdout.on('data', function (data) {
        console.log(name + '|' + p.pid + ':' + data);
    });

    p.stderr.on('data', function (data) {
        console.log(name + '|' + p.pid + ' ERROR: ' + data);
    });

    p.on('exit', function (code) {
        if (code === expectedExitCode) {
            console.log(name + '|' + p.pid + ': PASSED');
        } else {
            console.log(name + '|' + p.pid + ': FAILED, exited with code ' + code);
            process.exit(1);
        }
    });
}<commit_msg>- add runAllTests in a folder<commit_after>var fs = require('fs');
var util = require('util');
var spawn = require('child_process').spawn;

//runAllTests('regression-tests');

spawnTest('regression-tests/test1.js', 'test1_consolelog');
spawnTest('regression-tests/test2.js', 'test2_consolelog');
spawnTest('regression-tests/test3.js', 'test3_consolelog');

function runAllTests(path) {
    fs.readdirSync(__dirname + path).forEach(function(file) {
        var name = file.substr(0, file.indexOf('.'));
        spawnTest(__dirname + '/' + path + '/' + file, name);
    });
}

function spawnTest(path, name, expectedExitCode) {
    if (!expectedExitCode) {
        expectedExitCode = 0;
    }
    var p = spawn('node', [__dirname + '/' + path]);

    p.stdout.on('data', function (data) {
        console.log(p.pid + '|' + name + ':' + data);
    });

    p.stderr.on('data', function (data) {
        console.log(p.pid + '|' + name + ' ERROR: ' + data);
    });

    p.on('exit', function (code) {
        if (code === expectedExitCode) {
            console.log(p.pid + '|' + name + ': PASSED');
        } else {
            console.log(p.pid + '|' + name + ': FAILED, exited with code ' + code);
            process.exit(1);
        }
    });
}<|endoftext|>


<commit_before>import { injectGlobal } from 'styled-components';

const globalStyles = injectGlobal`
  html,
  body {
    height: 100%;
  }

	body {
    box-sizing: border-box;
    color: #1f2228;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    font-size: 16px;
		margin: 0;
    overflow-y: hidden;
	}

  *,
  *:before,
  *:after {
    box-sizing: inherit;
  }

  a[href]:not([class]) {
    color: #3685d6;
    text-decoration: none;

    &:hover {
      text-decoration: underline;
    }
  }

  a > svg {
    vertical-align: text-bottom;
  }
`;

export default globalStyles;
<commit_msg>feat: set body font-size to 14px + antialiased fonts<commit_after>import { injectGlobal } from 'styled-components';

const globalStyles = injectGlobal`
  html,
  body {
    height: 100%;
  }

	body {
    box-sizing: border-box;
    color: #1f2228;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    font-size: 14px;
		margin: 0;
    overflow-y: hidden;

    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    -moz-osx-font-smoothing: grayscale;
    font-feature-settings: 'liga';
	}

  *,
  *:before,
  *:after {
    box-sizing: inherit;
  }

  a[href]:not([class]) {
    color: #3685d6;
    text-decoration: none;

    &:hover {
      text-decoration: underline;
    }
  }

  a > svg {
    vertical-align: text-bottom;
  }
`;

export default globalStyles;
<|endoftext|>


<commit_before>import { combineReducers } from 'redux'
import {paragraphs} from './paragraphsReducer'

export const reducer = combineReducers({
	paragraphs
})

//this is a reducer<commit_msg>comment<commit_after>import { combineReducers } from 'redux'
import {paragraphs} from './paragraphsReducer'

//new lone

export const reducer = combineReducers({
	paragraphs
})
<|endoftext|>


<commit_before>/* jshint node: true */

var Promise = require('bluebird');
var orm = require('../../util/orm');
var errors = require('../../errors');
var sectors = require('../sectors');

module.exports = {

  // Doit retourner le délai en tours avant l'exécution de l'action
  cooldown:  60 * 1000, // 60s de cooldown

  // Exécute l'action
  execute: Promise.method(function(bot, params) {

    var Sector = orm.model('Sector');

    // On effectue toutes les opérations dans une transaction
    return orm.transaction(function(trx) {

      var opts = {transacting: trx};

      // On récupère le secteur courant du Bot
      return bot.related('sector')
        .fetch(opts)
        .then(function(currentSector) {

          var dir = params.dir;
          var coords = {
            x: currentSector.get('x'),
            y: currentSector.get('y')
          };

          switch (dir) {
            case "N" :
              coords.y -= 1;
              break;
            case "S" :
              coords.y += 1;
              break;
            case "W" :
              coords.x -= 1;
              break;
            case "E" :
              coords.x += 1;
              break;
            case "SE" :
              coords.x += 1;
              coords.y += 1;
              break;
            case "NE" :
              coords.x += 1;
              coords.y -= 1;
              break;
            case "SW" :
              coords.x += 1;
              coords.y -= 1;
              break;
            case "NW" :
              coords.x -= 1;
              coords.y -= 1;
              break;
            default:
              throw new errors.UnknownDirectionError(dir);
          }

          return coords;

        })
        .then(function(coords) {
          return Sector
            .where(coords)
            .fetch(opts)
            .then(function(existing) {
              if(existing) {
                return existing;
              } else {
                var sector = sectors.getSector(coords.x, coords.y);
                return new Sector(sector).save(null, opts);
              }
            });
        })
        .then(function(destination) {
          bot.set('sector_id', destination.id);
          return bot.save(null, opts);
        })
        .then(function() {
          delete bot.relations.sector;
          return bot.related('sector').fetch(opts);
        });

    });

  })

};
<commit_msg>Correction bug direction action move<commit_after>/* jshint node: true */

var Promise = require('bluebird');
var orm = require('../../util/orm');
var errors = require('../../errors');
var sectors = require('../sectors');

module.exports = {

  // Doit retourner le délai en tours avant l'exécution de l'action
  cooldown:  60 * 1000, // 60s de cooldown

  // Exécute l'action
  execute: Promise.method(function(bot, params) {

    var Sector = orm.model('Sector');

    // On effectue toutes les opérations dans une transaction
    return orm.transaction(function(trx) {

      var opts = {transacting: trx};

      // On récupère le secteur courant du Bot
      return bot.related('sector')
        .fetch(opts)
        .then(function(currentSector) {

          var dir = params.dir;
          var coords = {
            x: currentSector.get('x'),
            y: currentSector.get('y')
          };

          switch (dir) {
            case "N" :
              coords.y -= 1;
              break;
            case "S" :
              coords.y += 1;
              break;
            case "W" :
              coords.x -= 1;
              break;
            case "E" :
              coords.x += 1;
              break;
            case "SE" :
              coords.x += 1;
              coords.y += 1;
              break;
            case "NE" :
              coords.x += 1;
              coords.y -= 1;
              break;
            case "SW" :
              coords.x -= 1;
              coords.y += 1;
              break;
            case "NW" :
              coords.x -= 1;
              coords.y -= 1;
              break;
            default:
              throw new errors.UnknownDirectionError(dir);
          }

          return coords;

        })
        .then(function(coords) {
          return Sector
            .where(coords)
            .fetch(opts)
            .then(function(existing) {
              if(existing) {
                return existing;
              } else {
                var sector = sectors.getSector(coords.x, coords.y);
                return new Sector(sector).save(null, opts);
              }
            });
        })
        .then(function(destination) {
          bot.set('sector_id', destination.id);
          return bot.save(null, opts);
        })
        .then(function() {
          delete bot.relations.sector;
          return bot.related('sector').fetch(opts);
        });

    });

  })

};
<|endoftext|>


<commit_before>'use strict';

var api = require('../api');
var RSVP = require('rsvp');
var utils = require('../utils');
var logger = require('../logger');
var _ = require('lodash');

var API_VERSION = 'v1beta1';

function _listConfigs(projectId) {
  return api.request('GET', utils.endpoint([API_VERSION, 'projects', projectId, 'configs']), {
    auth: true,
    origin: api.runtimeconfigOrigin
  }).then(function(resp) {
    return RSVP.resolve(resp.body.configs);
  });
}

function _createConfig(projectId, configId) {
  var path = _.join(['projects', projectId, 'configs'], '/');
  var endpoint = utils.endpoint([API_VERSION, path]);
  return api.request('POST', endpoint, {
    auth: true,
    origin: api.runtimeconfigOrigin,
    data: {
      name: path + '/' + configId
    }
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 409) {
    // Config has already been created as part of a parallel operation during firebase functions:config:set
      return RSVP.resolve();
    }
    return RSVP.reject();
  });
}

function _deleteConfig(projectId, configId) {
  return api.request('DELETE', utils.endpoint([API_VERSION, 'projects', projectId, 'configs', configId]), {
    auth: true,
    origin: api.runtimeconfigOrigin
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) {
      logger.debug('Config already deleted.');
      return RSVP.resolve();
    }
    return RSVP.reject(err);
  });
}

function _listVariables(configPath) {
  return api.request('GET', utils.endpoint([API_VERSION, configPath, 'variables']), {
    auth: true,
    origin: api.runtimeconfigOrigin
  }).then(function(resp) {
    return RSVP.resolve(resp.body.variables);
  });
}

function _getVariable(varPath) {
  return api.request('GET', utils.endpoint([API_VERSION, varPath]), {
    auth: true,
    origin: api.runtimeconfigOrigin
  }).then(function(resp) {
    return RSVP.resolve(resp.body);
  });
}

function _createVariable(projectId, configId, varId, value) {
  var path = _.join(['projects', projectId, 'configs', configId, 'variables'], '/');
  var endpoint = utils.endpoint([API_VERSION, path]);
  return api.request('POST', endpoint, {
    auth: true,
    origin: api.runtimeconfigOrigin,
    data: {
      name: path + '/' + varId,
      text: value
    }
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) { // parent config doesn't exist yet
      return _createConfig(projectId, configId).then(function() {
        return _createVariable(projectId, configId, varId, value);
      });
    }
    return RSVP.reject(err);
  });
}

function _updateVariable(projectId, configId, varId, value) {
  var path = _.join(['projects', projectId, 'configs', configId, 'variables', varId], '/');
  var endpoint = utils.endpoint([API_VERSION, path]);
  return api.request('PUT', endpoint, {
    auth: true,
    origin: api.runtimeconfigOrigin,
    data: {
      name: path,
      text: value
    }
  });
}

function _setVariable(projectId, configId, varId, value) {
  var path = _.join(['projects', projectId, 'configs', configId, 'variables', varId], '/');
  return _getVariable(path).then(function() {
    return _updateVariable(projectId, configId, varId, value);
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) {
      return _createVariable(projectId, configId, varId, value);
    }
    return RSVP.reject(err);
  });
}

function _deleteVariable(projectId, configId, varId) {
  var endpoint = utils.endpoint([API_VERSION, 'projects', projectId, 'configs', configId, 'variables', varId])
    + '?recursive=true';
  return api.request('DELETE', endpoint, {
    auth: true,
    origin: api.runtimeconfigOrigin
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) {
      logger.debug('Variable already deleted.');
      return RSVP.resolve();
    }
    return RSVP.reject(err);
  });
}

module.exports = {
  configs: {
    list: _listConfigs,
    create: _createConfig,
    delete: _deleteConfig
  },
  variables: {
    list: _listVariables,
    get: _getVariable,
    set: _setVariable,
    delete: _deleteVariable
  }
};
<commit_msg>Retry Runtime Config API calls when there is 500 or 503 error code (#539)<commit_after>'use strict';

var api = require('../api');
var RSVP = require('rsvp');
var utils = require('../utils');
var logger = require('../logger');
var _ = require('lodash');

var API_VERSION = 'v1beta1';

function _retryOnServerError(requestFunction) {
  return requestFunction().catch(function(err) {
    if (_.includes([500, 503], _.get(err, 'context.response.statusCode'))) {
      return new Promise(function(resolve) {
        setTimeout(resolve, 1000);
      })
      .then(requestFunction);
    }
    return RSVP.reject(err);
  });
}

function _listConfigs(projectId) {
  return _retryOnServerError(function() {
    return api.request('GET', utils.endpoint([API_VERSION, 'projects', projectId, 'configs']), {
      auth: true,
      origin: api.runtimeconfigOrigin
    });
  }).then(function(resp) {
    return RSVP.resolve(resp.body.configs);
  });
}

function _createConfig(projectId, configId) {
  var path = _.join(['projects', projectId, 'configs'], '/');
  var endpoint = utils.endpoint([API_VERSION, path]);
  return _retryOnServerError(function() {
    return api.request('POST', endpoint, {
      auth: true,
      origin: api.runtimeconfigOrigin,
      data: {
        name: path + '/' + configId
      }
    });
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 409) {
    // Config has already been created as part of a parallel operation during firebase functions:config:set
      return RSVP.resolve();
    }
    return RSVP.reject(err);
  });
}

function _deleteConfig(projectId, configId) {
  return _retryOnServerError(function() {
    return api.request('DELETE', utils.endpoint([API_VERSION, 'projects', projectId, 'configs', configId]), {
      auth: true,
      origin: api.runtimeconfigOrigin
    });
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) {
      logger.debug('Config already deleted.');
      return RSVP.resolve();
    }
    return RSVP.reject(err);
  });
}

function _listVariables(configPath) {
  return _retryOnServerError(function() {
    return api.request('GET', utils.endpoint([API_VERSION, configPath, 'variables']), {
      auth: true,
      origin: api.runtimeconfigOrigin
    });
  }).then(function(resp) {
    return RSVP.resolve(resp.body.variables);
  });
}

function _getVariable(varPath) {
  return _retryOnServerError(function() {
    return api.request('GET', utils.endpoint([API_VERSION, varPath]), {
      auth: true,
      origin: api.runtimeconfigOrigin
    });
  }).then(function(resp) {
    return RSVP.resolve(resp.body);
  });
}

function _createVariable(projectId, configId, varId, value) {
  var path = _.join(['projects', projectId, 'configs', configId, 'variables'], '/');
  var endpoint = utils.endpoint([API_VERSION, path]);
  return _retryOnServerError(function() {
    return api.request('POST', endpoint, {
      auth: true,
      origin: api.runtimeconfigOrigin,
      data: {
        name: path + '/' + varId,
        text: value
      }
    });
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) { // parent config doesn't exist yet
      return _createConfig(projectId, configId).then(function() {
        return _createVariable(projectId, configId, varId, value);
      });
    }
    return RSVP.reject(err);
  });
}

function _updateVariable(projectId, configId, varId, value) {
  var path = _.join(['projects', projectId, 'configs', configId, 'variables', varId], '/');
  var endpoint = utils.endpoint([API_VERSION, path]);
  return _retryOnServerError(function() {
    return api.request('PUT', endpoint, {
      auth: true,
      origin: api.runtimeconfigOrigin,
      data: {
        name: path,
        text: value
      }
    });
  });
}

function _setVariable(projectId, configId, varId, value) {
  var path = _.join(['projects', projectId, 'configs', configId, 'variables', varId], '/');
  return _getVariable(path).then(function() {
    return _updateVariable(projectId, configId, varId, value);
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) {
      return _createVariable(projectId, configId, varId, value);
    }
    return RSVP.reject(err);
  });
}

function _deleteVariable(projectId, configId, varId) {
  var endpoint = utils.endpoint([API_VERSION, 'projects', projectId, 'configs', configId, 'variables', varId])
    + '?recursive=true';
  return _retryOnServerError(function() {
    return api.request('DELETE', endpoint, {
      auth: true,
      origin: api.runtimeconfigOrigin
    });
  }).catch(function(err) {
    if (_.get(err, 'context.response.statusCode') === 404) {
      logger.debug('Variable already deleted.');
      return RSVP.resolve();
    }
    return RSVP.reject(err);
  });
}

module.exports = {
  configs: {
    list: _listConfigs,
    create: _createConfig,
    delete: _deleteConfig
  },
  variables: {
    list: _listVariables,
    get: _getVariable,
    set: _setVariable,
    delete: _deleteVariable
  }
};
<|endoftext|>


<commit_before>/**
 * Created by Serhii from Ubrainians for Imcode
 * on 28.07.16.
 *
 * Service for correct and convenient working with links.
 * Provides context path from <base> tag.
 * Works with links from links.json file.
 *
 * @author Serhii
 */
var Linker = function () {
    this._contextPath = $("base").attr("href"); // tag 'base' should be on every page and should hold context path
    this._getLinks();
};

Linker.prototype = {
    _links: [],
    _simpleURLs: {},
    _contextPath: "",
    _linksCookiesKey: "links.json",

    /**
     * Gets links from server.
     * @private
     */
    _getLinksFromServer: function () {
        var linksPath = this._contextPath + "/api/links"; // should be the only link in app

        $.ajax({
            url: linksPath,
            type: "GET",
            async: false,
            success: function (response) {
                this._links = response;
            }.bind(this)
        });
    },

    /**
     * Writes links json to cookies
     * @private
     */
    _setLinksToCookies: function () {
        Cookies.set(this._linksCookiesKey, this._links, {
            expires: 1
        });
    },

    /**
     * Divides links with args (that needs builder) and simple URL
     * @private
     */
    _divideLinks: function () {
        this._links.filter(function (link) {
            return !link.args.length;
        }).forEach(function (simpleLink) {
            this._links.remove(simpleLink);
            this._simpleURLs[simpleLink.name] = simpleLink.url;
        }.bind(this))
    },

    /**
     * Make all steps to load links from server
     * @private
     */
    _loadLinks: function () {
        this._getLinksFromServer();
        this._setLinksToCookies();
        this._divideLinks();
    },

    /**
     * Gets links from cookies or from server if cookie is empty and then write cookie with 1 day expiration
     * @private
     */
    _getLinks: function () {
        var linksFromCookies = Cookies.getJSON(this._linksCookiesKey);

        if (linksFromCookies && linksFromCookies.length) {
            this._links = linksFromCookies;
            this._divideLinks();

        } else {
            this._loadLinks();
        }
    },

    /**
     * @returns {string} context path
     */
    getContextPath: function () {
        return this._contextPath;
    },

    /**
     * @returns {Linker._simpleURLs|{}} URLs without arguments
     */
    getSimpleURLs: function () {
        return this._simpleURLs;
    },

    /**
     * @returns {Array} all links with args
     */
    getLinks: function () {
        return this._links;
    },

    /**
     * Use it to get some link with context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [argsURL] - arguments for url in correct order
     * @returns {string} built link
     */
    get: function (name, argsURL) {
        return this._contextPath + this._getURL(arguments);
    },

    /**
     * Use it to get some link without context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [argsURL] - arguments for url in correct order
     * @returns {string} built link
     */
    getRelative: function (name, argsURL) {
        return this._getURL(arguments);
    },

    /**
     * Get simple URL or from link with builder
     * @returns {string} requested URL
     * @private
     */
    _getURL: function (args) {
        if (args.length == 1) {
            return this._getSimpleUrl(args[0]);
        } else {
            var urlArgs = this._prepareArgs(args);

            if (!urlArgs.length) {
                return this._getSimpleUrl(args[0]);

            } else {
                return this._getLinkUrl(args[0], urlArgs);
            }
        }
    },

    /**
     * Returns URL by it's name from links.json file
     * @param {string} name - the name of URL
     * @returns {string} requested URL
     * @private
     */
    _getSimpleUrl: function (name) {
        var link = this._simpleURLs[name];

        if (!link) {
            this._loadLinks();
            link = this._simpleURLs[name];
        }

        if (!link) {
            throw new Error("Can not found link with name '" + name + "'");
        }

        return link;
    },

    /**
     * Returns link's url by it's name and arguments.
     * @param {string} name - the name of url, from links.json file.
     * @param {string[]} [args] - arguments for url in correct order
     * @returns {string} built link's url
     * @private
     */
    _getLinkUrl: function (name, args) {
        var link = this._getLink(name, args);

        if (!link) {
            this._loadLinks();
            link = this._getLink(name, args);
        }

        if (!link) {
            throw new Error("Can not found link with name '" + name + "' and arguments [" + args + "]");
        }

        var result = link.url;

        args.forEach(function (arg, index) {
            result = result.replace("{" + (index + 1) + "}", arg);
        });

        return result;
    },

    /**
     * Takes link object
     * @param {string} name - link's name
     * @param {string[]} [args] - link's arguments
     * @returns {object} result link or undefined if not found
     * @private
     */
    _getLink: function (name, args) {
        return this._links.find(function (link) {
            return (link.name == name && link.args.length == args.length);
        });
    },

    /**
     * Function get all arguments and returns only url's arguments.
     * First is name, and as we don't need it here we check all others args and return prepared array.
     * @param args - all arguments, including name
     * @returns {Array} array of only url arguments
     * @private
     */
    _prepareArgs: function (args) {
        return Array.prototype.slice
            .call(args, 1) // 0 argument is link's name, 1.. is args to url so we start from 1
            .flatMap(function (arg) { // [[1],2] -> [1,2]
                return arg;
            })
            .filter(function (arg) {
                return (typeof arg !== 'undefined' && arg !== null);
            });
    }
};

/**
 * Flatmap function, as in Java Stream API
 * @param {function} lambda - lambda to be used for each element
 * @returns {Array} - resulted array
 */
Array.prototype.flatMap = function (lambda) {
    return Array.prototype.concat.apply([], this.map(lambda));
};

/**
 * Extension for Array to made convenient remove
 * @param {*} value to remove from array
 */
Array.prototype.remove = function (value) {
    while (true) {
        var index = this.indexOf(value);
        if (index < 0) return;
        this.splice(index, 1);
    }
};

var Imcms = {};
Imcms.Linker = new Linker();
<commit_msg>IMCMS-122 - Direct link to menu, textfield and image: Added one more link checking for Linker.<commit_after>/**
 * Created by Serhii from Ubrainians for Imcode
 * on 28.07.16.
 *
 * Service for correct and convenient working with links.
 * Provides context path from <base> tag.
 * Works with links from links.json file.
 *
 * @author Serhii
 */
var Linker = function () {
    this._contextPath = $("base").attr("href"); // tag 'base' should be on every page and should hold context path
    this._getLinks();
};

Linker.prototype = {
    _links: [],
    _simpleURLs: {},
    _contextPath: "",
    _linksCookiesKey: "links.json",

    /**
     * Gets links from server.
     * @private
     */
    _getLinksFromServer: function () {
        var linksPath = this._contextPath + "/api/links"; // should be the only link in app

        $.ajax({
            url: linksPath,
            type: "GET",
            async: false,
            success: function (response) {
                this._links = response;
            }.bind(this)
        });
    },

    /**
     * Writes links json to cookies
     * @private
     */
    _setLinksToCookies: function () {
        Cookies.set(this._linksCookiesKey, this._links, {
            expires: 1
        });
    },

    /**
     * Divides links with args (that needs builder) and simple URL
     * @private
     */
    _divideLinks: function () {
        this._links.filter(function (link) {
            return !link.args.length;
        }).forEach(function (simpleLink) {
            this._links.remove(simpleLink);
            this._simpleURLs[simpleLink.name] = simpleLink.url;
        }.bind(this))
    },

    /**
     * Make all steps to load links from server
     * @private
     */
    _loadLinks: function () {
        this._getLinksFromServer();
        this._setLinksToCookies();
        this._divideLinks();
    },

    /**
     * Gets links from cookies or from server if cookie is empty and then write cookie with 1 day expiration
     * @private
     */
    _getLinks: function () {
        var linksFromCookies = Cookies.getJSON(this._linksCookiesKey);

        if (linksFromCookies && linksFromCookies.length) {
            this._links = linksFromCookies;
            this._divideLinks();

        } else {
            this._loadLinks();
        }
    },

    /**
     * @returns {string} context path
     */
    getContextPath: function () {
        return this._contextPath;
    },

    /**
     * @returns {Linker._simpleURLs|{}} URLs without arguments
     */
    getSimpleURLs: function () {
        return this._simpleURLs;
    },

    /**
     * @returns {Array} all links with args
     */
    getLinks: function () {
        return this._links;
    },

    /**
     * Use it to get some link with context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [argsURL] - arguments for url in correct order
     * @returns {string} built link
     */
    get: function (name, argsURL) {
        return this._contextPath + this._getURL(arguments);
    },

    /**
     * Use it to get some link without context path.
     * @param {string} name - the name of url, from links.json file.
     * @param {...string} [argsURL] - arguments for url in correct order
     * @returns {string} built link
     */
    getRelative: function (name, argsURL) {
        return this._getURL(arguments);
    },

    /**
     * Get simple URL or from link with builder
     * @returns {string} requested URL
     * @private
     */
    _getURL: function (args) {
        if (args.length == 1) {
            return this._getSimpleUrl(args[0]);
        } else {
            var urlArgs = this._prepareArgs(args);

            if (!urlArgs.length) {
                return this._getSimpleUrl(args[0]);

            } else {
                return this._getLinkUrl(args[0], urlArgs);
            }
        }
    },

    /**
     * Returns URL by it's name from links.json file
     * @param {string} name - the name of URL
     * @returns {string} requested URL
     * @private
     */
    _getSimpleUrl: function (name) {
        var link = this._simpleURLs[name];

        if (!link) {
            this._loadLinks();
            link = this._simpleURLs[name];
        }

        if (!link) {
            throw new Error("Can not found link with name '" + name + "'");
        }

        return link;
    },

    /**
     * Returns link's url by it's name and arguments.
     * @param {string} name - the name of url, from links.json file.
     * @param {string[]} [args] - arguments for url in correct order
     * @returns {string} built link's url
     * @private
     */
    _getLinkUrl: function (name, args) {
        var link = this._getLink(name, args);

        if (!link) {
            this._loadLinks();
            link = this._getLink(name, args);
        }

        if (!link || !link.url) {
            throw new Error("Can not found link with name '" + name + "' and arguments [" + args + "]");
        }

        var result = link.url;

        args.forEach(function (arg, index) {
            result = result.replace("{" + (index + 1) + "}", arg);
        });

        return result;
    },

    /**
     * Takes link object
     * @param {string} name - link's name
     * @param {string[]} [args] - link's arguments
     * @returns {object} result link or undefined if not found
     * @private
     */
    _getLink: function (name, args) {
        return this._links.find(function (link) {
            return (link.name == name && link.args.length == args.length);
        });
    },

    /**
     * Function get all arguments and returns only url's arguments.
     * First is name, and as we don't need it here we check all others args and return prepared array.
     * @param args - all arguments, including name
     * @returns {Array} array of only url arguments
     * @private
     */
    _prepareArgs: function (args) {
        return Array.prototype.slice
            .call(args, 1) // 0 argument is link's name, 1.. is args to url so we start from 1
            .flatMap(function (arg) { // [[1],2] -> [1,2]
                return arg;
            })
            .filter(function (arg) {
                return (typeof arg !== 'undefined' && arg !== null);
            });
    }
};

/**
 * Flatmap function, as in Java Stream API
 * @param {function} lambda - lambda to be used for each element
 * @returns {Array} - resulted array
 */
Array.prototype.flatMap = function (lambda) {
    return Array.prototype.concat.apply([], this.map(lambda));
};

/**
 * Extension for Array to made convenient remove
 * @param {*} value to remove from array
 */
Array.prototype.remove = function (value) {
    while (true) {
        var index = this.indexOf(value);
        if (index < 0) return;
        this.splice(index, 1);
    }
};

var Imcms = {};
Imcms.Linker = new Linker();
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import {
  FormattedMessage,
  defineMessages,
  injectIntl,
  intlShape,
} from 'react-intl';
import DatePicker from 'material-ui/DatePicker';
import areIntlLocalesSupported from 'intl-locales-supported';
import IntlPolyfill from 'intl';
import IconDateRange from 'material-ui/svg-icons/action/date-range';
import IconSchedule from 'material-ui/svg-icons/action/schedule';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import CheckContext from '../../CheckContext';
import { convertNumbers2English } from '../../helpers';
import { alertRed, black38, black54, units, caption, FlexRow } from '../../styles/js/variables';
import timezones from '../../timezones';

const styles = {
  error: {
    font: 'caption',
    color: alertRed,
    display: 'block',
  },
  primaryColumn: {
    flex: 10,
  },
  secondaryColumn: {
    color: black54,
    height: units(2.5),
    width: units(2.5),
    flex: '1',
  },
  row: {
    marginBottom: units(2),
  },
  time: {
    width: units(6),
    textAlign: 'center',
  },
  label: {
    font: caption,
    color: black38,
  },
};

const messages = defineMessages({
  ok: {
    id: 'datetimeRespondTask.ok',
    defaultMessage: 'OK',
  },
  cancel: {
    id: 'datetimeRespondTask.cancel',
    defaultMessage: 'Cancel',
  },
  timeError: {
    id: 'datetimeRespondTask.timeError',
    defaultMessage: 'Error: invalid time',
  },
});

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

    let date = '';
    let hour = '';
    let minute = '';
    const note = this.props.note || '';
    let timezone = 'GMT';

    const response = convertNumbers2English(this.props.response);

    if (response) {
      const values = response.match(/^(\d+-\d+-\d+) (\d+):(\d+) ([+-]?\d+) ([^ ]+)/);
      const hasTime = !/notime/.test(response);
      date = new Date(`${values[1]} 00:00`);
      if (hasTime) {
        hour = values[2];
        minute = values[3];
      }
      timezone = values[5];
    }

    this.state = {
      taskAnswerDisabled: false,
      timeError: null,
      timezone,
      date,
      hour,
      minute,
      note,
      original: {
        timezone,
        date,
        hour,
        minute,
        note,
      },
    };
  }

  canSubmit(date) {
    const value = date || this.state.date;
    return !!value;
  }

  handleKeyPress(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (this.canSubmit()) {
        this.handleSubmit();
      }
      e.preventDefault();
    }
  }

  handlePressButton() {
    if (this.canSubmit()) {
      this.handleSubmit();
    }
  }

  handleChange(e, date) {
    this.setState({ taskAnswerDisabled: !this.canSubmit(date), date });
  }

  handleChangeTimezone(e, index, value) {
    this.setState({ timezone: value });
  }

  handleChangeNote(e) {
    this.setState({ note: e.target.value });
  }

  handleChangeTime(part, e) {
    const value = parseInt(convertNumbers2English(e.target.value));

    const validators = {
      hour: [0, 23],
      minute: [0, 59],
    };

    const state = {
      taskAnswerDisabled: !this.canSubmit(),
    };
    state[part] = e.target.value;

    if (
      e.target.value !== '' &&
      (isNaN(value) ||
        value < validators[part][0] ||
        value > validators[part][1])
    ) {
      state.timeError = this.props.intl.formatMessage(messages.timeError);
    } else {
      state.timeError = null;
    }

    this.setState(state);
  }

  handleSubmit() {
    if (!this.state.taskAnswerDisabled && !this.state.timeError) {
      const date = this.state.date;
      const note = this.state.note;
      let month = `${date.getMonth() + 1}`;
      let day = `${date.getDate()}`;
      const year = date.getFullYear();
      if (month.length < 2) {
        month = `0${month}`;
      }
      if (day.length < 2) {
        day = `0${day}`;
      }
      let hour = 0;
      let minute = 0;
      if (this.state.hour !== '') {
        hour = this.state.hour;
      }
      if (this.state.minute !== '') {
        minute = this.state.minute;
      }
      let offset = '';
      const timezone = this.state.timezone;
      if (timezones[timezone]) {
        offset = timezones[timezone].offset;
        if (offset > 0) {
          offset = `+${offset}`;
        }
      }
      let notime = '';
      if (this.state.hour == '' && this.state.minute == '') {
        notime = 'notime';
      }

      const response = `${year}-${month}-${day} ${hour}:${minute} ${offset} ${timezone} ${notime}`;

      this.setState({ taskAnswerDisabled: true });
      this.props.onSubmit(response, note);
    }
  }

  getLocale() {
    return new CheckContext(this).getContextStore().locale || 'en';
  }

  handleCancel() {
    const ori = this.state.original;
    this.setState({ timezone: ori.timezone, date: ori.date, hour: ori.hour, minute: ori.minute, note: ori.note });
    if (this.props.onDismiss) {
      this.props.onDismiss();
    }
  }

  render() {
    const locale = this.getLocale();
    let DateTimeFormat;

    if (areIntlLocalesSupported(['en', 'pt', 'ar', 'fr'])) {
      DateTimeFormat = global.Intl.DateTimeFormat;
    } else {
      DateTimeFormat = IntlPolyfill.DateTimeFormat;
      require('intl/locale-data/jsonp/pt');
      require('intl/locale-data/jsonp/en');
      require('intl/locale-data/jsonp/ar');
      require('intl/locale-data/jsonp/fr');
    }

    return (

        <FlexRow style={styles.row}>
          <IconDateRange
            className="task__icon"
            style={styles.secondaryColumn}
          />
          <DatePicker
            floatingLabelText={
              <FormattedMessage
                id="datetimeRespondTask.pickDate"
                defaultMessage="Pick a date from the calendar"
              />
              }
            id="task__response-date"
            className="task__response-input"
            name="response"
            value={this.state.date}
            onKeyPress={this.handleKeyPress.bind(this)}
            onChange={this.handleChange.bind(this)}
            fullWidth
            locale={locale}
            DateTimeFormat={DateTimeFormat}
            okLabel={this.props.intl.formatMessage(messages.ok)}
            cancelLabel={this.props.intl.formatMessage(messages.cancel)}
            mode="landscape"
            style={styles.primaryColumn}
          />
        </FlexRow>

        <FlexRow style={styles.row}>
          <IconSchedule
            className="task__icon"
            style={styles.secondaryColumn}
          />

              <FormattedMessage
                id="datetimeRespondTask.timeOptional"
                defaultMessage="Time (optional)"
              />

            <FlexRow style={{ justifyContent: 'flex-start', alignItems: 'center' }} id="task__response-time">
              <TextField
                hintText="HH"
                name="hour"
                style={styles.time}
                inputStyle={styles.time}
                hintStyle={styles.time}
                value={this.state.hour}
                onChange={this.handleChangeTime.bind(this, 'hour')}
              />{' '}

:
{' '}
              <TextField
                name="minute"
                hintText="MM"
                style={styles.time}
                inputStyle={styles.time}
                hintStyle={styles.time}
                value={this.state.minute}
                onChange={this.handleChangeTime.bind(this, 'minute')}
              />
              <SelectField
                value={this.state.timezone}
                onChange={this.handleChangeTimezone.bind(this)}
                autoWidth
                className="task__datetime-timezone"
                style={{ marginLeft: units(2) }}
              >
                {Object.values(timezones).map(tz =>
                  <MenuItem key={tz.code} value={tz.code} primaryText={{tz.label}} />,
                )}
              </SelectField>
            </FlexRow>

        </FlexRow>

          {this.state.timeError ? this.state.timeError : ''}

        <TextField
          floatingLabelText={
            <FormattedMessage
              id="datetimeRespondTask.note"
              defaultMessage="Note any additional details here."
            />
          }
          name="note"
          value={this.state.note}
          multiLine
          fullWidth
          onKeyPress={this.handleKeyPress.bind(this)}
          onChange={this.handleChangeNote.bind(this)}
        />

          <FlatButton
            className="task__cancel"
            label={<FormattedMessage id="datetimeRespondTask.cancelTask" defaultMessage="Cancel" />}
            onClick={this.handleCancel.bind(this)}
          />
          <FlatButton
            className="task__save"
            label={
              <FormattedMessage
                id="datetimeRespondTask.resolveTask"
                defaultMessage="Resolve task"
              />
            }
            primary
            onClick={this.handlePressButton.bind(this)}
          />

    );
  }
}

DatetimeRespondTask.propTypes = {
  intl: intlShape.isRequired,
};

DatetimeRespondTask.contextTypes = {
  store: React.PropTypes.object,
};

export default injectIntl(DatetimeRespondTask);
<commit_msg>Ticket #5960: Fixing how Arabic numbers are edited<commit_after>import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import {
  FormattedMessage,
  defineMessages,
  injectIntl,
  intlShape,
} from 'react-intl';
import DatePicker from 'material-ui/DatePicker';
import areIntlLocalesSupported from 'intl-locales-supported';
import IntlPolyfill from 'intl';
import IconDateRange from 'material-ui/svg-icons/action/date-range';
import IconSchedule from 'material-ui/svg-icons/action/schedule';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import CheckContext from '../../CheckContext';
import { convertNumbers2English } from '../../helpers';
import { alertRed, black38, black54, units, caption, FlexRow } from '../../styles/js/variables';
import timezones from '../../timezones';

const styles = {
  error: {
    font: 'caption',
    color: alertRed,
    display: 'block',
  },
  primaryColumn: {
    flex: 10,
  },
  secondaryColumn: {
    color: black54,
    height: units(2.5),
    width: units(2.5),
    flex: '1',
  },
  row: {
    marginBottom: units(2),
  },
  time: {
    width: units(6),
    textAlign: 'center',
  },
  label: {
    font: caption,
    color: black38,
  },
};

const messages = defineMessages({
  ok: {
    id: 'datetimeRespondTask.ok',
    defaultMessage: 'OK',
  },
  cancel: {
    id: 'datetimeRespondTask.cancel',
    defaultMessage: 'Cancel',
  },
  timeError: {
    id: 'datetimeRespondTask.timeError',
    defaultMessage: 'Error: invalid time',
  },
});

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

    let date = '';
    let hour = '';
    let minute = '';
    const note = this.props.note || '';
    let timezone = 'GMT';

    let response = this.props.response;

    if (response) {
      response = convertNumbers2English(response);
      const values = response.match(/^(\d+-\d+-\d+) (\d+):(\d+) ([+-]?\d+) ([^ ]+)/);
      const hasTime = !/notime/.test(response);
      date = new Date(`${values[1]} 00:00`);
      if (hasTime) {
        hour = values[2];
        minute = values[3];
      }
      timezone = values[5];
    }

    this.state = {
      taskAnswerDisabled: false,
      timeError: null,
      timezone,
      date,
      hour,
      minute,
      note,
      original: {
        timezone,
        date,
        hour,
        minute,
        note,
      },
    };
  }

  canSubmit(date) {
    const value = date || this.state.date;
    return !!value;
  }

  handleKeyPress(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (this.canSubmit()) {
        this.handleSubmit();
      }
      e.preventDefault();
    }
  }

  handlePressButton() {
    if (this.canSubmit()) {
      this.handleSubmit();
    }
  }

  handleChange(e, date) {
    this.setState({ taskAnswerDisabled: !this.canSubmit(date), date });
  }

  handleChangeTimezone(e, index, value) {
    this.setState({ timezone: value });
  }

  handleChangeNote(e) {
    this.setState({ note: e.target.value });
  }

  handleChangeTime(part, e) {
    const value = parseInt(convertNumbers2English(e.target.value));

    const validators = {
      hour: [0, 23],
      minute: [0, 59],
    };

    const state = {
      taskAnswerDisabled: !this.canSubmit(),
    };
    state[part] = e.target.value;

    if (
      e.target.value !== '' &&
      (isNaN(value) ||
        value < validators[part][0] ||
        value > validators[part][1])
    ) {
      state.timeError = this.props.intl.formatMessage(messages.timeError);
    } else {
      state.timeError = null;
    }

    this.setState(state);
  }

  handleSubmit() {
    if (!this.state.taskAnswerDisabled && !this.state.timeError) {
      const date = this.state.date;
      const note = this.state.note;
      let month = `${date.getMonth() + 1}`;
      let day = `${date.getDate()}`;
      const year = date.getFullYear();
      if (month.length < 2) {
        month = `0${month}`;
      }
      if (day.length < 2) {
        day = `0${day}`;
      }
      let hour = 0;
      let minute = 0;
      if (this.state.hour !== '') {
        hour = this.state.hour;
      }
      if (this.state.minute !== '') {
        minute = this.state.minute;
      }
      let offset = '';
      const timezone = this.state.timezone;
      if (timezones[timezone]) {
        offset = timezones[timezone].offset;
        if (offset > 0) {
          offset = `+${offset}`;
        }
      }
      let notime = '';
      if (this.state.hour == '' && this.state.minute == '') {
        notime = 'notime';
      }

      const response = `${year}-${month}-${day} ${hour}:${minute} ${offset} ${timezone} ${notime}`;

      this.setState({ taskAnswerDisabled: true });
      this.props.onSubmit(response, note);
    }
  }

  getLocale() {
    return new CheckContext(this).getContextStore().locale || 'en';
  }

  handleCancel() {
    const ori = this.state.original;
    this.setState({ timezone: ori.timezone, date: ori.date, hour: ori.hour, minute: ori.minute, note: ori.note });
    if (this.props.onDismiss) {
      this.props.onDismiss();
    }
  }

  render() {
    const locale = this.getLocale();
    let DateTimeFormat;

    if (areIntlLocalesSupported(['en', 'pt', 'ar', 'fr'])) {
      DateTimeFormat = global.Intl.DateTimeFormat;
    } else {
      DateTimeFormat = IntlPolyfill.DateTimeFormat;
      require('intl/locale-data/jsonp/pt');
      require('intl/locale-data/jsonp/en');
      require('intl/locale-data/jsonp/ar');
      require('intl/locale-data/jsonp/fr');
    }

    return (

        <FlexRow style={styles.row}>
          <IconDateRange
            className="task__icon"
            style={styles.secondaryColumn}
          />
          <DatePicker
            floatingLabelText={
              <FormattedMessage
                id="datetimeRespondTask.pickDate"
                defaultMessage="Pick a date from the calendar"
              />
              }
            id="task__response-date"
            className="task__response-input"
            name="response"
            value={this.state.date}
            onKeyPress={this.handleKeyPress.bind(this)}
            onChange={this.handleChange.bind(this)}
            fullWidth
            locale={locale}
            DateTimeFormat={DateTimeFormat}
            okLabel={this.props.intl.formatMessage(messages.ok)}
            cancelLabel={this.props.intl.formatMessage(messages.cancel)}
            mode="landscape"
            style={styles.primaryColumn}
          />
        </FlexRow>

        <FlexRow style={styles.row}>
          <IconSchedule
            className="task__icon"
            style={styles.secondaryColumn}
          />

              <FormattedMessage
                id="datetimeRespondTask.timeOptional"
                defaultMessage="Time (optional)"
              />

            <FlexRow style={{ justifyContent: 'flex-start', alignItems: 'center' }} id="task__response-time">
              <TextField
                hintText="HH"
                name="hour"
                style={styles.time}
                inputStyle={styles.time}
                hintStyle={styles.time}
                value={this.state.hour}
                onChange={this.handleChangeTime.bind(this, 'hour')}
              />{' '}

:
{' '}
              <TextField
                name="minute"
                hintText="MM"
                style={styles.time}
                inputStyle={styles.time}
                hintStyle={styles.time}
                value={this.state.minute}
                onChange={this.handleChangeTime.bind(this, 'minute')}
              />
              <SelectField
                value={this.state.timezone}
                onChange={this.handleChangeTimezone.bind(this)}
                autoWidth
                className="task__datetime-timezone"
                style={{ marginLeft: units(2) }}
              >
                {Object.values(timezones).map(tz =>
                  <MenuItem key={tz.code} value={tz.code} primaryText={{tz.label}} />,
                )}
              </SelectField>
            </FlexRow>

        </FlexRow>

          {this.state.timeError ? this.state.timeError : ''}

        <TextField
          floatingLabelText={
            <FormattedMessage
              id="datetimeRespondTask.note"
              defaultMessage="Note any additional details here."
            />
          }
          name="note"
          value={this.state.note}
          multiLine
          fullWidth
          onKeyPress={this.handleKeyPress.bind(this)}
          onChange={this.handleChangeNote.bind(this)}
        />

          <FlatButton
            className="task__cancel"
            label={<FormattedMessage id="datetimeRespondTask.cancelTask" defaultMessage="Cancel" />}
            onClick={this.handleCancel.bind(this)}
          />
          <FlatButton
            className="task__save"
            label={
              <FormattedMessage
                id="datetimeRespondTask.resolveTask"
                defaultMessage="Resolve task"
              />
            }
            primary
            onClick={this.handlePressButton.bind(this)}
          />

    );
  }
}

DatetimeRespondTask.propTypes = {
  intl: intlShape.isRequired,
};

DatetimeRespondTask.contextTypes = {
  store: React.PropTypes.object,
};

export default injectIntl(DatetimeRespondTask);
<|endoftext|>