<commit_before>import React from 'react';
import ReactDOM from 'react-dom';

const CANCEL_DISTANCE_ON_SCROLL = 20;

const defaultStyles = {
  root: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflow: 'hidden',
  },
  sidebar: {
    zIndex: 2,
    position: 'absolute',
    top: 0,
    bottom: 0,
    transition: 'transform .3s ease-out',
    WebkitTransition: '-webkit-transform .3s ease-out',
    willChange: 'transform',
    overflowY: 'auto',
  },
  content: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflow: 'auto',
    transition: 'left .3s ease-out, right .3s ease-out',
  },
  overlay: {
    zIndex: 1,
    position: 'fixed',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    opacity: 0,
    visibility: 'hidden',
    transition: 'opacity .3s ease-out',
    backgroundColor: 'rgba(0,0,0,.3)',
  },
  dragHandle: {
    zIndex: 1,
    position: 'fixed',
    top: 0,
    bottom: 0,
  },
};

class Sidebar extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      // the detected width of the sidebar in pixels
      sidebarWidth: 0,

      // keep track of touching params
      touchIdentifier: null,
      touchStartX: null,
      touchStartY: null,
      touchCurrentX: null,
      touchCurrentY: null,

      // if touch is supported by the browser
      dragSupported: false,
    };

    this.overlayClicked = this.overlayClicked.bind(this);
    this.onTouchStart = this.onTouchStart.bind(this);
    this.onTouchMove = this.onTouchMove.bind(this);
    this.onTouchEnd = this.onTouchEnd.bind(this);
    this.onScroll = this.onScroll.bind(this);
  }

  componentDidMount() {
    this.setState({
      dragSupported: typeof window === 'object' && 'ontouchstart' in window,
    });
    this.saveSidebarWidth();
  }

  componentDidUpdate() {
    // filter out the updates when we're touching
    if (!this.isTouching()) {
      this.saveSidebarWidth();
    }
  }

  onTouchStart(ev) {
    // filter out if a user starts swiping with a second finger
    if (!this.isTouching()) {
      const touch = ev.targetTouches[0];
      this.setState({
        touchIdentifier: touch.identifier,
        touchStartX: touch.clientX,
        touchStartY: touch.clientY,
        touchCurrentX: touch.clientX,
        touchCurrentY: touch.clientY,
      });
    }
  }

  onTouchMove(ev) {
    if (this.isTouching()) {
      for (let ind = 0; ind < ev.targetTouches.length; ind++) {
        // we only care about the finger that we are tracking
        if (ev.targetTouches[ind].identifier === this.state.touchIdentifier) {
          this.setState({
            touchCurrentX: ev.targetTouches[ind].clientX,
            touchCurrentY: ev.targetTouches[ind].clientY,
          });
          break;
        }
      }
    }
  }

  onTouchEnd() {
    if (this.isTouching()) {
      // trigger a change to open if sidebar has been dragged beyond dragToggleDistance
      const touchWidth = this.touchSidebarWidth();

      if (this.props.open && touchWidth < this.state.sidebarWidth - this.props.dragToggleDistance ||
          !this.props.open && touchWidth > this.props.dragToggleDistance) {
        this.props.onSetOpen(!this.props.open);
      }

      this.setState({
        touchIdentifier: null,
        touchStartX: null,
        touchStartY: null,
        touchCurrentX: null,
        touchCurrentY: null,
      });
    }
  }

  // This logic helps us prevents the user from sliding the sidebar horizontally
  // while scrolling the sidebar vertically. When a scroll event comes in, we're
  // cancelling the ongoing gesture if it did not move horizontally much.
  onScroll() {
    if (this.isTouching() && this.inCancelDistanceOnScroll()) {
      this.setState({
        touchIdentifier: null,
        touchStartX: null,
        touchStartY: null,
        touchCurrentX: null,
        touchCurrentY: null,
      });
    }
  }

  // True if the on going gesture X distance is less than the cancel distance
  inCancelDistanceOnScroll() {
    let cancelDistanceOnScroll;

    if (this.props.pullRight) {
      cancelDistanceOnScroll = Math.abs(this.state.touchCurrentX - this.state.touchStartX) <
                                        CANCEL_DISTANCE_ON_SCROLL;
    } else {
      cancelDistanceOnScroll = Math.abs(this.state.touchStartX - this.state.touchCurrentX) <
                                        CANCEL_DISTANCE_ON_SCROLL;
    }
    return cancelDistanceOnScroll;
  }

  isTouching() {
    return this.state.touchIdentifier !== null;
  }

  overlayClicked() {
    if (this.props.open) {
      this.props.onSetOpen(false);
    }
  }

  saveSidebarWidth() {
    const width = ReactDOM.findDOMNode(this.refs.sidebar).offsetWidth;

    if (width !== this.state.sidebarWidth) {
      this.setState({sidebarWidth: width});
    }
  }

  // calculate the sidebarWidth based on current touch info
  touchSidebarWidth() {
    // if the sidebar is open and start point of drag is inside the sidebar
    // we will only drag the distance they moved their finger
    // otherwise we will move the sidebar to be below the finger.
    if (this.props.pullRight) {
      if (this.props.open && window.innerWidth - this.state.touchStartX < this.state.sidebarWidth) {
        if (this.state.touchCurrentX > this.state.touchStartX) {
          return this.state.sidebarWidth + this.state.touchStartX - this.state.touchCurrentX;
        }
        return this.state.sidebarWidth;
      }
      return Math.min(window.innerWidth - this.state.touchCurrentX, this.state.sidebarWidth);
    }

    if (this.props.open && this.state.touchStartX < this.state.sidebarWidth) {
      if (this.state.touchCurrentX > this.state.touchStartX) {
        return this.state.sidebarWidth;
      }
      return this.state.sidebarWidth - this.state.touchStartX + this.state.touchCurrentX;
    }
    return Math.min(this.state.touchCurrentX, this.state.sidebarWidth);
  }

  render() {
    const sidebarStyle = {...defaultStyles.sidebar, ...this.props.styles.sidebar};
    const contentStyle = {...defaultStyles.content, ...this.props.styles.content};
    const overlayStyle = {...defaultStyles.overlay, ...this.props.styles.overlay};
    const useTouch = this.state.dragSupported && this.props.touch;
    const isTouching = this.isTouching();
    const rootProps = {
      className: this.props.rootClassName,
      style: {...defaultStyles.root, ...this.props.styles.root},
    };
    let dragHandle;

    // sidebarStyle right/left
    if (this.props.pullRight) {
      sidebarStyle.right = 0;
      sidebarStyle.transform = 'translateX(100%)';
      sidebarStyle.WebkitTransform = 'translateX(100%)';
      if (this.props.shadow) {
        sidebarStyle.boxShadow = '-2px 2px 4px rgba(0, 0, 0, 0.15)';
      }
    } else {
      sidebarStyle.left = 0;
      sidebarStyle.transform = 'translateX(-100%)';
      sidebarStyle.WebkitTransform = 'translateX(-100%)';
      if (this.props.shadow) {
        sidebarStyle.boxShadow = '2px 2px 4px rgba(0, 0, 0, 0.15)';
      }
    }

    if (isTouching) {
      const percentage = this.touchSidebarWidth() / this.state.sidebarWidth;

      // slide open to what we dragged
      if (this.props.pullRight) {
        sidebarStyle.transform = `translateX(${(1 - percentage) * 100}%)`;
        sidebarStyle.WebkitTransform = `translateX(${(1 - percentage) * 100}%)`;
      } else {
        sidebarStyle.transform = `translateX(-${(1 - percentage) * 100}%)`;
        sidebarStyle.WebkitTransform = `translateX(-${(1 - percentage) * 100}%)`;
      }

      // fade overlay to match distance of drag
      overlayStyle.opacity = percentage;
      overlayStyle.visibility = 'visible';
    } else if (this.props.docked) {
      // show sidebar
      if (this.state.sidebarWidth !== 0) {
        sidebarStyle.transform = `translateX(0%)`;
        sidebarStyle.WebkitTransform = `translateX(0%)`;
      }

      // make space on the left/right side of the content for the sidebar
      if (this.props.pullRight) {
        contentStyle.right = `${this.state.sidebarWidth}px`;
      } else {
        contentStyle.left = `${this.state.sidebarWidth}px`;
      }
    } else if (this.props.open) {
      // slide open sidebar
      sidebarStyle.transform = `translateX(0%)`;
      sidebarStyle.WebkitTransform = `translateX(0%)`;

      // show overlay
      overlayStyle.opacity = 1;
      overlayStyle.visibility = 'visible';
    }

    if (isTouching || !this.props.transitions) {
      sidebarStyle.transition = 'none';
      sidebarStyle.WebkitTransition = 'none';
      contentStyle.transition = 'none';
      overlayStyle.transition = 'none';
    }

    if (useTouch) {
      if (this.props.open) {
        rootProps.onTouchStart = this.onTouchStart;
        rootProps.onTouchMove = this.onTouchMove;
        rootProps.onTouchEnd = this.onTouchEnd;
        rootProps.onTouchCancel = this.onTouchEnd;
        rootProps.onScroll = this.onScroll;
      } else {
        const dragHandleStyle = {...defaultStyles.dragHandle, ...this.props.styles.dragHandle};
        dragHandleStyle.width = this.props.touchHandleWidth;

        // dragHandleStyle right/left
        if (this.props.pullRight) {
          dragHandleStyle.right = 0;
        } else {
          dragHandleStyle.left = 0;
        }

        dragHandle = (

);
      }
    }

    return (

          {this.props.sidebar}

          {dragHandle}
          {this.props.children}

    );
  }
}

Sidebar.propTypes = {
  // main content to render
  children: React.PropTypes.node.isRequired,

  // styles
  styles: React.PropTypes.shape({
    root: React.PropTypes.object,
    sidebar: React.PropTypes.object,
    content: React.PropTypes.object,
    overlay: React.PropTypes.object,
    dragHandle: React.PropTypes.object,
  }),

  // root component optional class
  rootClassName: React.PropTypes.string,

  // sidebar optional class
  sidebarClassName: React.PropTypes.string,

  // content optional class
  contentClassName: React.PropTypes.string,

  // overlay optional class
  overlayClassName: React.PropTypes.string,

  // sidebar content to render
  sidebar: React.PropTypes.node.isRequired,

  // boolean if sidebar should be docked
  docked: React.PropTypes.bool,

  // boolean if sidebar should slide open
  open: React.PropTypes.bool,

  // boolean if transitions should be disabled
  transitions: React.PropTypes.bool,

  // boolean if touch gestures are enabled
  touch: React.PropTypes.bool,

  // max distance from the edge we can start touching
  touchHandleWidth: React.PropTypes.number,

  // Place the sidebar on the right
  pullRight: React.PropTypes.bool,

  // Enable/Disable sidebar shadow
  shadow: React.PropTypes.bool,

  // distance we have to drag the sidebar to toggle open state
  dragToggleDistance: React.PropTypes.number,

  // callback called when the overlay is clicked
  onSetOpen: React.PropTypes.func,
};

Sidebar.defaultProps = {
  docked: false,
  open: false,
  transitions: true,
  touch: true,
  touchHandleWidth: 20,
  pullRight: false,
  shadow: true,
  dragToggleDistance: 30,
  onSetOpen: () => {},
  styles: {},
};

export default Sidebar;
<commit_msg>Allow overlay to animate when closing (#85)<commit_after>import React from 'react';
import ReactDOM from 'react-dom';

const CANCEL_DISTANCE_ON_SCROLL = 20;

const defaultStyles = {
  root: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflow: 'hidden',
  },
  sidebar: {
    zIndex: 2,
    position: 'absolute',
    top: 0,
    bottom: 0,
    transition: 'transform .3s ease-out',
    WebkitTransition: '-webkit-transform .3s ease-out',
    willChange: 'transform',
    overflowY: 'auto',
  },
  content: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflow: 'auto',
    transition: 'left .3s ease-out, right .3s ease-out',
  },
  overlay: {
    zIndex: 1,
    position: 'fixed',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    opacity: 0,
    visibility: 'hidden',
    transition: 'opacity .3s ease-out, visibility .3s ease-out',
    backgroundColor: 'rgba(0,0,0,.3)',
  },
  dragHandle: {
    zIndex: 1,
    position: 'fixed',
    top: 0,
    bottom: 0,
  },
};

class Sidebar extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      // the detected width of the sidebar in pixels
      sidebarWidth: 0,

      // keep track of touching params
      touchIdentifier: null,
      touchStartX: null,
      touchStartY: null,
      touchCurrentX: null,
      touchCurrentY: null,

      // if touch is supported by the browser
      dragSupported: false,
    };

    this.overlayClicked = this.overlayClicked.bind(this);
    this.onTouchStart = this.onTouchStart.bind(this);
    this.onTouchMove = this.onTouchMove.bind(this);
    this.onTouchEnd = this.onTouchEnd.bind(this);
    this.onScroll = this.onScroll.bind(this);
  }

  componentDidMount() {
    this.setState({
      dragSupported: typeof window === 'object' && 'ontouchstart' in window,
    });
    this.saveSidebarWidth();
  }

  componentDidUpdate() {
    // filter out the updates when we're touching
    if (!this.isTouching()) {
      this.saveSidebarWidth();
    }
  }

  onTouchStart(ev) {
    // filter out if a user starts swiping with a second finger
    if (!this.isTouching()) {
      const touch = ev.targetTouches[0];
      this.setState({
        touchIdentifier: touch.identifier,
        touchStartX: touch.clientX,
        touchStartY: touch.clientY,
        touchCurrentX: touch.clientX,
        touchCurrentY: touch.clientY,
      });
    }
  }

  onTouchMove(ev) {
    if (this.isTouching()) {
      for (let ind = 0; ind < ev.targetTouches.length; ind++) {
        // we only care about the finger that we are tracking
        if (ev.targetTouches[ind].identifier === this.state.touchIdentifier) {
          this.setState({
            touchCurrentX: ev.targetTouches[ind].clientX,
            touchCurrentY: ev.targetTouches[ind].clientY,
          });
          break;
        }
      }
    }
  }

  onTouchEnd() {
    if (this.isTouching()) {
      // trigger a change to open if sidebar has been dragged beyond dragToggleDistance
      const touchWidth = this.touchSidebarWidth();

      if (this.props.open && touchWidth < this.state.sidebarWidth - this.props.dragToggleDistance ||
          !this.props.open && touchWidth > this.props.dragToggleDistance) {
        this.props.onSetOpen(!this.props.open);
      }

      this.setState({
        touchIdentifier: null,
        touchStartX: null,
        touchStartY: null,
        touchCurrentX: null,
        touchCurrentY: null,
      });
    }
  }

  // This logic helps us prevents the user from sliding the sidebar horizontally
  // while scrolling the sidebar vertically. When a scroll event comes in, we're
  // cancelling the ongoing gesture if it did not move horizontally much.
  onScroll() {
    if (this.isTouching() && this.inCancelDistanceOnScroll()) {
      this.setState({
        touchIdentifier: null,
        touchStartX: null,
        touchStartY: null,
        touchCurrentX: null,
        touchCurrentY: null,
      });
    }
  }

  // True if the on going gesture X distance is less than the cancel distance
  inCancelDistanceOnScroll() {
    let cancelDistanceOnScroll;

    if (this.props.pullRight) {
      cancelDistanceOnScroll = Math.abs(this.state.touchCurrentX - this.state.touchStartX) <
                                        CANCEL_DISTANCE_ON_SCROLL;
    } else {
      cancelDistanceOnScroll = Math.abs(this.state.touchStartX - this.state.touchCurrentX) <
                                        CANCEL_DISTANCE_ON_SCROLL;
    }
    return cancelDistanceOnScroll;
  }

  isTouching() {
    return this.state.touchIdentifier !== null;
  }

  overlayClicked() {
    if (this.props.open) {
      this.props.onSetOpen(false);
    }
  }

  saveSidebarWidth() {
    const width = ReactDOM.findDOMNode(this.refs.sidebar).offsetWidth;

    if (width !== this.state.sidebarWidth) {
      this.setState({sidebarWidth: width});
    }
  }

  // calculate the sidebarWidth based on current touch info
  touchSidebarWidth() {
    // if the sidebar is open and start point of drag is inside the sidebar
    // we will only drag the distance they moved their finger
    // otherwise we will move the sidebar to be below the finger.
    if (this.props.pullRight) {
      if (this.props.open && window.innerWidth - this.state.touchStartX < this.state.sidebarWidth) {
        if (this.state.touchCurrentX > this.state.touchStartX) {
          return this.state.sidebarWidth + this.state.touchStartX - this.state.touchCurrentX;
        }
        return this.state.sidebarWidth;
      }
      return Math.min(window.innerWidth - this.state.touchCurrentX, this.state.sidebarWidth);
    }

    if (this.props.open && this.state.touchStartX < this.state.sidebarWidth) {
      if (this.state.touchCurrentX > this.state.touchStartX) {
        return this.state.sidebarWidth;
      }
      return this.state.sidebarWidth - this.state.touchStartX + this.state.touchCurrentX;
    }
    return Math.min(this.state.touchCurrentX, this.state.sidebarWidth);
  }

  render() {
    const sidebarStyle = {...defaultStyles.sidebar, ...this.props.styles.sidebar};
    const contentStyle = {...defaultStyles.content, ...this.props.styles.content};
    const overlayStyle = {...defaultStyles.overlay, ...this.props.styles.overlay};
    const useTouch = this.state.dragSupported && this.props.touch;
    const isTouching = this.isTouching();
    const rootProps = {
      className: this.props.rootClassName,
      style: {...defaultStyles.root, ...this.props.styles.root},
    };
    let dragHandle;

    // sidebarStyle right/left
    if (this.props.pullRight) {
      sidebarStyle.right = 0;
      sidebarStyle.transform = 'translateX(100%)';
      sidebarStyle.WebkitTransform = 'translateX(100%)';
      if (this.props.shadow) {
        sidebarStyle.boxShadow = '-2px 2px 4px rgba(0, 0, 0, 0.15)';
      }
    } else {
      sidebarStyle.left = 0;
      sidebarStyle.transform = 'translateX(-100%)';
      sidebarStyle.WebkitTransform = 'translateX(-100%)';
      if (this.props.shadow) {
        sidebarStyle.boxShadow = '2px 2px 4px rgba(0, 0, 0, 0.15)';
      }
    }

    if (isTouching) {
      const percentage = this.touchSidebarWidth() / this.state.sidebarWidth;

      // slide open to what we dragged
      if (this.props.pullRight) {
        sidebarStyle.transform = `translateX(${(1 - percentage) * 100}%)`;
        sidebarStyle.WebkitTransform = `translateX(${(1 - percentage) * 100}%)`;
      } else {
        sidebarStyle.transform = `translateX(-${(1 - percentage) * 100}%)`;
        sidebarStyle.WebkitTransform = `translateX(-${(1 - percentage) * 100}%)`;
      }

      // fade overlay to match distance of drag
      overlayStyle.opacity = percentage;
      overlayStyle.visibility = 'visible';
    } else if (this.props.docked) {
      // show sidebar
      if (this.state.sidebarWidth !== 0) {
        sidebarStyle.transform = `translateX(0%)`;
        sidebarStyle.WebkitTransform = `translateX(0%)`;
      }

      // make space on the left/right side of the content for the sidebar
      if (this.props.pullRight) {
        contentStyle.right = `${this.state.sidebarWidth}px`;
      } else {
        contentStyle.left = `${this.state.sidebarWidth}px`;
      }
    } else if (this.props.open) {
      // slide open sidebar
      sidebarStyle.transform = `translateX(0%)`;
      sidebarStyle.WebkitTransform = `translateX(0%)`;

      // show overlay
      overlayStyle.opacity = 1;
      overlayStyle.visibility = 'visible';
    }

    if (isTouching || !this.props.transitions) {
      sidebarStyle.transition = 'none';
      sidebarStyle.WebkitTransition = 'none';
      contentStyle.transition = 'none';
      overlayStyle.transition = 'none';
    }

    if (useTouch) {
      if (this.props.open) {
        rootProps.onTouchStart = this.onTouchStart;
        rootProps.onTouchMove = this.onTouchMove;
        rootProps.onTouchEnd = this.onTouchEnd;
        rootProps.onTouchCancel = this.onTouchEnd;
        rootProps.onScroll = this.onScroll;
      } else {
        const dragHandleStyle = {...defaultStyles.dragHandle, ...this.props.styles.dragHandle};
        dragHandleStyle.width = this.props.touchHandleWidth;

        // dragHandleStyle right/left
        if (this.props.pullRight) {
          dragHandleStyle.right = 0;
        } else {
          dragHandleStyle.left = 0;
        }

        dragHandle = (

);
      }
    }

    return (

          {this.props.sidebar}

          {dragHandle}
          {this.props.children}

    );
  }
}

Sidebar.propTypes = {
  // main content to render
  children: React.PropTypes.node.isRequired,

  // styles
  styles: React.PropTypes.shape({
    root: React.PropTypes.object,
    sidebar: React.PropTypes.object,
    content: React.PropTypes.object,
    overlay: React.PropTypes.object,
    dragHandle: React.PropTypes.object,
  }),

  // root component optional class
  rootClassName: React.PropTypes.string,

  // sidebar optional class
  sidebarClassName: React.PropTypes.string,

  // content optional class
  contentClassName: React.PropTypes.string,

  // overlay optional class
  overlayClassName: React.PropTypes.string,

  // sidebar content to render
  sidebar: React.PropTypes.node.isRequired,

  // boolean if sidebar should be docked
  docked: React.PropTypes.bool,

  // boolean if sidebar should slide open
  open: React.PropTypes.bool,

  // boolean if transitions should be disabled
  transitions: React.PropTypes.bool,

  // boolean if touch gestures are enabled
  touch: React.PropTypes.bool,

  // max distance from the edge we can start touching
  touchHandleWidth: React.PropTypes.number,

  // Place the sidebar on the right
  pullRight: React.PropTypes.bool,

  // Enable/Disable sidebar shadow
  shadow: React.PropTypes.bool,

  // distance we have to drag the sidebar to toggle open state
  dragToggleDistance: React.PropTypes.number,

  // callback called when the overlay is clicked
  onSetOpen: React.PropTypes.func,
};

Sidebar.defaultProps = {
  docked: false,
  open: false,
  transitions: true,
  touch: true,
  touchHandleWidth: 20,
  pullRight: false,
  shadow: true,
  dragToggleDistance: 30,
  onSetOpen: () => {},
  styles: {},
};

export default Sidebar;
<|endoftext|>


<commit_before>/*
 * Tafi
 * https://github.com/jeef3/tafi
 *
 * Copyright (c) 2013 Jeff Knaggs
 * Licensed under the MIT license.
 */

(function($) {

  function Tafi($element, settings) {
    var i,
      length,
      choicesValues,
      choiceValue;

    this.initElements($element);
    this.initEvents();

    this.options = _rehydrateOptions(settings.options);
    this.path = settings.path;
    this.partials = settings.partials || {};
    this.decisions = [];

    this.currentJunction = _buildJunction.call(this);

    choicesValues = settings.choicesValues;
    for (i = 0, length = choicesValues.length; i < length; i++) {
      choiceValue = choicesValues[i];

      this.makeDecision(choiceValue);
    }
  }

  Tafi.prototype.initElements = function ($element) {

    // Container
    this.$container = $("<div />", {
      id: $element.attr("id"),
      "class": $element.attr("class")
    });
    this.$container.addClass("tafi");

    // Visible Input
    this.$nextDecision = $("<div />", {
      "class": "tafi-next-decision"
    });

    this.$input = $("<input />", {
      id: $element.attr("id"),
      type: "text",
      placeholder: $element.attr("placeholder"),
      required: $element.prop("required"),
      "class": "tafi-input"
    });
    this.$nextDecision.append(this.$input);

    // Hidden Input
    this.$hidden = $("<input />", {
      type: "hidden",
      name: $element.attr("name")
    });

    this.$container
      .append(this.$nextDecision)
      .append(this.$hidden);

    $element.replaceWith(this.$container);
  };

  Tafi.prototype.initEvents = function () {

    $(document)
      .on("click", $.proxy(_documentClick, this));

    this.$container
      .on("click", ".tafi-decision", $.proxy(_decisionClick, this))
      .on("makedecision", $.proxy(_decisionMade, this))
      .on("deletedecision", $.proxy(_decisionDeleted, this));

    this.$input
      .on("focus", $.proxy(_inputFocus, this))
      .on("blur", $.proxy(_inputBlur, this))
      .on("keyup", $.proxy(_inputKeyup, this));
  };

  Tafi.prototype.redraw = function () {
    this._updateInput();
    this._redrawDecisions();
  };

  Tafi.prototype._redrawDecisions = function () {
    var i,
      length;

    this.$container.find(".tafi-decision").remove();

    for (length = this.decisions.length - 1, i = length; i > -1; i--) {
      this.$container.prepend(_buildDecision(this.decisions[i]));
    }
  };

  Tafi.prototype._updateInput = function () {
    var option = this.options[this.currentJunction.option];

    this.$input.attr("placeholder", option.title);

    this._updateCurrentChoices();
  };

  Tafi.prototype._updateCurrentChoices = function () {
    var option = this.options[this.currentJunction.option],
      $currentChoiceList = _buildOptionChoicesList.call(this, option);

    this.$nextDecision
      .find(".tafi-option-choices")
        .remove()
        .end()
      .append($currentChoiceList);
  };

  Tafi.prototype.showDecisionChoices = function ($decision) {
    this._hideChoices();

    $decision.addClass("tafi-decision-show-options");
  };

  Tafi.prototype._hideChoices = function () {
    this.$container
      .find(".tafi-decision, .tafi-next-decision")
      .removeClass("tafi-decision-show-options");
  };

  Tafi.prototype.makeDecision = function (choiceValue) {
    var option = this.options[this.currentJunction.option],
      nextJunction = _getNextJunction.call(this, this.currentJunction.branches, choiceValue),
      nextOption = this.options[nextJunction.option],
      decision = { option: option, choice: option.findChoice(choiceValue) },
      onlyOption;

    if (!nextJunction) throw new Error("Invalid decision, no branch exists for " + choiceValue);

    this.decisions.push(decision);
    this.currentJunction = nextJunction;

    this.$container.trigger("makedecision", decision);

    // If, from here, there is only one way to go, take it.
    if (!nextOption.choices ||
        $.map(nextOption.choices, function (n, i) { return i; }).length === 1) {

      onlyOption = nextJunction.branches["*"];
      this.makeDecision(onlyOption);
    }
  };

  Tafi.prototype.deleteDecision = function (decision) {
    var index = this.decisions.indexOf(decision),
      removeTo = index === -1 ? - 1 : (this.decisions.length),
      removed = this.decisions.slice(index),
      junction,
      i,
      length,
      decision;

    this.decisions = this.decisions.slice(0, removeTo);

    junction = _buildJunction.call(this);

    for (i = 0, length = this.decisions.length; i < length; i++) {
      decision = this.decisions[i];

      junction = _getNextJunction.call(this, junction.branches, decision.choice.value || decision.choice);
    }

    this.currentJunction = junction;

    // TODO: How to skip implicit decisions (e.g.: separator) and just delete the thing before it?

    this.$container.trigger("deletedecision", removed);
  };

  Tafi.prototype.reset = function () {
    // Clear all and return to start
  };

  //
  // Private

  var _rehydrateOptions = function (optionsData) {
    var options = {};

    $.each(optionsData, function (name, option) {
      options[name] = new Option(name, option);
    });

    return options;
  };

  var _buildJunction = function (junctionRoot) {
    var junction;

    if (!junctionRoot) {
      junctionRoot = this.path;
    }

    if (typeof junctionRoot === "string") {
      junction = this.partials[junctionRoot];
    } else {
      junction = junctionRoot;
    }

    return junction;
  };

  var _getNextJunction = function (branches, choice) {
    var junctionRoot;

    $.each(branches, function (choiceValue, junction) {
      if (choiceValue === choice) {
        junctionRoot = junction;
        return false;
      }
    });

    if (!junctionRoot && branches["*"]) {
      junctionRoot = branches["*"];
    }

    if (!junctionRoot) {
      throw new Error("No path found for " + choice);
    }

    return _buildJunction.call(this, junctionRoot);
  };

  var _buildDecision = function (decision) {
    var option = decision.option,
      choice = decision.choice,
      $inner = $("<span />"),
      $decision = $("<div />");

    $decision
      .addClass("tafi-decision")
      .addClass(option.isEditable() ? "tafi-editable-decision" : "tafi-noneditable-decision")
      .data("tafi-option", option.name)
      .data("tafi-choice", choice.value || choice)

    if (option.title && choice.label) {
      $decision.attr("title", "" + option.title + ": " + choice.label);
    }

    $inner
      .addClass("tafi-decision-text")
      .text(choice.text || choice);

    $decision
      .append($inner)
      .append(_buildOptionChoicesList(option));

    return $decision;
  };

  var _buildOptionChoicesList = function (option) {
    var $optionChoices;

    if (!option.choices) return $();

    $optionChoices = $("<ul />", { "class": "tafi-option-choices" });

    // TODO: Take an optional class value from dev? e.g.: dropdown-menu
    // $optionChoices.addClass(settings.optionChoicesClass);

    $.each(option.choices, function (index, choice) {
      var $li = $("<li />");

      $li
        .data("tafi-value", choice.value)
        .append($("<a />", { href: "#", role: "button" }).text(choice.label));

      $optionChoices.append($li);
    });

    return $optionChoices;
  };

  //
  // Events

  var _documentClick = function (e) {
//    this._hideChoices();
  };

  var _decisionClick = function (e) {
    // Set the active decision as the clicked one

    // display the option choices
    this.showDecisionChoices($(e.currentTarget));
  };

  var _decisionMade = function () {
    this.redraw()
  };

  var _decisionDeleted = function () {
    this.redraw();
  };

  var _inputFocus = function (e) {
    var $choiceList = $(e.currentTarget).parent();
    this.showDecisionChoices($choiceList);
  };

  var _inputBlur = function () {
    this._hideChoices();
  };

  var _inputKeyup = function (e) {
    switch (e.which) {
      // On 8 (backspace), when at the start, this.delete()
      case 8: this.deleteDecision(); break;

      // On 13 (enter) or tab, select and move next

      // On 27 (Escape), hide the choices
//      case 27: this._hideChoices(); break;
    }
  };

  //
  // Option
  function Option(name, option) {

    var _this = this;
    $.each(option, function (key, value) {
      _this[key] = value;
    });

    this.name = name;
  }

  Option.prototype.isEditable = function () {
    return !!this.choices;
  };

  Option.prototype.findChoice = function (value) {
    var i,
      length;

    if (!this.choices) return this.default;

    for (i = 0, length = this.choices.length; i < length; i++) {
      if (this.choices[i].value === value) return this.choices[i];
    }

    throw new Error("No choice found with value " + value);
  };

  //
  // jQuery plugin

  $.fn.tafi = function (settings) {
    return this.each(function () {
      var tafi = new Tafi($(this), settings);
      return tafi.container;
    });
  };
}(jQuery));
<commit_msg>Fixed deleting implicitly chosen options<commit_after>/*
 * Tafi
 * https://github.com/jeef3/tafi
 *
 * Copyright (c) 2013 Jeff Knaggs
 * Licensed under the MIT license.
 */

(function($) {

  function Tafi($element, settings) {
    var i,
      length,
      choicesValues,
      choiceValue;

    this.initElements($element);
    this.initEvents();

    this.options = _rehydrateOptions(settings.options);
    this.path = settings.path;
    this.partials = settings.partials || {};
    this.decisions = [];

    this.currentJunction = _buildJunction.call(this);

    choicesValues = settings.choicesValues;
    for (i = 0, length = choicesValues.length; i < length; i++) {
      choiceValue = choicesValues[i];

      this.makeDecision(choiceValue);
    }
  }

  Tafi.prototype.initElements = function ($element) {

    // Container
    this.$container = $("<div />", {
      id: $element.attr("id"),
      "class": $element.attr("class")
    });
    this.$container.addClass("tafi");

    // Visible Input
    this.$nextDecision = $("<div />", {
      "class": "tafi-next-decision"
    });

    this.$input = $("<input />", {
      id: $element.attr("id"),
      type: "text",
      placeholder: $element.attr("placeholder"),
      required: $element.prop("required"),
      "class": "tafi-input"
    });
    this.$nextDecision.append(this.$input);

    // Hidden Input
    this.$hidden = $("<input />", {
      type: "hidden",
      name: $element.attr("name")
    });

    this.$container
      .append(this.$nextDecision)
      .append(this.$hidden);

    $element.replaceWith(this.$container);
  };

  Tafi.prototype.initEvents = function () {

    $(document)
      .on("click", $.proxy(_documentClick, this));

    this.$container
      .on("click", ".tafi-decision", $.proxy(_decisionClick, this))
      .on("makedecision", $.proxy(_decisionMade, this))
      .on("deletedecision", $.proxy(_decisionDeleted, this));

    this.$input
      .on("focus", $.proxy(_inputFocus, this))
      .on("blur", $.proxy(_inputBlur, this))
      .on("keyup", $.proxy(_inputKeyup, this));
  };

  Tafi.prototype.redraw = function () {
    this._updateInput();
    this._redrawDecisions();
  };

  Tafi.prototype._redrawDecisions = function () {
    var i,
      length;

    this.$container.find(".tafi-decision").remove();

    for (length = this.decisions.length - 1, i = length; i > -1; i--) {
      this.$container.prepend(_buildDecision(this.decisions[i]));
    }
  };

  Tafi.prototype._updateInput = function () {
    var option = this.options[this.currentJunction.option];

    this.$input.attr("placeholder", option.title);

    this._updateCurrentChoices();
  };

  Tafi.prototype._updateCurrentChoices = function () {
    var option = this.options[this.currentJunction.option],
      $currentChoiceList = _buildOptionChoicesList.call(this, option);

    this.$nextDecision
      .find(".tafi-option-choices")
        .remove()
        .end()
      .append($currentChoiceList);
  };

  Tafi.prototype.showDecisionChoices = function ($decision) {
    this._hideChoices();

    $decision.addClass("tafi-decision-show-options");
  };

  Tafi.prototype._hideChoices = function () {
    this.$container
      .find(".tafi-decision, .tafi-next-decision")
      .removeClass("tafi-decision-show-options");
  };

  Tafi.prototype.makeDecision = function (choiceValue) {
    var option = this.options[this.currentJunction.option],
      nextJunction = _getNextJunction.call(this, this.currentJunction.branches, choiceValue),
      nextOption = this.options[nextJunction.option],
      decision = { option: option, choice: option.findChoice(choiceValue) },
      onlyOption;

    if (!nextJunction) throw new Error("Invalid decision, no branch exists for " + choiceValue);

    this.decisions.push(decision);
    this.currentJunction = nextJunction;

    this.$container.trigger("makedecision", decision);

    // If, from here, there is only one way to go, take it.
    if (!nextOption.choices ||
        $.map(nextOption.choices, function (n, i) { return i; }).length === 1) {

      onlyOption = nextJunction.branches["*"];
      this.makeDecision(onlyOption);
    }
  };

  Tafi.prototype.deleteDecision = function (decision) {
    var index = this.decisions.indexOf(decision),
      removeTo = index === -1 ? - 1 : (this.decisions.length),
      removed = this.decisions.slice(index),
      junction,
      i,
      length,
      decision;

    this.decisions = this.decisions.slice(0, removeTo);

    junction = _buildJunction.call(this);

    // TODO: How to skip implicit decisions (e.g.: separator) and just delete the thing before it?
    for (i = 0, length = this.decisions.length; i < length; i++) {
      decision = this.decisions[i];

      junction = _getNextJunction.call(this, junction.branches, decision.choice.value || decision.choice);
    }

    this.currentJunction = junction;

    this.$container.trigger("deletedecision", removed);

    // If, from here, we are at an implicit decision, go back one more
    if (removed.length === 1 && !removed[0].option.choices) {
      this.deleteDecision();
    }
  };

  Tafi.prototype.reset = function () {
    // Clear all and return to start
  };

  //
  // Private

  var _rehydrateOptions = function (optionsData) {
    var options = {};

    $.each(optionsData, function (name, option) {
      options[name] = new Option(name, option);
    });

    return options;
  };

  var _buildJunction = function (junctionRoot) {
    var junction;

    if (!junctionRoot) {
      junctionRoot = this.path;
    }

    if (typeof junctionRoot === "string") {
      junction = this.partials[junctionRoot];
    } else {
      junction = junctionRoot;
    }

    return junction;
  };

  var _getNextJunction = function (branches, choice) {
    var junctionRoot;

    $.each(branches, function (choiceValue, junction) {
      if (choiceValue === choice) {
        junctionRoot = junction;
        return false;
      }
    });

    if (!junctionRoot && branches["*"]) {
      junctionRoot = branches["*"];
    }

    if (!junctionRoot) {
      throw new Error("No path found for " + choice);
    }

    return _buildJunction.call(this, junctionRoot);
  };

  var _buildDecision = function (decision) {
    var option = decision.option,
      choice = decision.choice,
      $inner = $("<span />"),
      $decision = $("<div />");

    $decision
      .addClass("tafi-decision")
      .addClass(option.isEditable() ? "tafi-editable-decision" : "tafi-noneditable-decision")
      .data("tafi-option", option.name)
      .data("tafi-choice", choice.value || choice)

    if (option.title && choice.label) {
      $decision.attr("title", "" + option.title + ": " + choice.label);
    }

    $inner
      .addClass("tafi-decision-text")
      .text(choice.text || choice);

    $decision
      .append($inner)
      .append(_buildOptionChoicesList(option));

    return $decision;
  };

  var _buildOptionChoicesList = function (option) {
    var $optionChoices;

    if (!option.choices) return $();

    $optionChoices = $("<ul />", { "class": "tafi-option-choices" });

    // TODO: Take an optional class value from dev? e.g.: dropdown-menu
    // $optionChoices.addClass(settings.optionChoicesClass);

    $.each(option.choices, function (index, choice) {
      var $li = $("<li />");

      $li
        .data("tafi-value", choice.value)
        .append($("<a />", { href: "#", role: "button" }).text(choice.label));

      $optionChoices.append($li);
    });

    return $optionChoices;
  };

  //
  // Events

  var _documentClick = function (e) {
//    this._hideChoices();
  };

  var _decisionClick = function (e) {
    // Set the active decision as the clicked one

    // display the option choices
    this.showDecisionChoices($(e.currentTarget));
  };

  var _decisionMade = function () {
    this.redraw()
  };

  var _decisionDeleted = function () {
    this.redraw();
  };

  var _inputFocus = function (e) {
    var $choiceList = $(e.currentTarget).parent();
    this.showDecisionChoices($choiceList);
  };

  var _inputBlur = function () {
    this._hideChoices();
  };

  var _inputKeyup = function (e) {
    switch (e.which) {
      // On 8 (backspace), when at the start, this.delete()
      case 8: this.deleteDecision(); break;

      // On 13 (enter) or tab, select and move next

      // On 27 (Escape), hide the choices
//      case 27: this._hideChoices(); break;
    }
  };

  //
  // Option
  function Option(name, option) {

    var _this = this;
    $.each(option, function (key, value) {
      _this[key] = value;
    });

    this.name = name;
  }

  Option.prototype.isEditable = function () {
    return !!this.choices;
  };

  Option.prototype.findChoice = function (value) {
    var i,
      length;

    if (!this.choices) return this.default;

    for (i = 0, length = this.choices.length; i < length; i++) {
      if (this.choices[i].value === value) return this.choices[i];
    }

    throw new Error("No choice found with value " + value);
  };

  //
  // jQuery plugin

  $.fn.tafi = function (settings) {
    return this.each(function () {
      var tafi = new Tafi($(this), settings);
      return tafi.container;
    });
  };
}(jQuery));
<|endoftext|>


<commit_before>'use strict'

const path = require('path')
require('app-module-path').addPath(path.join(__dirname, '..'))

const PeerId = require('peer-id')
const map = require('async/map')

// const sinon = require("sinon")
// const chai = require("chai")
// chai.use(require("sinon-chai"))
// chai.should()
// const expect = chai.expect
const assert = require('assert')

// global.sinon = sinon
// global.expect = expect
global.assert = assert
// global.chai = chai

before(function (cb) {
  map(require('../test/ids.json'), PeerId.createFromJSON, (e, ids) => {
    if (e) return cb(e)
    global.id = ids[0]
    global.ids = ids
    cb()
  })
})

const fs = require('fs')
const files = fs.readdirSync(__dirname + '/').filter(file => fs.lstatSync(__dirname + '/' + file).isDirectory()).sort()
const cp = require('child_process')

it.zero = (args, opts) => cp.spawn('docker', ['run', '-p', '13355:15541', '-p', '127.0.0.1:44220:43110', 'mkg20001/zeronet-docker', process.env.DEBUG ? '--debug' : '--silent', '--ui_ip', '0.0.0.0'].concat(Array.isArray(args) ? args : [args]), opts || {
  stdio: 'inherit'
})
it.zhost = cp.execSync("ip -4 addr show docker0 | grep -Po 'inet \\K[\\d.]+'").toString().split('\n').shift()

describe('zeronet', function () {
  files.forEach(file => {
    describe(file, function () {
      fs.readdirSync(__dirname + '/' + file).sort().forEach(function (file2) {
        if (file2.endsWith('.js')) require(__dirname + '/' + file + '/' + file2)
      })
    })
  })
})

after(() => setTimeout(() => process.exit(0), 1000))
<commit_msg>fix: interop tests<commit_after>'use strict'

const path = require('path')
require('app-module-path').addPath(path.join(__dirname, '..'))

const PeerId = require('peer-id')
const map = require('async/map')

// const sinon = require("sinon")
// const chai = require("chai")
// chai.use(require("sinon-chai"))
// chai.should()
// const expect = chai.expect
const assert = require('assert')

// global.sinon = sinon
// global.expect = expect
global.assert = assert
// global.chai = chai

before(function (cb) {
  map(require('../test/ids.json'), PeerId.createFromJSON, (e, ids) => {
    if (e) return cb(e)
    global.id = ids[0]
    global.ids = ids
    cb()
  })
})

const fs = require('fs')
const files = fs.readdirSync(__dirname + '/').filter(file => fs.lstatSync(__dirname + '/' + file).isDirectory()).sort()
const cp = require('child_process')

it.zero = (args, opts) => cp.spawn('docker', ['run', /* '-p', '13355:15541', '-p', '127.0.0.1:44220:43110', */ 'mkg20001/zeronet-docker', process.env.DEBUG ? '--debug' : '--silent', '--ui_ip', '0.0.0.0'].concat(Array.isArray(args) ? args : [args]), opts || {
  stdio: 'inherit'
})
it.zhost = cp.execSync("ip -4 addr show docker0 | grep -Po 'inet \\K[\\d.]+'").toString().split('\n').shift()

describe('zeronet', function () {
  files.forEach(file => {
    describe(file, function () {
      fs.readdirSync(__dirname + '/' + file).sort().forEach(function (file2) {
        if (file2.endsWith('.js')) require(__dirname + '/' + file + '/' + file2)
      })
    })
  })
})

after(() => setTimeout(() => process.exit(0), 1000))
<|endoftext|>


<commit_before>/* globals window, exports, define */

(function(window) {
    'use strict'

    var re = {
        not_string: /[^s]/,
        not_bool: /[^t]/,
        not_type: /[^T]/,
        not_primitive: /[^v]/,
        number: /[diefg]/,
        numeric_arg: /[bcdiefguxX]/,
        json: /[j]/,
        not_json: /[^j]/,
        text: /^[^\x25]+/,
        modulo: /^\x25{2}/,
        placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,
        key: /^([a-z_][a-z_\d]*)/i,
        key_access: /^\.([a-z_][a-z_\d]*)/i,
        index_access: /^\[(\d+)\]/,
        sign: /^[\+\-]/
    }

    function sprintf() {
        var key = arguments[0], cache = sprintf.cache
        if (!(cache[key])) {
            cache[key] = sprintf.parse(key)
        }
        return sprintf.format.call(null, cache[key], arguments)
    }

    sprintf.format = function(parse_tree, argv) {
        var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ''
        for (i = 0; i < tree_length; i++) {
            node_type = get_type(parse_tree[i])
            if (node_type === 'string') {
                output[output.length] = parse_tree[i]
            }
            else if (node_type === 'array') {
                match = parse_tree[i] // convenience purposes only
                if (match[2]) { // keyword argument
                    arg = argv[cursor]
                    for (k = 0; k < match[2].length; k++) {
                        if (!arg.hasOwnProperty(match[2][k])) {
                            throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k]))
                        }
                        arg = arg[match[2][k]]
                    }
                }
                else if (match[1]) { // positional argument (explicit)
                    arg = argv[match[1]]
                }
                else { // positional argument (implicit)
                    arg = argv[cursor++]
                }

                if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && get_type(arg) == 'function') {
                    arg = arg()
                }

                if (re.numeric_arg.test(match[8]) && (get_type(arg) != 'number' && isNaN(arg))) {
                    throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
                }

                if (re.number.test(match[8])) {
                    is_positive = arg >= 0
                }

                switch (match[8]) {
                    case 'b':
                        arg = parseInt(arg, 10).toString(2)
                    break
                    case 'c':
                        arg = String.fromCharCode(parseInt(arg, 10))
                    break
                    case 'd':
                    case 'i':
                        arg = parseInt(arg, 10)
                    break
                    case 'j':
                        arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
                    break
                    case 'e':
                        arg = match[7] ? parseFloat(arg).toExponential(match[7]) : parseFloat(arg).toExponential()
                    break
                    case 'f':
                        arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
                    break
                    case 'g':
                        arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
                    break
                    case 'o':
                        arg = arg.toString(8)
                    break
                    case 's':
                        arg = String(arg)
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 't':
                        arg = String(!!arg)
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 'T':
                        arg = get_type(arg)
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 'u':
                        arg = parseInt(arg, 10) >>> 0
                    break
                    case 'v':
                        arg = arg.valueOf()
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 'x':
                        arg = parseInt(arg, 10).toString(16)
                    break
                    case 'X':
                        arg = parseInt(arg, 10).toString(16).toUpperCase()
                    break
                }
                if (re.json.test(match[8])) {
                    output[output.length] = arg
                }
                else {
                    if (re.number.test(match[8]) && (!is_positive || match[3])) {
                        sign = is_positive ? '+' : '-'
                        arg = arg.toString().replace(re.sign, '')
                    }
                    else {
                        sign = ''
                    }
                    pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' '
                    pad_length = match[6] - (sign + arg).length
                    pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : '') : ''
                    output[output.length] = match[5] ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg)
                }
            }
        }
        return output.join('')
    }

    sprintf.cache = Object.create(null)

    sprintf.parse = function(fmt) {
        var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
        while (_fmt) {
            if ((match = re.text.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = match[0]
            }
            else if ((match = re.modulo.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = '%'
            }
            else if ((match = re.placeholder.exec(_fmt)) !== null) {
                if (match[2]) {
                    arg_names |= 1
                    var field_list = [], replacement_field = match[2], field_match = []
                    if ((field_match = re.key.exec(replacement_field)) !== null) {
                        field_list[field_list.length] = field_match[1]
                        while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
                            if ((field_match = re.key_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else {
                                throw new SyntaxError("[sprintf] failed to parse named argument key")
                            }
                        }
                    }
                    else {
                        throw new SyntaxError("[sprintf] failed to parse named argument key")
                    }
                    match[2] = field_list
                }
                else {
                    arg_names |= 2
                }
                if (arg_names === 3) {
                    throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
                }
                parse_tree[parse_tree.length] = match
            }
            else {
                throw new SyntaxError("[sprintf] unexpected placeholder")
            }
            _fmt = _fmt.substring(match[0].length)
        }
        return parse_tree
    }

    var vsprintf = function(fmt, argv, _argv) {
        _argv = (argv || []).slice(0)
        _argv.splice(0, 0, fmt)
        return sprintf.apply(null, _argv)
    }

    /**
     * helpers
     */
    function get_type(variable) {
        if (typeof variable === 'number') {
            return 'number'
        }
        else if (typeof variable === 'string') {
            return 'string'
        }
        else {
            return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
        }
    }

    var preformattedPadding = {
        '0': ['', '0', '00', '000', '0000', '00000', '000000', '0000000'],
        ' ': ['', ' ', '  ', '   ', '    ', '     ', '      ', '       '],
        '_': ['', '_', '__', '___', '____', '_____', '______', '_______'],
    }
    function str_repeat(input, multiplier) {
        if (multiplier >= 0 && multiplier <= 7 && preformattedPadding[input]) {
            return preformattedPadding[input][multiplier]
        }
        return Array(multiplier + 1).join(input)
    }

    /**
     * export to either browser or node.js
     */
    if (typeof exports !== 'undefined') {
        exports['sprintf'] = sprintf
        exports['vsprintf'] = vsprintf
    }
    if (typeof window !== 'undefined') {
        window['sprintf'] = sprintf
        window['vsprintf'] = vsprintf

        if (typeof define === 'function' && define['amd']) {
            define(function() {
                return {
                    'sprintf': sprintf,
                    'vsprintf': vsprintf
                }
            })
        }
    }
})(typeof window === 'undefined' ? this : window);
<commit_msg>Use `String.prototype.repeat()` instead of custom function (IE needs a polyfill)<commit_after>/* globals window, exports, define */

(function(window) {
    'use strict'

    var re = {
        not_string: /[^s]/,
        not_bool: /[^t]/,
        not_type: /[^T]/,
        not_primitive: /[^v]/,
        number: /[diefg]/,
        numeric_arg: /[bcdiefguxX]/,
        json: /[j]/,
        not_json: /[^j]/,
        text: /^[^\x25]+/,
        modulo: /^\x25{2}/,
        placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,
        key: /^([a-z_][a-z_\d]*)/i,
        key_access: /^\.([a-z_][a-z_\d]*)/i,
        index_access: /^\[(\d+)\]/,
        sign: /^[\+\-]/
    }

    function sprintf() {
        var key = arguments[0], cache = sprintf.cache
        if (!(cache[key])) {
            cache[key] = sprintf.parse(key)
        }
        return sprintf.format.call(null, cache[key], arguments)
    }

    sprintf.format = function(parse_tree, argv) {
        var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ''
        for (i = 0; i < tree_length; i++) {
            node_type = get_type(parse_tree[i])
            if (node_type === 'string') {
                output[output.length] = parse_tree[i]
            }
            else if (node_type === 'array') {
                match = parse_tree[i] // convenience purposes only
                if (match[2]) { // keyword argument
                    arg = argv[cursor]
                    for (k = 0; k < match[2].length; k++) {
                        if (!arg.hasOwnProperty(match[2][k])) {
                            throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k]))
                        }
                        arg = arg[match[2][k]]
                    }
                }
                else if (match[1]) { // positional argument (explicit)
                    arg = argv[match[1]]
                }
                else { // positional argument (implicit)
                    arg = argv[cursor++]
                }

                if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && get_type(arg) == 'function') {
                    arg = arg()
                }

                if (re.numeric_arg.test(match[8]) && (get_type(arg) != 'number' && isNaN(arg))) {
                    throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
                }

                if (re.number.test(match[8])) {
                    is_positive = arg >= 0
                }

                switch (match[8]) {
                    case 'b':
                        arg = parseInt(arg, 10).toString(2)
                    break
                    case 'c':
                        arg = String.fromCharCode(parseInt(arg, 10))
                    break
                    case 'd':
                    case 'i':
                        arg = parseInt(arg, 10)
                    break
                    case 'j':
                        arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
                    break
                    case 'e':
                        arg = match[7] ? parseFloat(arg).toExponential(match[7]) : parseFloat(arg).toExponential()
                    break
                    case 'f':
                        arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
                    break
                    case 'g':
                        arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
                    break
                    case 'o':
                        arg = arg.toString(8)
                    break
                    case 's':
                        arg = String(arg)
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 't':
                        arg = String(!!arg)
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 'T':
                        arg = get_type(arg)
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 'u':
                        arg = parseInt(arg, 10) >>> 0
                    break
                    case 'v':
                        arg = arg.valueOf()
                        arg = (match[7] ? arg.substring(0, match[7]) : arg)
                    break
                    case 'x':
                        arg = parseInt(arg, 10).toString(16)
                    break
                    case 'X':
                        arg = parseInt(arg, 10).toString(16).toUpperCase()
                    break
                }
                if (re.json.test(match[8])) {
                    output[output.length] = arg
                }
                else {
                    if (re.number.test(match[8]) && (!is_positive || match[3])) {
                        sign = is_positive ? '+' : '-'
                        arg = arg.toString().replace(re.sign, '')
                    }
                    else {
                        sign = ''
                    }
                    pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' '
                    pad_length = match[6] - (sign + arg).length
                    pad = match[6] ? (pad_length > 0 ? pad_character.repeat(pad_length) : '') : ''
                    output[output.length] = match[5] ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg)
                }
            }
        }
        return output.join('')
    }

    sprintf.cache = Object.create(null)

    sprintf.parse = function(fmt) {
        var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
        while (_fmt) {
            if ((match = re.text.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = match[0]
            }
            else if ((match = re.modulo.exec(_fmt)) !== null) {
                parse_tree[parse_tree.length] = '%'
            }
            else if ((match = re.placeholder.exec(_fmt)) !== null) {
                if (match[2]) {
                    arg_names |= 1
                    var field_list = [], replacement_field = match[2], field_match = []
                    if ((field_match = re.key.exec(replacement_field)) !== null) {
                        field_list[field_list.length] = field_match[1]
                        while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
                            if ((field_match = re.key_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
                                field_list[field_list.length] = field_match[1]
                            }
                            else {
                                throw new SyntaxError("[sprintf] failed to parse named argument key")
                            }
                        }
                    }
                    else {
                        throw new SyntaxError("[sprintf] failed to parse named argument key")
                    }
                    match[2] = field_list
                }
                else {
                    arg_names |= 2
                }
                if (arg_names === 3) {
                    throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
                }
                parse_tree[parse_tree.length] = match
            }
            else {
                throw new SyntaxError("[sprintf] unexpected placeholder")
            }
            _fmt = _fmt.substring(match[0].length)
        }
        return parse_tree
    }

    var vsprintf = function(fmt, argv, _argv) {
        _argv = (argv || []).slice(0)
        _argv.splice(0, 0, fmt)
        return sprintf.apply(null, _argv)
    }

    /**
     * helpers
     */
    function get_type(variable) {
        if (typeof variable === 'number') {
            return 'number'
        }
        else if (typeof variable === 'string') {
            return 'string'
        }
        else {
            return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
        }
    }

    /**
     * export to either browser or node.js
     */
    if (typeof exports !== 'undefined') {
        exports['sprintf'] = sprintf
        exports['vsprintf'] = vsprintf
    }
    if (typeof window !== 'undefined') {
        window['sprintf'] = sprintf
        window['vsprintf'] = vsprintf

        if (typeof define === 'function' && define['amd']) {
            define(function() {
                return {
                    'sprintf': sprintf,
                    'vsprintf': vsprintf
                }
            })
        }
    }
})(typeof window === 'undefined' ? this : window);
<|endoftext|>


<commit_before>elation.requireCSS('ui.input');
elation.require(['elements.elements'], function() {
  /**
   * Handles text input from users
   *
   * @class input
   * @augments elation.ui.base
   * @memberof elation.ui
   *
   * @param {object} args
   * @param {string} args.type
   * @param {string} args.value
   * @param {string} args.inputname
   * @param {string} args.placeholder
   * @param {boolean} args.disabled
   * @param {boolean} args.hidden
   * @param {boolean} args.autofocus
   */

  elation.elements.define('ui.input', class extends elation.elements.base {
    /**
     * Initialize component
     * @function init
     * @memberof elation.ui.input#
     */
    init() {
      super.init();
      this.defineAttributes({
        hidden: { type: 'boolean', default: false },
        label: { type: 'string' },
        type: { type: 'string' },
        placeholder: { type: 'string' },
        value: { type: 'string', get: this.getValue, set: this.setValue },
        disabled: { type: 'boolean', default: false },
        autofocus: { type: 'boolean', get: this.getAutofocus, set: this.setAutofocus },
        onaccept: { type: 'callback' },
      });

      if (this.preview) {
        this.value = 'Lorem ipsum dolor sit amet...';
      }
    }
    create() {
      if (this.label) {
        //this.labelobj = elation.ui.label({ append: this, label: this.label });
        this.labelobject = elation.elements.create('ui.label', { append: this, label: this.label });
        elation.events.add(this.labelobject, 'click', (ev) => { this.focus(); ev.stopPropagation(); });
      }

      this.inputelement = elation.html.create({tag: 'input', append: this});

      if (this.type) {
        this.inputelement.type = this.type;
      }

      for (var k in this.events) {
        elation.events.add(this.inputelement, k, this.events[k]);
      }

      if (this.hidden) this.hide();

      if (this.placeholder) {
        this.inputelement.placeholder = this.placeholder;
      }

      let value = this.value;
      elation.events.add(this, 'keydown', this.handlekeydown.bind(this));
      this.addEventProxies(this.inputelement, 'keyup,keypress,focus,blur,input,select,change');
      this.addPropertyProxies(this.inputelement, 'value,disabled,autofocus,form,name,type,required,placeholder');
      if (value) {
        this.value = value;
      }

      elation.events.add(this, 'focus', this.handlefocus.bind(this));

      // Set up object setters/getters to bridge with HTML element attributes
/*
      Object.defineProperty(this, "value", { get: function() { return this.inputelement.value; }, set: function(v) { this.inputelement.value = v; } });
      Object.defineProperty(this, "disabled", { get: function() { return this.inputelement.disabled; }, set: function(v) { this.inputelement.disabled = v; } });
      Object.defineProperty(this, "autofocus", { get: function() { return this.inputelement.autofocus; }, set: function(v) { this.inputelement.autofocus = v; } });
*/

      if (this.name) {
        this.inputelement.name = this.name;
      }
      if (this.placeholder) {
        this.inputelement.placeholder = this.placeholder;
      }
      if (this.disabled) {
        this.inputelement.disabled = true;
      }
      if (this.autofocus) {
        this.inputelement.autofocus = true;
      }
      if (this.value) {
        this.inputelement.value = this.value;
      }
    }
    /**
     * Mark this component as being enabled
     * @function enable
     * @memberof elation.ui.input#
     */
    enable() {
      this.disabled = false;
    }
    /** Mark this component as being disabled
     * @function disable
     * @memberof elation.ui.input#
     */
    disable() {
      this.disabled = true;
    }
    /** Sets this input element as focused
     * @function focus
     * @memberof elation.ui.input#
     */
    focus() {
      this.inputelement.focus();
      //this.dispatchEvent({type: 'focus'});
    }
    /** Removes focus from this input element
     * @function blur
     * @memberof elation.ui.input#
     */
    blur() {
      this.inputelement.blur();
      //this.dispatchEvent({type: 'blur'});
    }
    /** Accepts the current value of the input component and emit appropriate events
     * @function accept
     * @memberof elation.ui.input#
     * @fire elation.ui.input#ui_input_accept
     */
    accept(ev) {
      this.blur();
      this.dispatchEvent({
        type: 'accept',
        data: this.value,
        shiftKey: ev && ev.shiftKey,
        altKey: ev && ev.altKey,
        ctrlKey: ev && ev.ctrlKey,
        metaKey: ev && ev.metaKey,
      });
    }
    /** Restore input value to what it was before editing began and emit appropriate events
     * @function cancel
     * @memberof elation.ui.input#
     * @fire elation.ui.input#ui_input_cancel
     */
    cancel() {
      if (!elation.utils.isNull(this.lastvalue) && this.lastvalue != this.value) {
        this.value = this.lastvalue;
        this.dispatchEvent({type: 'cancel', data: this.value});
      }
      this.blur();
    }
    /** Select all text
     * @function selectall
     * @memberof elation.ui.input#
     * @fire elation.ui.input#ui_input_select
     */
    select() {
      this.inputelement.setSelectionRange(0, this.value.length)
      this.dispatchEvent({type: 'select', data: this.value});
    }
    /**
     * Reset input to blank, optionally focusing it
     * @function clear
     * @memberof elation.ui.input#
     * @param focus boolean force focus on this component
     * @fire elation.ui.input#ui_input_clear
     */
    clear(focus) {
      this.value = "";
      this.lastvalue = "";
      if (focus) {
        this.focus();
      }
      this.dispatchEvent({type: 'clear', data: this.value});
    }
    /**
     * Event handler for HTML input element's keydown event
     * @function handlekeydown
     * @memberof elation.ui.input#
     * @param ev event
     */
    handlekeydown(ev) {
      switch (ev.keyCode) {
        case 13: // enter
          this.accept(ev);
          break;
        case 27: // esc
          this.cancel();
          break;
      }
    }
    /**
     * Event handler for HTML input element's focus event
     * @function handlefocus
     * @memberof elation.ui.input#
     * @param ev event
     */
    handlefocus(ev) {
      this.lastvalue = this.value;
      //this.dispatchEvent({type: 'focus'});
    }
  });
});
<commit_msg>Don't try to focus if input hasn't been created yet<commit_after>elation.requireCSS('ui.input');
elation.require(['elements.elements'], function() {
  /**
   * Handles text input from users
   *
   * @class input
   * @augments elation.ui.base
   * @memberof elation.ui
   *
   * @param {object} args
   * @param {string} args.type
   * @param {string} args.value
   * @param {string} args.inputname
   * @param {string} args.placeholder
   * @param {boolean} args.disabled
   * @param {boolean} args.hidden
   * @param {boolean} args.autofocus
   */

  elation.elements.define('ui.input', class extends elation.elements.base {
    /**
     * Initialize component
     * @function init
     * @memberof elation.ui.input#
     */
    init() {
      super.init();
      this.defineAttributes({
        hidden: { type: 'boolean', default: false },
        label: { type: 'string' },
        type: { type: 'string' },
        placeholder: { type: 'string' },
        value: { type: 'string', get: this.getValue, set: this.setValue },
        disabled: { type: 'boolean', default: false },
        autofocus: { type: 'boolean', get: this.getAutofocus, set: this.setAutofocus },
        onaccept: { type: 'callback' },
      });

      if (this.preview) {
        this.value = 'Lorem ipsum dolor sit amet...';
      }
    }
    create() {
      if (this.label) {
        //this.labelobj = elation.ui.label({ append: this, label: this.label });
        this.labelobject = elation.elements.create('ui.label', { append: this, label: this.label });
        elation.events.add(this.labelobject, 'click', (ev) => { this.focus(); ev.stopPropagation(); });
      }

      this.inputelement = elation.html.create({tag: 'input', append: this});

      if (this.type) {
        this.inputelement.type = this.type;
      }

      for (var k in this.events) {
        elation.events.add(this.inputelement, k, this.events[k]);
      }

      if (this.hidden) this.hide();

      if (this.placeholder) {
        this.inputelement.placeholder = this.placeholder;
      }

      let value = this.value;
      elation.events.add(this, 'keydown', this.handlekeydown.bind(this));
      this.addEventProxies(this.inputelement, 'keyup,keypress,focus,blur,input,select,change');
      this.addPropertyProxies(this.inputelement, 'value,disabled,autofocus,form,name,type,required,placeholder');
      if (value) {
        this.value = value;
      }

      elation.events.add(this, 'focus', this.handlefocus.bind(this));

      // Set up object setters/getters to bridge with HTML element attributes
/*
      Object.defineProperty(this, "value", { get: function() { return this.inputelement.value; }, set: function(v) { this.inputelement.value = v; } });
      Object.defineProperty(this, "disabled", { get: function() { return this.inputelement.disabled; }, set: function(v) { this.inputelement.disabled = v; } });
      Object.defineProperty(this, "autofocus", { get: function() { return this.inputelement.autofocus; }, set: function(v) { this.inputelement.autofocus = v; } });
*/

      if (this.name) {
        this.inputelement.name = this.name;
      }
      if (this.placeholder) {
        this.inputelement.placeholder = this.placeholder;
      }
      if (this.disabled) {
        this.inputelement.disabled = true;
      }
      if (this.autofocus) {
        this.inputelement.autofocus = true;
      }
      if (this.value) {
        this.inputelement.value = this.value;
      }
    }
    /**
     * Mark this component as being enabled
     * @function enable
     * @memberof elation.ui.input#
     */
    enable() {
      this.disabled = false;
    }
    /** Mark this component as being disabled
     * @function disable
     * @memberof elation.ui.input#
     */
    disable() {
      this.disabled = true;
    }
    /** Sets this input element as focused
     * @function focus
     * @memberof elation.ui.input#
     */
    focus() {
      if (this.inputelement) {
        this.inputelement.focus();
      }
      //this.dispatchEvent({type: 'focus'});
    }
    /** Removes focus from this input element
     * @function blur
     * @memberof elation.ui.input#
     */
    blur() {
      this.inputelement.blur();
      //this.dispatchEvent({type: 'blur'});
    }
    /** Accepts the current value of the input component and emit appropriate events
     * @function accept
     * @memberof elation.ui.input#
     * @fire elation.ui.input#ui_input_accept
     */
    accept(ev) {
      this.blur();
      this.dispatchEvent({
        type: 'accept',
        data: this.value,
        shiftKey: ev && ev.shiftKey,
        altKey: ev && ev.altKey,
        ctrlKey: ev && ev.ctrlKey,
        metaKey: ev && ev.metaKey,
      });
    }
    /** Restore input value to what it was before editing began and emit appropriate events
     * @function cancel
     * @memberof elation.ui.input#
     * @fire elation.ui.input#ui_input_cancel
     */
    cancel() {
      if (!elation.utils.isNull(this.lastvalue) && this.lastvalue != this.value) {
        this.value = this.lastvalue;
        this.dispatchEvent({type: 'cancel', data: this.value});
      }
      this.blur();
    }
    /** Select all text
     * @function selectall
     * @memberof elation.ui.input#
     * @fire elation.ui.input#ui_input_select
     */
    select() {
      this.inputelement.setSelectionRange(0, this.value.length)
      this.dispatchEvent({type: 'select', data: this.value});
    }
    /**
     * Reset input to blank, optionally focusing it
     * @function clear
     * @memberof elation.ui.input#
     * @param focus boolean force focus on this component
     * @fire elation.ui.input#ui_input_clear
     */
    clear(focus) {
      this.value = "";
      this.lastvalue = "";
      if (focus) {
        this.focus();
      }
      this.dispatchEvent({type: 'clear', data: this.value});
    }
    /**
     * Event handler for HTML input element's keydown event
     * @function handlekeydown
     * @memberof elation.ui.input#
     * @param ev event
     */
    handlekeydown(ev) {
      switch (ev.keyCode) {
        case 13: // enter
          this.accept(ev);
          break;
        case 27: // esc
          this.cancel();
          break;
      }
    }
    /**
     * Event handler for HTML input element's focus event
     * @function handlefocus
     * @memberof elation.ui.input#
     * @param ev event
     */
    handlefocus(ev) {
      this.lastvalue = this.value;
      //this.dispatchEvent({type: 'focus'});
    }
  });
});
<|endoftext|>


<commit_before>// If we can, we use the browser extension API to store data
// it's async though, so we synchronize changes from an intermediate
// storageData object
const useStorage = typeof chrome.storage !== 'undefined'

let storageData = null

export function init () {
  return new Promise((resolve) => {
    if (useStorage) {
      chrome.storage.local.get(null, result => {
        storageData = result
        resolve()
      })
    } else {
      storageData = {}
      resolve()
    }
  })
}

export function get (key, defaultValue = null) {
  checkStorage()
  if (useStorage) {
    return getDefaultValue(storageData[key], defaultValue)
  } else {
    try {
      return getDefaultValue(JSON.parse(localStorage.getItem(key)), defaultValue)
    } catch (e) {}
  }
}

export function set (key, val) {
  checkStorage()
  if (useStorage) {
    storageData[key] = val
    chrome.storage.local.set({ [key]: val })
  } else {
    try {
      localStorage.setItem(key, JSON.stringify(val))
    } catch (e) {}
  }
}

export function remove (key) {
  checkStorage()
  if (useStorage) {
    delete storageData[key]
    chrome.storage.local.remove([key])
  } else {
    try {
      localStorage.removeItem(key)
    } catch (e) {}
  }
}

export function clear () {
  checkStorage()
  if (useStorage) {
    storageData = {}
    chrome.storage.local.clear()
  } else {
    try {
      localStorage.clear()
    } catch (e) {}
  }
}

function checkStorage () {
  if (!storageData) {
    throw new Error(`Storage wasn't initialized with 'init()'`)
  }
}

function getDefaultValue (value, defaultValue) {
  if (value == null) {
    return defaultValue
  }
  return value
}
<commit_msg>fix: chrome API check<commit_after>// If we can, we use the browser extension API to store data
// it's async though, so we synchronize changes from an intermediate
// storageData object
const useStorage = typeof chrome !== 'undefined' && typeof chrome.storage !== 'undefined'

let storageData = null

export function init () {
  return new Promise((resolve) => {
    if (useStorage) {
      chrome.storage.local.get(null, result => {
        storageData = result
        resolve()
      })
    } else {
      storageData = {}
      resolve()
    }
  })
}

export function get (key, defaultValue = null) {
  checkStorage()
  if (useStorage) {
    return getDefaultValue(storageData[key], defaultValue)
  } else {
    try {
      return getDefaultValue(JSON.parse(localStorage.getItem(key)), defaultValue)
    } catch (e) {}
  }
}

export function set (key, val) {
  checkStorage()
  if (useStorage) {
    storageData[key] = val
    chrome.storage.local.set({ [key]: val })
  } else {
    try {
      localStorage.setItem(key, JSON.stringify(val))
    } catch (e) {}
  }
}

export function remove (key) {
  checkStorage()
  if (useStorage) {
    delete storageData[key]
    chrome.storage.local.remove([key])
  } else {
    try {
      localStorage.removeItem(key)
    } catch (e) {}
  }
}

export function clear () {
  checkStorage()
  if (useStorage) {
    storageData = {}
    chrome.storage.local.clear()
  } else {
    try {
      localStorage.clear()
    } catch (e) {}
  }
}

function checkStorage () {
  if (!storageData) {
    throw new Error(`Storage wasn't initialized with 'init()'`)
  }
}

function getDefaultValue (value, defaultValue) {
  if (value == null) {
    return defaultValue
  }
  return value
}
<|endoftext|>


<commit_before>  // Strings
  // ---------------------------------------------------------------------------

  /**
   * Assert subject is a String
   * @return {Boolean}
   */
  matchers.toBeString = function() {
    return priv.is(this.actual, 'String');
  };

  /**
   * @return {Boolean}
   */
  matchers.toBeEmptyString = function() {
    return this.actual === '';
  };

  /**
   * @return {Boolean}
   */
  matchers.toBeNonEmptyString = function() {
    return matchers.toBeString.call(this) && this.actual.length > 0;
  };

  /**
   * Assert subject is string containing HTML Markup
   * @return {Boolean}
   */
  matchers.toBeHtmlString = function() {
    var container = document.createElement('div');
    container.innerHTML = this.actual;
    return matchers.toBeString.call(this) && priv.some(container.childNodes, function(node) {
      return node.nodeType !== 3;
    });
  };

  /**
   * Assert subject is string containing parseable JSON
   * @return {Boolean}
   */
  matchers.toBeJsonString = function() {
    var isParseable;
    var json;
    try {
      json = JSON.parse(this.actual);
    } catch (e) {
      isParseable = false;
    }
    return isParseable !== false && json !== null;
  };

  /**
   * Assert subject is a String containing nothing but whitespace
   * @return {Boolean}
   */
  matchers.toBeWhitespace = function() {
    return matchers.toBeString.call(this) && this.actual.search(/\S/) === -1;
  };

  /**
   * Assert subject is a String whose first characters match our expected string
   * @param  {String} expected
   * @return {Boolean}
   */
  matchers.toStartWith = function(expected) {
    if (!matchers.toBeNonEmptyString.call(this) || !matchers.toBeNonEmptyString.call({
      actual: expected
    })) {
      return false;
    }
    return this.actual.slice(0, expected.length) === expected;
  };

  /**
   * Assert subject is a String whose last characters match our expected string
   * @param  {String} expected
   * @return {Boolean}
   */
  matchers.toEndWith = function(expected) {
    if (!matchers.toBeNonEmptyString.call(this) || !matchers.toBeNonEmptyString.call({
      actual: expected
    })) {
      return false;
    }
    return this.actual.slice(this.actual.length - expected.length, this.actual.length) === expected;
  };

  /**
   * Assert subject is a String whose length is greater than our other string
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toBeLongerThan = function(other) {
    return matchers.toBeString.call(this) && matchers.toBeString.call({
      actual: other
    }) && this.actual.length > other.length;
  };

  /**
   * Assert subject is a String whose length is less than our other string
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toBeShorterThan = function(other) {
    return matchers.toBeString.call(this) && matchers.toBeString.call({
      actual: other
    }) && this.actual.length < other.length;
  };

  /**
   * Assert subject is a String whose length is equal to our other string
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toBeSameLengthAs = function(other) {
    return matchers.toBeString.call(this) && matchers.toBeString.call({
      actual: other
    }) && this.actual.length === other.length;
  };

  // String Members
  // ---------------------------------------------------------------------------

  /**
   * @name toHaveEmptyString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveEmptyString = function(memberName) {
    return priv.assertMember.call(this, 'toBeEmptyString', memberName);
  };

  /**
   * @name toHaveHtmlString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveHtmlString = function(memberName) {
    return priv.assertMember.call(this, 'toBeHtmlString', memberName);
  };

  /**
   * @name toHaveJsonString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveJsonString = function(memberName) {
    return priv.assertMember.call(this, 'toBeJsonString', memberName);
  };

  /**
   * @name toHaveNonEmptyString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveNonEmptyString = function(memberName) {
    return priv.assertMember.call(this, 'toBeNonEmptyString', memberName);
  };

  /**
   * @name toHaveString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveString = function(memberName) {
    return priv.assertMember.call(this, 'toBeString', memberName);
  };

  /**
   * @name toHaveStringLongerThan
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toHaveStringLongerThan = function(memberName, other) {
    return priv.assertMember.call(this, 'toBeLongerThan', memberName, other);
  };

  /**
   * @name toHaveStringSameLengthAs
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toHaveStringSameLengthAs = function(memberName, other) {
    return priv.assertMember.call(this, 'toBeSameLengthAs', memberName, other);
  };

  /**
   * @name toHaveStringShorterThan
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toHaveStringShorterThan = function(memberName, other) {
    return priv.assertMember.call(this, 'toBeShorterThan', memberName, other);
  };

  /**
   * @name toHaveWhitespaceString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveWhitespaceString = function(memberName) {
    return priv.assertMember.call(this, 'toBeWhitespace', memberName);
  };
<commit_msg>JSDoc String Matchers.<commit_after>  // Strings
  // ---------------------------------------------------------------------------

  /**
   * @name toBeString
   *
   * @description
   * Assert subject is a String.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @return {Boolean}
   */
  matchers.toBeString = function() {
    return priv.is(this.actual, 'String');
  };

  /**
   * @name toBeEmptyString
   *
   * @description
   * Assert subject is a String of length 0.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @return {Boolean}
   */
  matchers.toBeEmptyString = function() {
    return this.actual === '';
  };

  /**
   * @name toBeNonEmptyString
   *
   * @description
   * Assert subject is a String with at least 1 character.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @return {Boolean}
   */
  matchers.toBeNonEmptyString = function() {
    return matchers.toBeString.call(this) && this.actual.length > 0;
  };

  /**
   * @name toBeHtmlString
   *
   * @description
   * Assert subject is string containing HTML Markup.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @return {Boolean}
   */
  matchers.toBeHtmlString = function() {
    var container = document.createElement('div');
    container.innerHTML = this.actual;
    return matchers.toBeString.call(this) && priv.some(container.childNodes, function(node) {
      return node.nodeType !== 3;
    });
  };

  /**
   * @name toBeJsonString
   *
   * @description
   * Assert subject is string containing parseable JSON.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @return {Boolean}
   */
  matchers.toBeJsonString = function() {
    var isParseable;
    var json;
    try {
      json = JSON.parse(this.actual);
    } catch (e) {
      isParseable = false;
    }
    return isParseable !== false && json !== null;
  };

  /**
   * @name toBeWhitespace
   *
   * @description
   * Assert subject is a String containing nothing but whitespace.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @return {Boolean}
   */
  matchers.toBeWhitespace = function() {
    return matchers.toBeString.call(this) && this.actual.search(/\S/) === -1;
  };

  /**
   * @name toStartWith
   *
   * @description
   * Assert subject is a String whose first characters match our expected string.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} expected
   * @return {Boolean}
   */
  matchers.toStartWith = function(expected) {
    if (!matchers.toBeNonEmptyString.call(this) || !matchers.toBeNonEmptyString.call({
      actual: expected
    })) {
      return false;
    }
    return this.actual.slice(0, expected.length) === expected;
  };

  /**
   * @name toEndWith
   *
   * @description
   * Assert subject is a String whose last characters match our expected string.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} expected
   * @return {Boolean}
   */
  matchers.toEndWith = function(expected) {
    if (!matchers.toBeNonEmptyString.call(this) || !matchers.toBeNonEmptyString.call({
      actual: expected
    })) {
      return false;
    }
    return this.actual.slice(this.actual.length - expected.length, this.actual.length) === expected;
  };

  /**
   * @name toBeLongerThan
   *
   * @description
   * Assert subject is a String whose length is greater than our other string.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toBeLongerThan = function(other) {
    return matchers.toBeString.call(this) && matchers.toBeString.call({
      actual: other
    }) && this.actual.length > other.length;
  };

  /**
   * @name toBeShorterThan
   *
   * @description
   * Assert subject is a String whose length is greater than our other string.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toBeShorterThan = function(other) {
    return matchers.toBeString.call(this) && matchers.toBeString.call({
      actual: other
    }) && this.actual.length < other.length;
  };

  /**
   * @name toBeSameLengthAs
   *
   * @description
   * Assert subject is a String whose length is equal to our other string.
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toBeSameLengthAs = function(other) {
    return matchers.toBeString.call(this) && matchers.toBeString.call({
      actual: other
    }) && this.actual.length === other.length;
  };

  // String Members
  // ---------------------------------------------------------------------------

  /**
   * @name toHaveEmptyString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveEmptyString = function(memberName) {
    return priv.assertMember.call(this, 'toBeEmptyString', memberName);
  };

  /**
   * @name toHaveHtmlString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveHtmlString = function(memberName) {
    return priv.assertMember.call(this, 'toBeHtmlString', memberName);
  };

  /**
   * @name toHaveJsonString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveJsonString = function(memberName) {
    return priv.assertMember.call(this, 'toBeJsonString', memberName);
  };

  /**
   * @name toHaveNonEmptyString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveNonEmptyString = function(memberName) {
    return priv.assertMember.call(this, 'toBeNonEmptyString', memberName);
  };

  /**
   * @name toHaveString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveString = function(memberName) {
    return priv.assertMember.call(this, 'toBeString', memberName);
  };

  /**
   * @name toHaveStringLongerThan
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toHaveStringLongerThan = function(memberName, other) {
    return priv.assertMember.call(this, 'toBeLongerThan', memberName, other);
  };

  /**
   * @name toHaveStringSameLengthAs
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toHaveStringSameLengthAs = function(memberName, other) {
    return priv.assertMember.call(this, 'toBeSameLengthAs', memberName, other);
  };

  /**
   * @name toHaveStringShorterThan
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @param  {String} other
   * @return {Boolean}
   */
  matchers.toHaveStringShorterThan = function(memberName, other) {
    return priv.assertMember.call(this, 'toBeShorterThan', memberName, other);
  };

  /**
   * @name toHaveWhitespaceString
   *
   * @description
   * .
   *
   * @example
   * See {@link http://git.io/jasmine-string-testing|Unit testing Strings with Jasmine}.
   *
   * @param  {String} memberName
   * @return {Boolean}
   */
  matchers.toHaveWhitespaceString = function(memberName) {
    return priv.assertMember.call(this, 'toBeWhitespace', memberName);
  };
<|endoftext|>