<commit_before>import React                from 'react';
import {
  omit, merge,
  set as timmSet,
  addDefaults,
}                           from 'timm';
import moment               from 'moment';
import {
  bindAll,
  cancelEvent,
  stopPropagation,
}                           from '../gral/helpers';
import {
  COLORS, KEYS,
  IS_IOS,
}                           from '../gral/constants';
import {
  HIDDEN_FOCUS_CAPTURE,
  HIDDEN_FOCUS_CAPTURE_IOS,
  inputReset, INPUT_DISABLED,
}                           from '../gral/styles';
import {
  dateTimeFormat, dateTimeFormatNative,
  dateFormat,
  timeFormat,
  getUtcFlag,
  startOfDefaultDay,
}                           from '../gral/dates';
import { isDate }           from '../gral/validators';
import input                from '../hocs/input';
import {
  floatAdd,
  floatDelete,
  floatUpdate,
}                           from '../components/floats';
import {
  DateTimePicker,
  TRAPPED_KEYS,
}                           from '../inputs/dateTimePicker';
import IosFloatWrapper      from '../inputs/iosFloatWrapper';

// External value: `Date?`
// Internal value: `String` (introduced by the user, copied & pasted, via dropdown...)
// External<->internal conversion uses props, since there are a number of cases
const NULL_VALUE = '';
function toInternalValue(extDate, props) {
  if (extDate == null) return NULL_VALUE;
  const { type, date, time, seconds, utc } = props;
  const mom = moment(extDate);
  if (getUtcFlag(date, time, utc)) mom.utc();
  const fmt = type === 'native'
    ? dateTimeFormatNative(date, time)
    : dateTimeFormat(date, time, seconds);
  return mom.format(fmt);
}
function toExternalValue(str, props) {
  const mom = displayToMoment(str, props);
  return mom !== null ? mom.toDate() : null;
}
function isNull(val) { return val === NULL_VALUE; }

function momentToDisplay(mom, props) {
  if (mom == null) return NULL_VALUE;
  const { type, date, time, seconds } = props;
  const fmt = type === 'native'
    ? dateTimeFormatNative(date, time)
    : dateTimeFormat(date, time, seconds);
  return mom.format(fmt);
}
function displayToMoment(str, props) {
  if (str === NULL_VALUE) return null;
  const { type, date, time, utc } = props;
  const fUtc = getUtcFlag(date, time, utc);
  let mom;
  if (!date) {
    // Add missing date info
    mom = startOfDefaultDay(fUtc);
    mom.add(moment.duration(str));
  } else {
    // Parse string, including seconds (even if props.seconds may be false)
    let fmt;
    if (type === 'native') {
      fmt = undefined; // automatic format detection for native HTML inputs
    } else {
      fmt = date && time
        ? `${dateFormat()} ${timeFormat(true)}`
        : dateFormat();
    }
    const fnMoment = fUtc ? moment.utc : moment;
    mom = fnMoment(str, fmt);
  }
  return mom.isValid() ? mom : null;
}

// ==========================================
// Wrapper
// ==========================================
// -- Props:
// --
// -- * **type** *string(`native` | `onlyField` | `inlinePicker` | `dropDownPicker`)? =
// --   `dropDownPicker`*
// -- * **checkIos** *boolean? = true*: whether Giu should check for iOS in order to
// --   simplify certain components (e.g. do not use analogue time picker)
// -- * **placeholder** *string?*: when unspecified, the expected date/time
// --   format will be used
// -- * **date** *boolean? = true*: whether the date is part of the value
// -- * **time** *boolean?*: whether the time is part of the value
// -- * **analogTime** *boolean? = true*: whether the time picker should be
// --   analogue (traditional clock) or digital (list)
// -- * **seconds** *boolean?*: whether seconds should be included in the time value
// -- * **utc** *boolean?*: by default, it is `true` *unless* `date` and `time` are both `true`.
// --   In other words, local time is only used by default if both `date` and `time` are enabled
// -- * **todayName** *string? = 'Today'*: label for the *Today* button
// -- * **lang** *string?*: the current language. Use it to inform Giu that
// --   you have changed moment's language, so that it updates the string representation of
// --   the current value accordingly and re-renders the component.
// --   Note: **you still must configure moment() yourself**
// -- * **style** *object?*: merged with the `input` style
// -- * **styleOuter** *object?*: when `type === 'inlinePicker'`,
// --   merged with the outermost `span` style
// -- * **accentColor** *string?*: CSS color descriptor (e.g. `darkgray`, `#ccffaa`...)
const DEFAULT_PROPS = {
  type:                   'dropDownPicker',
  checkIos:               true,
  date:                   true,
  time:                   false,
  analogTime:             true,
  seconds:                false,
  todayName:              'Today',
  accentColor:            COLORS.accent,
};

const DateInputWrapper = props0 => {
  let props = addDefaults(props0, DEFAULT_PROPS);
  if (IS_IOS && props.checkIos) {
    props = timmSet(props, 'analogTime', false);
  }
  props = omit(props, ['checkIos']);
  return <DateInput {...props} />;
};

DateInputWrapper.propTypes = {
  type:                     React.PropTypes.oneOf([
    'native',
    'onlyField',
    'inlinePicker',
    'dropDownPicker',
  ]),
  checkIos:              React.PropTypes.bool,
};

// ==========================================
// Component
// ==========================================
class BaseDateInput extends React.Component {
  static propTypes = {
    type:                   React.PropTypes.oneOf([
      'native',
      'onlyField',
      'inlinePicker',
      'dropDownPicker',
    ]),
    disabled:               React.PropTypes.bool,
    placeholder:            React.PropTypes.string,
    date:                   React.PropTypes.bool,
    time:                   React.PropTypes.bool,
    analogTime:             React.PropTypes.bool,
    seconds:                React.PropTypes.bool,
    utc:                    React.PropTypes.bool,
    todayName:              React.PropTypes.string,
    lang:                   React.PropTypes.string,
    floatPosition:          React.PropTypes.string,
    floatAlign:             React.PropTypes.string,
    floatZ:                 React.PropTypes.number,
    style:                  React.PropTypes.object,
    styleOuter:             React.PropTypes.object,
    accentColor:            React.PropTypes.string,
    // From input HOC
    curValue:               React.PropTypes.string.isRequired,
    errors:                 React.PropTypes.array.isRequired,
    registerOuterRef:       React.PropTypes.func.isRequired,
    registerFocusableRef:   React.PropTypes.func.isRequired,
    fFocused:               React.PropTypes.bool.isRequired,
    onFocus:                React.PropTypes.func.isRequired,
    onBlur:                 React.PropTypes.func.isRequired,
    onChange:               React.PropTypes.func.isRequired,
    // all others are passed through unchanged
  };

  constructor(props) {
    super(props);
    this.state = { fFloat: false };
    this.cmdsToPicker = null;
    this.keyDown = undefined;
    this.lastExtValue = toExternalValue(props.curValue, props);
    bindAll(this, [
      'registerInputRef',
      'onMouseDown',
      'onClick',
      'onFocus',
      'onBlur',
      'onKeyDown',
      'onChangePicker',
    ]);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.curValue !== this.props.curValue) {
      this.lastExtValue = toExternalValue(nextProps.curValue, nextProps);
    }
  }

  componentDidUpdate(prevProps) {
    this.renderFloat();
    const { lang, onChange } = this.props;

    // When the external language changes, we must update the internal value (a string)
    // to reflect the new date format
    if (prevProps.lang !== lang && this.lastExtValue != null) {
      onChange(
        null,
        toInternalValue(this.lastExtValue, this.props),
        { fDontFocus: true },
      );
    }
  }
  componentWillUnmount() { floatDelete(this.floatId); }

  // ==========================================
  // Render
  // ==========================================
  render() {
    const { type } = this.props;
    let out;
    if (type === 'native') {
      out = this.renderNativeField();
    } else if (type === 'inlinePicker') {
      out = (
        <span
          className="giu-date-input giu-date-input-inline-picker"
          style={style.outerInline(this.props)}
        >
          {this.renderField(true)}
          {this.renderPicker(true)}
        </span>
      );
    } else {  // 'only-field' || 'dropDownPicker'
      const elField = this.renderField();
      if (IS_IOS && type === 'dropDownPicker') {
        out = (
          <span style={style.wrapperForIos}>
            {elField}
            {this.renderFloatForIos()}
          </span>
        );
      } else {
        out = elField;
      }
    }
    return out;
  }

  renderNativeField() {
    const {
      curValue, onChange, placeholder,
      date, time,
      disabled,
    } = this.props;
    const otherProps = omit(this.props, PROP_KEYS_TO_REMOVE_FROM_INPUT);
    let htmlInputType;
    if (date && time) {
      htmlInputType = 'datetime-local';
    } else if (date) {
      htmlInputType = 'date';
    } else {
      htmlInputType = 'time';
    }
    return (
      <input ref={this.registerInputRef}
        className="giu-date-input"
        type={htmlInputType}
        value={curValue}
        {...otherProps}
        placeholder={placeholder || dateTimeFormatNative(date, time)}
        onFocus={this.onFocus}
        onBlur={this.onBlur}
        onChange={onChange}
        tabIndex={disabled ? -1 : undefined}
        style={style.field(this.props)}
      />
    );
  }

  renderField(fHidden) {
    const {
      curValue, onChange, placeholder,
      date, time, seconds,
      disabled,
      onCopy, onCut, onPaste,
    } = this.props;
    const otherProps = omit(this.props, PROP_KEYS_TO_REMOVE_FROM_INPUT);
    return (
      <input ref={this.registerInputRef}
        className={fHidden ? undefined : 'giu-date-input'}
        type="text"
        value={curValue}
        {...otherProps}
        placeholder={placeholder || dateTimeFormat(date, time, seconds)}
        onFocus={this.onFocus}
        onBlur={this.onBlur}
        onCopy={fHidden ? onCopy : undefined}
        onCut={fHidden ? onCut : undefined}
        onPaste={fHidden ? onPaste : undefined}
        onChange={onChange}
        onKeyDown={this.onKeyDown}
        tabIndex={disabled ? -1 : undefined}
        style={fHidden ? style.fieldHidden : style.field(this.props)}
      />
    );
  }

  renderFloat() {
    if (this.props.type !== 'dropDownPicker') return;
    if (IS_IOS) return;
    const { fFloat } = this.state;

    // Remove float
    if (!fFloat && this.floatId != null) {
      floatDelete(this.floatId);
      this.floatId = null;
      return;
    }

    // Create or update float
    if (fFloat) {
      const { floatZ, floatPosition, floatAlign } = this.props;
      const floatOptions = {
        position: floatPosition,
        align: floatAlign,
        zIndex: floatZ,
        getAnchorNode: () => this.refInput,
        children: this.renderPicker(),
      };
      if (this.floatId == null) {
        this.floatId = floatAdd(floatOptions);
      } else {
        floatUpdate(this.floatId, floatOptions);
      }
    }
  }

  renderFloatForIos() {
    if (!this.state.fFloat) return null;
    const { floatPosition, floatAlign, floatZ } = this.props;
    return (
      <IosFloatWrapper
        floatPosition={floatPosition}
        floatAlign={floatAlign}
        floatZ={floatZ}
      >
        {this.renderPicker(false)}
      </IosFloatWrapper>
    );
  }

  renderPicker(fInline) {
    const {
      type,
      curValue,
      disabled, fFocused,
      date, time, analogTime, seconds, utc,
      todayName,
      accentColor,
    } = this.props;
    const mom = displayToMoment(curValue, this.props);
    const registerOuterRef = type === 'inlinePicker'
      ? this.props.registerOuterRef
      : undefined;
    return (
      <DateTimePicker
        registerOuterRef={registerOuterRef}
        disabled={disabled}
        fFocused={fInline && fFocused}
        curValue={mom}
        onMouseDown={this.onMouseDown}
        onClick={this.onClick}
        onChange={this.onChangePicker}
        date={date}
        time={time}
        analogTime={analogTime}
        seconds={seconds}
        utc={utc}
        todayName={todayName}
        cmds={this.cmdsToPicker}
        keyDown={this.keyDown}
        accentColor={accentColor}
      />
    );
  }

  // ==========================================
  // Handlers
  // ==========================================
  registerInputRef(c) {
    this.refInput = c;
    this.props.registerFocusableRef(c);
  }

  onMouseDown(ev) {
    cancelEvent(ev);
    if (!this.props.fFocused) this.refInput.focus();
  }

  // Cancel bubbling of click events; they may reach Modals
  // on their way up and cause the element to blur.
  // Allow free propagation if the element is disabled.
  onClick(ev) {
    if (!this.props.disabled) stopPropagation(ev);
  }

  onFocus(ev) {
    this.setState({ fFloat: true });
    this.props.onFocus(ev);
  }

  onBlur(ev) {
    this.setState({ fFloat: false });
    this.props.onBlur(ev);
  }

  onKeyDown(ev) {
    const { type } = this.props;
    if (type === 'onlyField') return;
    const { which } = ev;
    const { fFloat } = this.state;
    if (which === KEYS.esc) {
      cancelEvent(ev);
      this.setState({ fFloat: !fFloat });
      this.keyDown = undefined;
      return;
    }

    if ((fFloat || type === 'inlinePicker') &&
        TRAPPED_KEYS.indexOf(which) >= 0) {
      cancelEvent(ev);
      const { keyCode, metaKey, shiftKey, altKey, ctrlKey } = ev;
      this.keyDown = { which, keyCode, metaKey, shiftKey, altKey, ctrlKey };
      this.forceUpdate();
    }
  }

  onChangePicker(ev, nextValue) {
    this.props.onChange(ev, momentToDisplay(nextValue, this.props));
  }
}

// ==========================================
// Styles
// ==========================================
const style = {
  outerInline: ({ styleOuter }) => {
    let out = styleOuter;
    if (IS_IOS) out = timmSet(out, 'position', 'relative');
    return out;
  },
  fieldBase: inputReset(),
  field: ({ style: styleField, disabled }) => {
    let out = style.fieldBase;
    if (disabled) out = merge(out, INPUT_DISABLED);
    out = merge(out, styleField);
    return out;
  },
  fieldHidden: IS_IOS ? HIDDEN_FOCUS_CAPTURE_IOS : HIDDEN_FOCUS_CAPTURE,
  wrapperForIos: { position: 'relative' },
};

// ==========================================
// Miscellaneous
// ==========================================
const PROP_KEYS_TO_REMOVE_FROM_INPUT = Object.keys(BaseDateInput.propTypes).concat([
  'cmds', 'keyDown', 'onResizeOuter',
]);

// ==========================================
// Public API
// ==========================================
const DateInput = input(BaseDateInput, {
  toInternalValue, toExternalValue, isNull,
  defaultValidators: { isDate: isDate() },
  validatorContext: { moment },
  fIncludeClipboardProps: true,
});

export default DateInputWrapper;
<commit_msg>Minor (docs)<commit_after>import React                from 'react';
import {
  omit, merge,
  set as timmSet,
  addDefaults,
}                           from 'timm';
import moment               from 'moment';
import {
  bindAll,
  cancelEvent,
  stopPropagation,
}                           from '../gral/helpers';
import {
  COLORS, KEYS,
  IS_IOS,
}                           from '../gral/constants';
import {
  HIDDEN_FOCUS_CAPTURE,
  HIDDEN_FOCUS_CAPTURE_IOS,
  inputReset, INPUT_DISABLED,
}                           from '../gral/styles';
import {
  dateTimeFormat, dateTimeFormatNative,
  dateFormat,
  timeFormat,
  getUtcFlag,
  startOfDefaultDay,
}                           from '../gral/dates';
import { isDate }           from '../gral/validators';
import input                from '../hocs/input';
import {
  floatAdd,
  floatDelete,
  floatUpdate,
}                           from '../components/floats';
import {
  DateTimePicker,
  TRAPPED_KEYS,
}                           from '../inputs/dateTimePicker';
import IosFloatWrapper      from '../inputs/iosFloatWrapper';

// External value: `Date?`
// Internal value: `String` (introduced by the user, copied & pasted, via dropdown...)
// External<->internal conversion uses props, since there are a number of cases
const NULL_VALUE = '';
function toInternalValue(extDate, props) {
  if (extDate == null) return NULL_VALUE;
  const { type, date, time, seconds, utc } = props;
  const mom = moment(extDate);
  if (getUtcFlag(date, time, utc)) mom.utc();
  const fmt = type === 'native'
    ? dateTimeFormatNative(date, time)
    : dateTimeFormat(date, time, seconds);
  return mom.format(fmt);
}
function toExternalValue(str, props) {
  const mom = displayToMoment(str, props);
  return mom !== null ? mom.toDate() : null;
}
function isNull(val) { return val === NULL_VALUE; }

function momentToDisplay(mom, props) {
  if (mom == null) return NULL_VALUE;
  const { type, date, time, seconds } = props;
  const fmt = type === 'native'
    ? dateTimeFormatNative(date, time)
    : dateTimeFormat(date, time, seconds);
  return mom.format(fmt);
}
function displayToMoment(str, props) {
  if (str === NULL_VALUE) return null;
  const { type, date, time, utc } = props;
  const fUtc = getUtcFlag(date, time, utc);
  let mom;
  if (!date) {
    // Add missing date info
    mom = startOfDefaultDay(fUtc);
    mom.add(moment.duration(str));
  } else {
    // Parse string, including seconds (even if props.seconds may be false)
    let fmt;
    if (type === 'native') {
      fmt = undefined; // automatic format detection for native HTML inputs
    } else {
      fmt = date && time
        ? `${dateFormat()} ${timeFormat(true)}`
        : dateFormat();
    }
    const fnMoment = fUtc ? moment.utc : moment;
    mom = fnMoment(str, fmt);
  }
  return mom.isValid() ? mom : null;
}

// ==========================================
// Wrapper
// ==========================================
// -- Props:
// --
// -- * **type** *string(`native` | `onlyField` | `inlinePicker` | `dropDownPicker`)? =
// --   `dropDownPicker`*
// -- * **checkIos** *boolean? = true*: whether Giu should check for iOS in order to
// --   simplify certain components (e.g. do not use analogue time picker)
// -- * **placeholder** *string?*: when unspecified, the expected date/time
// --   format will be used
// -- * **date** *boolean? = true*: whether the date is part of the value
// -- * **time** *boolean?*: whether the time is part of the value
// -- * **analogTime** *boolean? = true*: whether the time picker should be
// --   analogue (traditional clock) or digital (list)
// -- * **seconds** *boolean?*: whether seconds should be included in the time value
// -- * **utc** *boolean?*: by default, it is `true` *unless* `date` and `time` are both `true`.
// --   In other words, local time is only used by default if both `date` and `time` are enabled
// -- * **todayName** *string? = 'Today'*: label for the *Today* button
// -- * **lang** *string?*: current language (NB: just used to make sure the component is
// --   refreshed). Use it to inform Giu that you have changed `moment`'s language.
// -- * **style** *object?*: merged with the `input` style
// -- * **styleOuter** *object?*: when `type === 'inlinePicker'`,
// --   merged with the outermost `span` style
// -- * **accentColor** *string?*: CSS color descriptor (e.g. `darkgray`, `#ccffaa`...)
const DEFAULT_PROPS = {
  type:                   'dropDownPicker',
  checkIos:               true,
  date:                   true,
  time:                   false,
  analogTime:             true,
  seconds:                false,
  todayName:              'Today',
  accentColor:            COLORS.accent,
};

const DateInputWrapper = props0 => {
  let props = addDefaults(props0, DEFAULT_PROPS);
  if (IS_IOS && props.checkIos) {
    props = timmSet(props, 'analogTime', false);
  }
  props = omit(props, ['checkIos']);
  return <DateInput {...props} />;
};

DateInputWrapper.propTypes = {
  type:                     React.PropTypes.oneOf([
    'native',
    'onlyField',
    'inlinePicker',
    'dropDownPicker',
  ]),
  checkIos:              React.PropTypes.bool,
};

// ==========================================
// Component
// ==========================================
class BaseDateInput extends React.Component {
  static propTypes = {
    type:                   React.PropTypes.oneOf([
      'native',
      'onlyField',
      'inlinePicker',
      'dropDownPicker',
    ]),
    disabled:               React.PropTypes.bool,
    placeholder:            React.PropTypes.string,
    date:                   React.PropTypes.bool,
    time:                   React.PropTypes.bool,
    analogTime:             React.PropTypes.bool,
    seconds:                React.PropTypes.bool,
    utc:                    React.PropTypes.bool,
    todayName:              React.PropTypes.string,
    lang:                   React.PropTypes.string,
    floatPosition:          React.PropTypes.string,
    floatAlign:             React.PropTypes.string,
    floatZ:                 React.PropTypes.number,
    style:                  React.PropTypes.object,
    styleOuter:             React.PropTypes.object,
    accentColor:            React.PropTypes.string,
    // From input HOC
    curValue:               React.PropTypes.string.isRequired,
    errors:                 React.PropTypes.array.isRequired,
    registerOuterRef:       React.PropTypes.func.isRequired,
    registerFocusableRef:   React.PropTypes.func.isRequired,
    fFocused:               React.PropTypes.bool.isRequired,
    onFocus:                React.PropTypes.func.isRequired,
    onBlur:                 React.PropTypes.func.isRequired,
    onChange:               React.PropTypes.func.isRequired,
    // all others are passed through unchanged
  };

  constructor(props) {
    super(props);
    this.state = { fFloat: false };
    this.cmdsToPicker = null;
    this.keyDown = undefined;
    this.lastExtValue = toExternalValue(props.curValue, props);
    bindAll(this, [
      'registerInputRef',
      'onMouseDown',
      'onClick',
      'onFocus',
      'onBlur',
      'onKeyDown',
      'onChangePicker',
    ]);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.curValue !== this.props.curValue) {
      this.lastExtValue = toExternalValue(nextProps.curValue, nextProps);
    }
  }

  componentDidUpdate(prevProps) {
    this.renderFloat();
    const { lang, onChange } = this.props;

    // When the external language changes, we must update the internal value (a string)
    // to reflect the new date format
    if (prevProps.lang !== lang && this.lastExtValue != null) {
      onChange(
        null,
        toInternalValue(this.lastExtValue, this.props),
        { fDontFocus: true },
      );
    }
  }
  componentWillUnmount() { floatDelete(this.floatId); }

  // ==========================================
  // Render
  // ==========================================
  render() {
    const { type } = this.props;
    let out;
    if (type === 'native') {
      out = this.renderNativeField();
    } else if (type === 'inlinePicker') {
      out = (
        <span
          className="giu-date-input giu-date-input-inline-picker"
          style={style.outerInline(this.props)}
        >
          {this.renderField(true)}
          {this.renderPicker(true)}
        </span>
      );
    } else {  // 'only-field' || 'dropDownPicker'
      const elField = this.renderField();
      if (IS_IOS && type === 'dropDownPicker') {
        out = (
          <span style={style.wrapperForIos}>
            {elField}
            {this.renderFloatForIos()}
          </span>
        );
      } else {
        out = elField;
      }
    }
    return out;
  }

  renderNativeField() {
    const {
      curValue, onChange, placeholder,
      date, time,
      disabled,
    } = this.props;
    const otherProps = omit(this.props, PROP_KEYS_TO_REMOVE_FROM_INPUT);
    let htmlInputType;
    if (date && time) {
      htmlInputType = 'datetime-local';
    } else if (date) {
      htmlInputType = 'date';
    } else {
      htmlInputType = 'time';
    }
    return (
      <input ref={this.registerInputRef}
        className="giu-date-input"
        type={htmlInputType}
        value={curValue}
        {...otherProps}
        placeholder={placeholder || dateTimeFormatNative(date, time)}
        onFocus={this.onFocus}
        onBlur={this.onBlur}
        onChange={onChange}
        tabIndex={disabled ? -1 : undefined}
        style={style.field(this.props)}
      />
    );
  }

  renderField(fHidden) {
    const {
      curValue, onChange, placeholder,
      date, time, seconds,
      disabled,
      onCopy, onCut, onPaste,
    } = this.props;
    const otherProps = omit(this.props, PROP_KEYS_TO_REMOVE_FROM_INPUT);
    return (
      <input ref={this.registerInputRef}
        className={fHidden ? undefined : 'giu-date-input'}
        type="text"
        value={curValue}
        {...otherProps}
        placeholder={placeholder || dateTimeFormat(date, time, seconds)}
        onFocus={this.onFocus}
        onBlur={this.onBlur}
        onCopy={fHidden ? onCopy : undefined}
        onCut={fHidden ? onCut : undefined}
        onPaste={fHidden ? onPaste : undefined}
        onChange={onChange}
        onKeyDown={this.onKeyDown}
        tabIndex={disabled ? -1 : undefined}
        style={fHidden ? style.fieldHidden : style.field(this.props)}
      />
    );
  }

  renderFloat() {
    if (this.props.type !== 'dropDownPicker') return;
    if (IS_IOS) return;
    const { fFloat } = this.state;

    // Remove float
    if (!fFloat && this.floatId != null) {
      floatDelete(this.floatId);
      this.floatId = null;
      return;
    }

    // Create or update float
    if (fFloat) {
      const { floatZ, floatPosition, floatAlign } = this.props;
      const floatOptions = {
        position: floatPosition,
        align: floatAlign,
        zIndex: floatZ,
        getAnchorNode: () => this.refInput,
        children: this.renderPicker(),
      };
      if (this.floatId == null) {
        this.floatId = floatAdd(floatOptions);
      } else {
        floatUpdate(this.floatId, floatOptions);
      }
    }
  }

  renderFloatForIos() {
    if (!this.state.fFloat) return null;
    const { floatPosition, floatAlign, floatZ } = this.props;
    return (
      <IosFloatWrapper
        floatPosition={floatPosition}
        floatAlign={floatAlign}
        floatZ={floatZ}
      >
        {this.renderPicker(false)}
      </IosFloatWrapper>
    );
  }

  renderPicker(fInline) {
    const {
      type,
      curValue,
      disabled, fFocused,
      date, time, analogTime, seconds, utc,
      todayName,
      accentColor,
    } = this.props;
    const mom = displayToMoment(curValue, this.props);
    const registerOuterRef = type === 'inlinePicker'
      ? this.props.registerOuterRef
      : undefined;
    return (
      <DateTimePicker
        registerOuterRef={registerOuterRef}
        disabled={disabled}
        fFocused={fInline && fFocused}
        curValue={mom}
        onMouseDown={this.onMouseDown}
        onClick={this.onClick}
        onChange={this.onChangePicker}
        date={date}
        time={time}
        analogTime={analogTime}
        seconds={seconds}
        utc={utc}
        todayName={todayName}
        cmds={this.cmdsToPicker}
        keyDown={this.keyDown}
        accentColor={accentColor}
      />
    );
  }

  // ==========================================
  // Handlers
  // ==========================================
  registerInputRef(c) {
    this.refInput = c;
    this.props.registerFocusableRef(c);
  }

  onMouseDown(ev) {
    cancelEvent(ev);
    if (!this.props.fFocused) this.refInput.focus();
  }

  // Cancel bubbling of click events; they may reach Modals
  // on their way up and cause the element to blur.
  // Allow free propagation if the element is disabled.
  onClick(ev) {
    if (!this.props.disabled) stopPropagation(ev);
  }

  onFocus(ev) {
    this.setState({ fFloat: true });
    this.props.onFocus(ev);
  }

  onBlur(ev) {
    this.setState({ fFloat: false });
    this.props.onBlur(ev);
  }

  onKeyDown(ev) {
    const { type } = this.props;
    if (type === 'onlyField') return;
    const { which } = ev;
    const { fFloat } = this.state;
    if (which === KEYS.esc) {
      cancelEvent(ev);
      this.setState({ fFloat: !fFloat });
      this.keyDown = undefined;
      return;
    }

    if ((fFloat || type === 'inlinePicker') &&
        TRAPPED_KEYS.indexOf(which) >= 0) {
      cancelEvent(ev);
      const { keyCode, metaKey, shiftKey, altKey, ctrlKey } = ev;
      this.keyDown = { which, keyCode, metaKey, shiftKey, altKey, ctrlKey };
      this.forceUpdate();
    }
  }

  onChangePicker(ev, nextValue) {
    this.props.onChange(ev, momentToDisplay(nextValue, this.props));
  }
}

// ==========================================
// Styles
// ==========================================
const style = {
  outerInline: ({ styleOuter }) => {
    let out = styleOuter;
    if (IS_IOS) out = timmSet(out, 'position', 'relative');
    return out;
  },
  fieldBase: inputReset(),
  field: ({ style: styleField, disabled }) => {
    let out = style.fieldBase;
    if (disabled) out = merge(out, INPUT_DISABLED);
    out = merge(out, styleField);
    return out;
  },
  fieldHidden: IS_IOS ? HIDDEN_FOCUS_CAPTURE_IOS : HIDDEN_FOCUS_CAPTURE,
  wrapperForIos: { position: 'relative' },
};

// ==========================================
// Miscellaneous
// ==========================================
const PROP_KEYS_TO_REMOVE_FROM_INPUT = Object.keys(BaseDateInput.propTypes).concat([
  'cmds', 'keyDown', 'onResizeOuter',
]);

// ==========================================
// Public API
// ==========================================
const DateInput = input(BaseDateInput, {
  toInternalValue, toExternalValue, isNull,
  defaultValidators: { isDate: isDate() },
  validatorContext: { moment },
  fIncludeClipboardProps: true,
});

export default DateInputWrapper;
<|endoftext|>


<commit_before>/* ======= Instance Methods ======= */

/**
 * Logs a Message
 * @param {String} msg
 */
Moon.prototype.log = function(msg) {
  if(!Moon.config.silent) console.log(msg);
}

/**
 * Throws an Error
 * @param {String} msg
 */
Moon.prototype.error = function(msg) {
  console.error("[Moon] ERR: " + msg);
}

/**
 * Gets Value in Data
 * @param {String} key
 * @return {String} Value of key in data
 */
Moon.prototype.get = function(key) {
  return this.$data[key];
}

/**
 * Sets Value in Data
 * @param {String} key
 * @param {String} val
 */
Moon.prototype.set = function(key, val) {
  this.$data[key] = val;
  if(!this.$destroyed) this.build();
  this.$hooks.updated();
}

/**
 * Destroys Moon Instance
 */
Moon.prototype.destroy = function() {
  Object.defineProperty(this, '$data', {
    set: function(value) {
      _data = value;
    }
  });
  this.removeEvents();
  this.$destroyed = true;
  this.$hooks.destroyed();
}

/**
 * Calls a method
 * @param {String} method
 */
Moon.prototype.callMethod = function(method, args) {
  args = args || [];
  this.$methods[method].apply(this, args);
}

// Event Emitter, adapted from https://github.com/KingPixil/voke

/**
 * Attaches an Event Listener
 * @param {String} eventName
 * @param {Function} action
 */
Moon.prototype.on = function(eventName, action) {
  if(this.$events[eventName]) {
    this.$events[eventName].push(action);
  } else {
    this.$events[eventName] = [action];
  }
}

/**
 * Removes an Event Listener
 * @param {String} eventName
 * @param {Function} action
 */
Moon.prototype.off = function(eventName, action) {
  var index = this.$events[eventName].indexOf(action);
  if(index !== -1) {
    this.$events[eventName].splice(index, 1);
  }
}

/**
 * Removes All Event Listeners
 * @param {String} eventName
 * @param {Function} action
 */
Moon.prototype.removeEvents = function() {
  for(var evt in this.$events) {
    this.$events[evt] = [];
  }
}

/**
 * Emits an Event
 * @param {String} eventName
 * @param {Object} meta
 */
Moon.prototype.emit = function(eventName, meta) {
  meta = meta || {};
  meta.type = eventName;

  if(this.$events["*"]) {
    for(var i = 0; i < this.$events["*"].length; i++) {
      var globalHandler = this.$events["*"][i];
      globalHandler(meta);
    }
  }

  for(var i = 0; i < this.$events[eventName].length; i++) {
    var handler = this.$events[eventName][i];
    handler(meta);
  }
}

/**
 * Mounts Moon Element
 * @param {Node} el
 */
Moon.prototype.mount = function(el) {
  this.$el = document.querySelector(el);

  if(!this.$el) {
    this.error("Element " + this.$opts.el + " not found");
  }

  this.$template = this.$opts.template || this.$el.innerHTML;

  this.$el.innerHTML = this.$template;

  if(this.$render === noop) {
    this.$render = createRender(this.$template);
  }

  this.build();
  this.$hooks.mounted();
}

/**
 * Renders Virtual DOM
 * @return Virtual DOM
 */
Moon.prototype.render = function() {

}

/**
 * Diff then Patches Nodes With Data
 */
Moon.prototype.patch = function() {

}

/**
 * Render and Patches the DOM With Data
 */
Moon.prototype.build = function() {
  this.$dom = this.render();
  this.buildNodes(this.$dom, this.$el);
}

/**
 * Initializes Moon
 */
Moon.prototype.init = function() {
  this.log("======= Moon =======");
  this.$hooks.created();

  if(this.$opts.el) {
    this.mount(this.$opts.el);
  }
}
<commit_msg>call render<commit_after>/* ======= Instance Methods ======= */

/**
 * Logs a Message
 * @param {String} msg
 */
Moon.prototype.log = function(msg) {
  if(!Moon.config.silent) console.log(msg);
}

/**
 * Throws an Error
 * @param {String} msg
 */
Moon.prototype.error = function(msg) {
  console.error("[Moon] ERR: " + msg);
}

/**
 * Gets Value in Data
 * @param {String} key
 * @return {String} Value of key in data
 */
Moon.prototype.get = function(key) {
  return this.$data[key];
}

/**
 * Sets Value in Data
 * @param {String} key
 * @param {String} val
 */
Moon.prototype.set = function(key, val) {
  this.$data[key] = val;
  if(!this.$destroyed) this.build();
  this.$hooks.updated();
}

/**
 * Destroys Moon Instance
 */
Moon.prototype.destroy = function() {
  Object.defineProperty(this, '$data', {
    set: function(value) {
      _data = value;
    }
  });
  this.removeEvents();
  this.$destroyed = true;
  this.$hooks.destroyed();
}

/**
 * Calls a method
 * @param {String} method
 */
Moon.prototype.callMethod = function(method, args) {
  args = args || [];
  this.$methods[method].apply(this, args);
}

// Event Emitter, adapted from https://github.com/KingPixil/voke

/**
 * Attaches an Event Listener
 * @param {String} eventName
 * @param {Function} action
 */
Moon.prototype.on = function(eventName, action) {
  if(this.$events[eventName]) {
    this.$events[eventName].push(action);
  } else {
    this.$events[eventName] = [action];
  }
}

/**
 * Removes an Event Listener
 * @param {String} eventName
 * @param {Function} action
 */
Moon.prototype.off = function(eventName, action) {
  var index = this.$events[eventName].indexOf(action);
  if(index !== -1) {
    this.$events[eventName].splice(index, 1);
  }
}

/**
 * Removes All Event Listeners
 * @param {String} eventName
 * @param {Function} action
 */
Moon.prototype.removeEvents = function() {
  for(var evt in this.$events) {
    this.$events[evt] = [];
  }
}

/**
 * Emits an Event
 * @param {String} eventName
 * @param {Object} meta
 */
Moon.prototype.emit = function(eventName, meta) {
  meta = meta || {};
  meta.type = eventName;

  if(this.$events["*"]) {
    for(var i = 0; i < this.$events["*"].length; i++) {
      var globalHandler = this.$events["*"][i];
      globalHandler(meta);
    }
  }

  for(var i = 0; i < this.$events[eventName].length; i++) {
    var handler = this.$events[eventName][i];
    handler(meta);
  }
}

/**
 * Mounts Moon Element
 * @param {Node} el
 */
Moon.prototype.mount = function(el) {
  this.$el = document.querySelector(el);

  if(!this.$el) {
    this.error("Element " + this.$opts.el + " not found");
  }

  this.$template = this.$opts.template || this.$el.innerHTML;

  this.$el.innerHTML = this.$template;

  if(this.$render === noop) {
    this.$render = createRender(this.$template);
  }

  this.build();
  this.$hooks.mounted();
}

/**
 * Renders Virtual DOM
 * @return Virtual DOM
 */
Moon.prototype.render = function() {
  return this.$render(h);
}

/**
 * Diff then Patches Nodes With Data
 */
Moon.prototype.patch = function() {

}

/**
 * Render and Patches the DOM With Data
 */
Moon.prototype.build = function() {
  this.$dom = this.render();
  this.buildNodes(this.$dom, this.$el);
}

/**
 * Initializes Moon
 */
Moon.prototype.init = function() {
  this.log("======= Moon =======");
  this.$hooks.created();

  if(this.$opts.el) {
    this.mount(this.$opts.el);
  }
}
<|endoftext|>


<commit_before>$(document).ready(function() {

 	$(".login_btn").on("click", function(event){
 			event.preventDefault();
 			console.log("YOU CLICKED")
 	});

 	$(".signup_btn").on("click", function(event){
 			event.preventDefault();
 			console.log("YOU CLICKED")
 	});

});
<commit_msg>Add toggle functionality to auth btns<commit_after>$(document).ready(function() {

 	$(".login_btn").on("click", function(event){
 			event.preventDefault();
 			console.log("YOU CLICKED");
 			$(".login_form").toggle();
 	});

 	$(".signup_btn").on("click", function(event){
 			event.preventDefault();
 			console.log("YOU CLICKED");
 			$(".signup_form").toggle();
 	});

});
<|endoftext|>


<commit_before>$(document).ready(function() {

  //AJAX call for initial search with search bar
  $('#search').on('submit', function(event) {
    event.preventDefault();
    searchSetup();
    var $target = $(event.target);
    $.ajax({
      url: $target.attr('action'),
      type: 'POST',
      data: $target.serialize(),
      dataType: "JSON"
    }).done(function(response) {
      content = response
      $("#initial").remove();
      $("#searchWrapper img").hide();
      $("#search").show();
      $("#likeButtons").show();
      $("#content").show();
      $content = $("#content iframe")
      contentNum = 0;
      if (content[contentNum].type === "video") {
        $content.attr("src", youtubeId(content[contentNum].url))
        $content.attr("contentId", content[contentNum].id)
      } else {
        $content.attr("src", content[contentNum].url)
        $content.attr("contentId", content[contentNum].id)
      }
      twitterButton();
    })
  })

  //AJAX call for initial search with thumbnail images
  $('.thumbnailSearch').on('click', function(event) {
    event.preventDefault();
    searchSetup();
    var $target = $(event.target);
    $.ajax({
      url: '/search/' + $target.attr('categoryId'),
      type: 'GET',
      dataType: "JSON"
    }).done(function(response) {
      content = response
      $("#initial").hide();
      $("#searchWrapper img").hide();
      $("#search").show();
      $("#likeButtons").show();
      $("#content").show();
      $content = $("#content iframe")
      contentNum = 0;
      if (content[contentNum].type === "video") {
        $content.attr("src", youtubeId(content[contentNum].url))
        $content.attr("contentId", content[contentNum].id)
      } else {
        $content.attr("src", content[contentNum].url)
        $content.attr("contentId", content[contentNum].id)
      }
      twitterButton();
    })
  })

  //AJAX call to update likeCount of content and create Like
  $("#like").on('click', function(event) {
    event.preventDefault();
    $.ajax({
      url: '/' + content[contentNum].type + '/' + content[contentNum].id,
      type: 'POST'
    }).done(function(response) {
      console.log(response)
      if (response === "exists") {
        var n = noty({
          text: 'Like Already Exists!',
          type: 'warning',
          layout: 'topCenter',
          theme: 'relax',
          timeout: 1000
        });
      } else {
        var n = noty({
          text: 'Like Successful!',
          type: 'success',
          layout: 'topCenter',
          theme: 'relax',
          timeout: 1000
        });
      }
    }).fail(function(response) {
      var n = noty({
        text: 'Like Failed!',
        type: 'error',
        layout: 'topCenter',
        theme: 'relax',
        timeout: 1000
      });
    });
  })

  // Javascript used to go to the next piece of content
  $("#next").on('click', function(event) {
    event.preventDefault();
    if (content[++contentNum].type === "video") {
      $("#content iframe").attr("src", youtubeId(content[contentNum].url))
    } else {
      $("#content iframe").attr("src", content[contentNum].url)
    }
    twitterButton();
  })

  // Javascript used to go to go back to the previous piece of content
  $("#back").on('click', function(event) {
    event.preventDefault();
    if (content[--contentNum].type === "video") {
      $("#content iframe").attr("src", youtubeId(content[contentNum].url))
    } else {
      $("#content iframe").attr("src", content[contentNum].url)
    }
    twitterButton();
  })

  // JavaScript used to toggle between show and edit partials on profile page
  $("#editButton").on('click', function(event) {
    var $target = $(event.target);
    $target.text($target.text() === "Edit List" ? "Show List" : "Edit List");
    $("#showListDiv").toggle();
    $("#editListDiv").toggle();
  });

  //AJAX call to delete videos and articles from posts
  $("#editList").on('submit', function(event) {
    event.preventDefault();
    var $target = $(event.target);
    $.ajax({
      url: $target.attr('action'),
      type: 'DELETE',
      data: $target.serialize()
    }).done(function(response) {
      var n = noty({
        text: 'List Update Successful!',
        type: 'success',
        layout: 'topCenter',
        theme: 'relax',
        timeout: 1000
      });
      $("input:checked").each(function() {
        $("a[href='" + $(this).prev().attr('href') + "']").remove();
        $(this).remove();
      });
    }).fail(function(response) {
      var n = noty({
        text: 'List Update Failed!',
        type: 'error',
        layout: 'topCenter',
        theme: 'relax',
        timeout: 1000
      });
    });
  })

});

function youtubeId(url) {
  var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
  var match = url.match(regExp);

  if (match && match[2].length == 11) {
    return "//www.youtube.com/embed/" + match[2];
  }
}

function httpConvert(url) {
  return url = url.replace(/^http:\/\//, 'https://')
}

function twitterButton() {
  $("#twitterDiv").empty();
  var $clone = $(".twitter-share-button-template").clone();
  $clone.show();
  $clone.attr("data-url", httpConvert(content[contentNum].url));
  $clone.attr("class", "twitter-share-button");
  $("#twitterDiv").append($clone);
  $.getScript("http://platform.twitter.com/widgets.js");
}

function searchSetup() {
  $("#search").hide();
  $("#initial").show();
  $(".homepage").hide();
  $("#searchWrapper img").show();
}<commit_msg>Created searchFinish function to dry up initial search AJAX calls<commit_after>$(document).ready(function() {

  //AJAX call for initial search with search bar
  $('#search').on('submit', function(event) {
    event.preventDefault();
    searchSetup();
    var $target = $(event.target);
    $.ajax({
      url: $target.attr('action'),
      type: 'POST',
      data: $target.serialize(),
      dataType: "JSON"
    }).done(function(response) {
      searchFinish(response);
      twitterButton();
    })
  })

  //AJAX call for initial search with thumbnail images
  $('.thumbnailSearch').on('click', function(event) {
    event.preventDefault();
    searchSetup();
    var $target = $(event.target);
    $.ajax({
      url: '/search/' + $target.attr('categoryId'),
      type: 'GET',
      dataType: "JSON"
    }).done(function(response) {
      searchFinish(response);
      twitterButton();
    })
  })

  //AJAX call to update likeCount of content and create Like
  $("#like").on('click', function(event) {
    event.preventDefault();
    $.ajax({
      url: '/' + content[contentNum].type + '/' + content[contentNum].id,
      type: 'POST'
    }).done(function(response) {
      console.log(response)
      if (response === "exists") {
        var n = noty({
          text: 'Like Already Exists!',
          type: 'warning',
          layout: 'topCenter',
          theme: 'relax',
          timeout: 1000
        });
      } else {
        var n = noty({
          text: 'Like Successful!',
          type: 'success',
          layout: 'topCenter',
          theme: 'relax',
          timeout: 1000
        });
      }
    }).fail(function(response) {
      var n = noty({
        text: 'Like Failed!',
        type: 'error',
        layout: 'topCenter',
        theme: 'relax',
        timeout: 1000
      });
    });
  })

  // Javascript used to go to the next piece of content
  $("#next").on('click', function(event) {
    event.preventDefault();
    if (content[++contentNum].type === "video") {
      $("#content iframe").attr("src", youtubeId(content[contentNum].url))
    } else {
      $("#content iframe").attr("src", content[contentNum].url)
    }
    twitterButton();
  })

  // Javascript used to go to go back to the previous piece of content
  $("#back").on('click', function(event) {
    event.preventDefault();
    if (content[--contentNum].type === "video") {
      $("#content iframe").attr("src", youtubeId(content[contentNum].url))
    } else {
      $("#content iframe").attr("src", content[contentNum].url)
    }
    twitterButton();
  })

  // JavaScript used to toggle between show and edit partials on profile page
  $("#editButton").on('click', function(event) {
    var $target = $(event.target);
    $target.text($target.text() === "Edit List" ? "Show List" : "Edit List");
    $("#showListDiv").toggle();
    $("#editListDiv").toggle();
  });

  //AJAX call to delete videos and articles from posts
  $("#editList").on('submit', function(event) {
    event.preventDefault();
    var $target = $(event.target);
    $.ajax({
      url: $target.attr('action'),
      type: 'DELETE',
      data: $target.serialize()
    }).done(function(response) {
      var n = noty({
        text: 'List Update Successful!',
        type: 'success',
        layout: 'topCenter',
        theme: 'relax',
        timeout: 1000
      });
      $("input:checked").each(function() {
        $("a[href='" + $(this).prev().attr('href') + "']").remove();
        $(this).remove();
      });
    }).fail(function(response) {
      var n = noty({
        text: 'List Update Failed!',
        type: 'error',
        layout: 'topCenter',
        theme: 'relax',
        timeout: 1000
      });
    });
  })

});

function youtubeId(url) {
  var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
  var match = url.match(regExp);

  if (match && match[2].length == 11) {
    return "//www.youtube.com/embed/" + match[2];
  }
}

function httpConvert(url) {
  return url = url.replace(/^http:\/\//, 'https://')
}

function twitterButton() {
  $("#twitterDiv").empty();
  var $clone = $(".twitter-share-button-template").clone();
  $clone.show();
  $clone.attr("data-url", httpConvert(content[contentNum].url));
  $clone.attr("class", "twitter-share-button");
  $("#twitterDiv").append($clone);
  $.getScript("http://platform.twitter.com/widgets.js");
}

function searchSetup() {
  $("#search").hide();
  $("#initial").show();
  $(".homepage").hide();
  $("#searchWrapper img").show();
}

function searchFinish(response) {
  content = response
  $("#initial").remove();
  $("#searchWrapper img").hide();
  $("#search").show();
  $("#likeButtons").show();
  $("#content").show();
  $content = $("#content iframe")
  contentNum = 0;
  if (content[contentNum].type === "video") {
    $content.attr("src", youtubeId(content[contentNum].url))
    $content.attr("contentId", content[contentNum].id)
  } else {
    $content.attr("src", content[contentNum].url)
    $content.attr("contentId", content[contentNum].id)
  }
}<|endoftext|>


<commit_before>/*jshint strict: false */
/*globals $, analytics, jsbin, documentTitle, $document, throttle, editors*/

var saving = {
  todo: {
    html: false,
    css: false,
    javascript: false
  },
  _inprogress: false,
  inprogress: function (inprogress) {
    if (typeof inprogress === 'undefined') {
      return saving._inprogress;
    }

    saving._inprogress = inprogress;
    if (inprogress === false) {
      var panels = ['html','css','javascript', 'jasmine'];

      var save = function () {
        var todo = panels.pop();
        if (todo && saving.todo[todo]) {
          saving._inprogress = true;
          updateCode(todo, save);
          saving.todo[todo] = false;
        } else if (todo) {
          save();
        }
      };

      save();
    }
  }
};

// to allow for download button to be introduced via beta feature
$('a.save').click(function (event) {
  event.preventDefault();

  analytics.milestone();
  // if save is disabled, hitting save will trigger a reload
  var ajax = true;
  if (jsbin.saveDisabled === true) {
    ajax = false;
  }
  saveCode('save', ajax);

  return false;
});

var $shareLinks = $('#share .link');
var $panelCheckboxes = $('#sharemenu #sharepanels input');

// TODO remove split when live
var split = $('#sharemenu .share-split').length;

// TODO candidate for removal
function updateSavedState() {
  'use strict';
  if (split) {
    return;
  }

  var mapping = {
    live: 'output',
    javascript: 'js',
    css: 'css',
    html: 'html',
    console: 'console'
  };

  var withRevision = true;

  var query = $panelCheckboxes.filter(':checked').map(function () {
    return mapping[this.getAttribute('data-panel')];
  }).get().join(',');
  $shareLinks.each(function () {
    var path = this.getAttribute('data-path');
    var url = jsbin.getURL({ withRevision: withRevision }) + path + (query && this.id !== 'livepreview' ? '?' + query : ''),
        nodeName = this.nodeName;
    var hash = panels.getHighlightLines();

    if (hash) {
      hash = '#' + hash;
    }

    if (nodeName === 'A') {
      this.href = url;
    } else if (nodeName === 'INPUT') {
      this.value = url;
      if (path === '/edit') {
        this.value += hash;
      }
    } else if (nodeName === 'TEXTAREA') {
      this.value = ('' + documentTitle + '<' + 'script src="' + jsbin.static + '/js/chrome/embed.js"><' + '/script>').replace(/<>"&/g, function (m) {
          return {
            '<': '<',
            '>': '>',
            '"': '"',
            '&': '&'
          }[m];
        });
    }
  });
}

$('#sharemenu').bind('open', updateSavedState);
$('#sharebintype input[type=radio]').on('click', function () {
  if (this.value === 'snapshot') {
    jsbin.state.checksum = false;
    saveChecksum = false;
  }
  updateSavedState();
});

$document.on('saved', function () {
  updateSavedState();

  $('#sharebintype input[type=radio][value="realtime"]').prop('checked', true);

  $shareLinks.closest('.menu').removeClass('hidden');

  $('#jsbinurl').attr('href', jsbin.getURL()).removeClass('hidden');
  $('#clone').removeClass('hidden');
});

var saveChecksum = jsbin.state.checksum || store.sessionStorage.getItem('checksum') || false;

// store it back on state
jsbin.state.checksum = saveChecksum;

if (saveChecksum) {
  // remove the disabled class, but also remove the cancelling event handlers
  $('#share div.disabled').removeClass('disabled').unbind('click mousedown mouseup');
} else {
  $('#share div.disabled').one('click', function (event) {
    event.preventDefault();
    $('a.save').click();
  });
}

$document.one('saved', function () {
  $('#share div.disabled').removeClass('disabled').unbind('click mousedown mouseup');
});

function onSaveError(jqXHR, panelId) {
  if (jqXHR.status === 413) {
    // Hijack the tip label to show an error message.
    $('#tip p').html('Sorry this bin is too large for us to save');
    $(document.documentElement).addClass('showtip');
  } else if (jqXHR.status === 403) {
    $document.trigger('tip', {
      type: 'error',
      content: 'I think there\'s something wrong with your session and I\'m unable to save. Refresh to fix this, you will not lose your code.'
    });
  } else if (panelId) {
    if (panelId) { savingLabels[panelId].text('Saving...').animate({ opacity: 1 }, 100); }
    window._console.error({message: 'Warning: Something went wrong while saving. Your most recent work is not saved.'});
  }
}

// only start live saving it they're allowed to (whereas save is disabled if they're following)
if (!jsbin.saveDisabled) {
  $('.code.panel .label .name').append('Saved');

  var savingLabels = {
    html: $('.panel.html .name span'),
    javascript: $('.panel.javascript .name span'),
    css: $('.panel.css .name span')
  };

  $document.bind('jsbinReady', function () {
    jsbin.panels.allEditors(function (panel) {
      panel.on('processor', function () {
        // if the url doesn't match the root - i.e. they've actually saved something then save on processor change
        if (jsbin.root !== jsbin.getURL()) {
          $document.trigger('codeChange', [{ panelId: panel.id }]);
        }
      });
    });

    $document.bind('codeChange', function (event, data) {
      // savingLabels[data.panelId].text('Saving');
      if (savingLabels[data.panelId]) {
        savingLabels[data.panelId].css({ 'opacity': 0 }).stop(true, true);
      }
    });

    $document.bind('saveComplete', throttle(function (event, data) {
      // show saved, then revert out animation
      savingLabels[data.panelId]
        .text('Saved')
        .stop(true, true)
        .animate({ opacity: 1 }, 100)
        .delay(1200)
        .animate({ opacity: 0 }, 500);
    }, 500));

    $document.bind('codeChange', throttle(function (event, data) {
      if (!data.panelId) {
        return;
      }

      if (jsbin.state.deleted) {
        return;
      }

      var panelId = data.panelId;

      jsbin.panels.savecontent();

      if (saving.inprogress()) {
        // queue up the request and wait
        saving.todo[panelId] = true;
        return;
      }

      saving.inprogress(true);

      // We force a full save if there's no checksum OR if there's no bin code/url
      if (!saveChecksum || !jsbin.state.code) {
        // create the bin and when the response comes back update the url
        saveCode('save', true);
      } else {
        updateCode(panelId);
      }
    }, 250));
  });
} else {
  $document.one('jsbinReady', function () {
    'use strict';
    var shown = false;
    if (!jsbin.embed && !jsbin.sandbox) {
      $document.on('codeChange.live', function (event, data) {
        if (!data.onload && !shown && data.origin !== 'setValue') {
          shown = true;
          var ismac = navigator.userAgent.indexOf(' Mac ') !== -1;
          var cmd = ismac ? '⌘' : 'ctrl';
          var shift = ismac ? '⇧' : 'shift';
          var plus = ismac ? '' : '+';

          $document.trigger('tip', {
            type: 'notification',
            content: 'You\'re currently viewing someone else\'s live stream, but you can clone your own copy (' + cmd + plus + shift + plus + 'S) at any time to save your edits'
          });
        }
      });
    }
  });
}

function compressKeys(keys, obj) {
  obj.compressed = keys;
  keys.split(',').forEach(function (key) {
    obj[key] = LZString.compressToUTF16(obj[key]);
  });
}

function updateCode(panelId, callback) {
  var panelSettings = {};

  if (jsbin.state.processors) {
    panelSettings.processors = jsbin.state.processors;
  }

  var data = {
    code: jsbin.state.code,
    revision: jsbin.state.revision,
    method: 'update',
    panel: panelId,
    content: editors[panelId].getCode(),
    checksum: saveChecksum,
    settings: JSON.stringify(panelSettings),
  };

  if (jsbin.settings.useCompression) {
    compressKeys('content', data);
  }

  var update = function(){
    $.ajax({
      url: jsbin.getURL({ withRevision: true }) + '/save',
      data: data,
      type: 'post',
      dataType: 'json',
      headers: {'Accept': 'application/json'},
      success: function (data) {
        $document.trigger('saveComplete', { panelId: panelId });
        if (data.error) {
          saveCode('save', true, function () {
            // savedAlready = data.checksum;
          });
        } else {
          jsbin.state.latest = true;
        }
      },
      error: function (jqXHR) {
        onSaveError(jqXHR, panelId);
      },
      complete: function () {
        saving.inprogress(false);
        if (callback) { callback(); }
      }
    });
  };

  update();
}

$('a.clone').click(clone);
$('#tip').delegate('a.clone', 'click', clone);

function clone(event) {
  event.preventDefault();

  // save our panel layout - assumes our user is happy with this layout
  jsbin.panels.save();
  analytics.clone();

  var $form = setupform('save,new');
  $form.submit();

  return false;
}

function setupform(method) {
var $form = $('form#saveform').empty()
    .append('<input type="hidden" name="javascript" />')
    .append('<input type="hidden" name="html" />')
    .append('<input type="hidden" name="css" />')
    .append('<input type="hidden" name="method" />')
    .append('<input type="hidden" name="_csrf" value="' + jsbin.state.token + '" />')
    .append('<input type="hidden" name="settings" />')
    .append('<input type="hidden" name="checksum" />');

  var settings = {};

  if (jsbin.state.processors) {
    settings.processors = jsbin.state.processors;
  }

  // this prevents new revisions forking off the welcome bin
  // because it's looking silly!
  if (jsbin.state.code === 'welcome') {
    $form.attr('action', '/save');
  }

  $form.find('input[name=settings]').val(JSON.stringify(settings));
  $form.find('input[name=javascript]').val(editors.javascript.getCode());
  $form.find('input[name=css]').val(editors.css.getCode());
  $form.find('input[name=html]').val(editors.html.getCode());
  $form.find('input[name=method]').val(method);
  $form.find('input[name=checksum]').val(jsbin.state.checksum);

  return $form;
}

function pad(n){
  return n<10 ? '0'+n : n;
}

function ISODateString(d){
  return d.getFullYear()+'-'
    + pad(d.getMonth()+1)+'-'
    + pad(d.getDate())+'T'
    + pad(d.getHours())+':'
    + pad(d.getMinutes())+':'
    + pad(d.getSeconds())+'Z';
}

function saveCode(method, ajax, ajaxCallback) {
  // create form and post to it
  var $form = setupform(method);
  // save our panel layout - assumes our user is happy with this layout
  jsbin.panels.save();
  jsbin.panels.saveOnExit = true;

  var data = $form.serializeArray().reduce(function(obj, data) {
    obj[data.name] = data.value;
    return obj;
  }, {});

  data.url = $.cookie('new-bin-name');

  if (jsbin.settings.useCompression) {
    compressKeys('html,css,javascript', data);
  }

  var save = function(){
    if (ajax) {
      $.ajax({
        url: $form.attr('action'),
        data: data,
        dataType: 'json',
        type: 'post',
        headers: {'Accept': 'application/json'},
        success: function (data) {
          if (ajaxCallback) {
            ajaxCallback(data);
          }

          store.sessionStorage.setItem('checksum', data.checksum);
          saveChecksum = data.checksum;

          jsbin.state.checksum = saveChecksum;
          jsbin.state.code = data.code;
          jsbin.state.revision = data.revision;
          jsbin.state.latest = true; // this is never not true...end of conversation!
          jsbin.state.metadata = { name: jsbin.user.name };
          $form.attr('action', jsbin.getURL({ withRevision: true }) + '/save');

          if (window.history && window.history.pushState) {
            // updateURL(edit);
            var hash = panels.getHighlightLines();
            if (hash) {hash = '#' + hash;}
            // If split is truthy (> 0) then we are using the revisonless feature
            // this is temporary until we release the feature!
            window.history.pushState(null, '', jsbin.getURL({withRevision: !split}) + '/edit' + hash);
            store.sessionStorage.setItem('url', jsbin.getURL({withRevision: !split}));
          } else {
            window.location.hash = data.edit;
          }

          $document.trigger('saved');
        },
        error: function (jqXHR) {
          onSaveError(jqXHR, null);
        },
        complete: function () {
          saving.inprogress(false);
        }
      });
    } else {
      $form.submit();
    }
  };

  if($.cookie('new-bin-name') || jsbin.state.revision !== null){
    save();
    $.removeCookie('new-bin-name');
  }else{
    $document.trigger('tip', {
      type: 'notication',
      content: 'The name of bin is not set. <input type="button" id="set-name" value="Set name">
',
      callback: function(){
        $('#set-name').click(function(event){
          vex.dialog.prompt({
            message: 'What is name of new bin?',
            placeholder: 'Name of bin',
            className: 'vex-theme-os',
            callback: function(value) {
              $.cookie('new-bin-name', value);
              $('a.dismiss').trigger('click');
            }
          });
        });

      }
    });
  }
};
<commit_msg>Rename panels to Panels<commit_after>/*jshint strict: false */
/*globals $, analytics, jsbin, documentTitle, $document, throttle, editors*/

var saving = {
  todo: {
    html: false,
    css: false,
    javascript: false
  },
  _inprogress: false,
  inprogress: function (inprogress) {
    if (typeof inprogress === 'undefined') {
      return saving._inprogress;
    }

    saving._inprogress = inprogress;
    if (inprogress === false) {
      var panels = ['html','css','javascript', 'jasmine'];

      var save = function () {
        var todo = panels.pop();
        if (todo && saving.todo[todo]) {
          saving._inprogress = true;
          updateCode(todo, save);
          saving.todo[todo] = false;
        } else if (todo) {
          save();
        }
      };

      save();
    }
  }
};

// to allow for download button to be introduced via beta feature
$('a.save').click(function (event) {
  event.preventDefault();

  analytics.milestone();
  // if save is disabled, hitting save will trigger a reload
  var ajax = true;
  if (jsbin.saveDisabled === true) {
    ajax = false;
  }
  saveCode('save', ajax);

  return false;
});

var $shareLinks = $('#share .link');
var $panelCheckboxes = $('#sharemenu #sharepanels input');

// TODO remove split when live
var split = $('#sharemenu .share-split').length;

// TODO candidate for removal
function updateSavedState() {
  'use strict';
  if (split) {
    return;
  }

  var mapping = {
    live: 'output',
    javascript: 'js',
    css: 'css',
    html: 'html',
    console: 'console'
  };

  var withRevision = true;

  var query = $panelCheckboxes.filter(':checked').map(function () {
    return mapping[this.getAttribute('data-panel')];
  }).get().join(',');
  $shareLinks.each(function () {
    var path = this.getAttribute('data-path');
    var url = jsbin.getURL({ withRevision: withRevision }) + path + (query && this.id !== 'livepreview' ? '?' + query : ''),
        nodeName = this.nodeName;
    var hash = Panels.getHighlightLines();

    if (hash) {
      hash = '#' + hash;
    }

    if (nodeName === 'A') {
      this.href = url;
    } else if (nodeName === 'INPUT') {
      this.value = url;
      if (path === '/edit') {
        this.value += hash;
      }
    } else if (nodeName === 'TEXTAREA') {
      this.value = ('' + documentTitle + '<' + 'script src="' + jsbin.static + '/js/chrome/embed.js"><' + '/script>').replace(/<>"&/g, function (m) {
          return {
            '<': '<',
            '>': '>',
            '"': '"',
            '&': '&'
          }[m];
        });
    }
  });
}

$('#sharemenu').bind('open', updateSavedState);
$('#sharebintype input[type=radio]').on('click', function () {
  if (this.value === 'snapshot') {
    jsbin.state.checksum = false;
    saveChecksum = false;
  }
  updateSavedState();
});

$document.on('saved', function () {
  updateSavedState();

  $('#sharebintype input[type=radio][value="realtime"]').prop('checked', true);

  $shareLinks.closest('.menu').removeClass('hidden');

  $('#jsbinurl').attr('href', jsbin.getURL()).removeClass('hidden');
  $('#clone').removeClass('hidden');
});

var saveChecksum = jsbin.state.checksum || store.sessionStorage.getItem('checksum') || false;

// store it back on state
jsbin.state.checksum = saveChecksum;

if (saveChecksum) {
  // remove the disabled class, but also remove the cancelling event handlers
  $('#share div.disabled').removeClass('disabled').unbind('click mousedown mouseup');
} else {
  $('#share div.disabled').one('click', function (event) {
    event.preventDefault();
    $('a.save').click();
  });
}

$document.one('saved', function () {
  $('#share div.disabled').removeClass('disabled').unbind('click mousedown mouseup');
});

function onSaveError(jqXHR, panelId) {
  if (jqXHR.status === 413) {
    // Hijack the tip label to show an error message.
    $('#tip p').html('Sorry this bin is too large for us to save');
    $(document.documentElement).addClass('showtip');
  } else if (jqXHR.status === 403) {
    $document.trigger('tip', {
      type: 'error',
      content: 'I think there\'s something wrong with your session and I\'m unable to save. Refresh to fix this, you will not lose your code.'
    });
  } else if (panelId) {
    if (panelId) { savingLabels[panelId].text('Saving...').animate({ opacity: 1 }, 100); }
    window._console.error({message: 'Warning: Something went wrong while saving. Your most recent work is not saved.'});
  }
}

// only start live saving it they're allowed to (whereas save is disabled if they're following)
if (!jsbin.saveDisabled) {
  $('.code.panel .label .name').append('Saved');

  var savingLabels = {
    html: $('.panel.html .name span'),
    javascript: $('.panel.javascript .name span'),
    css: $('.panel.css .name span')
  };

  $document.bind('jsbinReady', function () {
    jsbin.panels.allEditors(function (panel) {
      panel.on('processor', function () {
        // if the url doesn't match the root - i.e. they've actually saved something then save on processor change
        if (jsbin.root !== jsbin.getURL()) {
          $document.trigger('codeChange', [{ panelId: panel.id }]);
        }
      });
    });

    $document.bind('codeChange', function (event, data) {
      // savingLabels[data.panelId].text('Saving');
      if (savingLabels[data.panelId]) {
        savingLabels[data.panelId].css({ 'opacity': 0 }).stop(true, true);
      }
    });

    $document.bind('saveComplete', throttle(function (event, data) {
      // show saved, then revert out animation
      savingLabels[data.panelId]
        .text('Saved')
        .stop(true, true)
        .animate({ opacity: 1 }, 100)
        .delay(1200)
        .animate({ opacity: 0 }, 500);
    }, 500));

    $document.bind('codeChange', throttle(function (event, data) {
      if (!data.panelId) {
        return;
      }

      if (jsbin.state.deleted) {
        return;
      }

      var panelId = data.panelId;

      jsbin.panels.savecontent();

      if (saving.inprogress()) {
        // queue up the request and wait
        saving.todo[panelId] = true;
        return;
      }

      saving.inprogress(true);

      // We force a full save if there's no checksum OR if there's no bin code/url
      if (!saveChecksum || !jsbin.state.code) {
        // create the bin and when the response comes back update the url
        saveCode('save', true);
      } else {
        updateCode(panelId);
      }
    }, 250));
  });
} else {
  $document.one('jsbinReady', function () {
    'use strict';
    var shown = false;
    if (!jsbin.embed && !jsbin.sandbox) {
      $document.on('codeChange.live', function (event, data) {
        if (!data.onload && !shown && data.origin !== 'setValue') {
          shown = true;
          var ismac = navigator.userAgent.indexOf(' Mac ') !== -1;
          var cmd = ismac ? '⌘' : 'ctrl';
          var shift = ismac ? '⇧' : 'shift';
          var plus = ismac ? '' : '+';

          $document.trigger('tip', {
            type: 'notification',
            content: 'You\'re currently viewing someone else\'s live stream, but you can clone your own copy (' + cmd + plus + shift + plus + 'S) at any time to save your edits'
          });
        }
      });
    }
  });
}

function compressKeys(keys, obj) {
  obj.compressed = keys;
  keys.split(',').forEach(function (key) {
    obj[key] = LZString.compressToUTF16(obj[key]);
  });
}

function updateCode(panelId, callback) {
  var panelSettings = {};

  if (jsbin.state.processors) {
    panelSettings.processors = jsbin.state.processors;
  }

  var data = {
    code: jsbin.state.code,
    revision: jsbin.state.revision,
    method: 'update',
    panel: panelId,
    content: editors[panelId].getCode(),
    checksum: saveChecksum,
    settings: JSON.stringify(panelSettings),
  };

  if (jsbin.settings.useCompression) {
    compressKeys('content', data);
  }

  var update = function(){
    $.ajax({
      url: jsbin.getURL({ withRevision: true }) + '/save',
      data: data,
      type: 'post',
      dataType: 'json',
      headers: {'Accept': 'application/json'},
      success: function (data) {
        $document.trigger('saveComplete', { panelId: panelId });
        if (data.error) {
          saveCode('save', true, function () {
            // savedAlready = data.checksum;
          });
        } else {
          jsbin.state.latest = true;
        }
      },
      error: function (jqXHR) {
        onSaveError(jqXHR, panelId);
      },
      complete: function () {
        saving.inprogress(false);
        if (callback) { callback(); }
      }
    });
  };

  update();
}

$('a.clone').click(clone);
$('#tip').delegate('a.clone', 'click', clone);

function clone(event) {
  event.preventDefault();

  // save our panel layout - assumes our user is happy with this layout
  jsbin.panels.save();
  analytics.clone();

  var $form = setupform('save,new');
  $form.submit();

  return false;
}

function setupform(method) {
var $form = $('form#saveform').empty()
    .append('<input type="hidden" name="javascript" />')
    .append('<input type="hidden" name="html" />')
    .append('<input type="hidden" name="css" />')
    .append('<input type="hidden" name="method" />')
    .append('<input type="hidden" name="_csrf" value="' + jsbin.state.token + '" />')
    .append('<input type="hidden" name="settings" />')
    .append('<input type="hidden" name="checksum" />');

  var settings = {};

  if (jsbin.state.processors) {
    settings.processors = jsbin.state.processors;
  }

  // this prevents new revisions forking off the welcome bin
  // because it's looking silly!
  if (jsbin.state.code === 'welcome') {
    $form.attr('action', '/save');
  }

  $form.find('input[name=settings]').val(JSON.stringify(settings));
  $form.find('input[name=javascript]').val(editors.javascript.getCode());
  $form.find('input[name=css]').val(editors.css.getCode());
  $form.find('input[name=html]').val(editors.html.getCode());
  $form.find('input[name=method]').val(method);
  $form.find('input[name=checksum]').val(jsbin.state.checksum);

  return $form;
}

function pad(n){
  return n<10 ? '0'+n : n;
}

function ISODateString(d){
  return d.getFullYear()+'-'
    + pad(d.getMonth()+1)+'-'
    + pad(d.getDate())+'T'
    + pad(d.getHours())+':'
    + pad(d.getMinutes())+':'
    + pad(d.getSeconds())+'Z';
}

function saveCode(method, ajax, ajaxCallback) {
  // create form and post to it
  var $form = setupform(method);
  // save our panel layout - assumes our user is happy with this layout
  jsbin.panels.save();
  jsbin.panels.saveOnExit = true;

  var data = $form.serializeArray().reduce(function(obj, data) {
    obj[data.name] = data.value;
    return obj;
  }, {});

  data.url = $.cookie('new-bin-name');

  if (jsbin.settings.useCompression) {
    compressKeys('html,css,javascript', data);
  }

  var save = function(){
    if (ajax) {
      $.ajax({
        url: $form.attr('action'),
        data: data,
        dataType: 'json',
        type: 'post',
        headers: {'Accept': 'application/json'},
        success: function (data) {
          if (ajaxCallback) {
            ajaxCallback(data);
          }

          store.sessionStorage.setItem('checksum', data.checksum);
          saveChecksum = data.checksum;

          jsbin.state.checksum = saveChecksum;
          jsbin.state.code = data.code;
          jsbin.state.revision = data.revision;
          jsbin.state.latest = true; // this is never not true...end of conversation!
          jsbin.state.metadata = { name: jsbin.user.name };
          $form.attr('action', jsbin.getURL({ withRevision: true }) + '/save');

          if (window.history && window.history.pushState) {
            // updateURL(edit);
            var hash = Panels.getHighlightLines();
            if (hash) {hash = '#' + hash;}
            // If split is truthy (> 0) then we are using the revisonless feature
            // this is temporary until we release the feature!
            window.history.pushState(null, '', jsbin.getURL({withRevision: !split}) + '/edit' + hash);
            store.sessionStorage.setItem('url', jsbin.getURL({withRevision: !split}));
          } else {
            window.location.hash = data.edit;
          }

          $document.trigger('saved');
        },
        error: function (jqXHR) {
          onSaveError(jqXHR, null);
        },
        complete: function () {
          saving.inprogress(false);
        }
      });
    } else {
      $form.submit();
    }
  };

  if($.cookie('new-bin-name') || jsbin.state.revision !== null){
    save();
    $.removeCookie('new-bin-name');
  }else{
    $document.trigger('tip', {
      type: 'notication',
      content: 'The name of bin is not set. <input type="button" id="set-name" value="Set name">
',
      callback: function(){
        $('#set-name').click(function(event){
          vex.dialog.prompt({
            message: 'What is name of new bin?',
            placeholder: 'Name of bin',
            className: 'vex-theme-os',
            callback: function(value) {
              $.cookie('new-bin-name', value);
              $('a.dismiss').trigger('click');
            }
          });
        });

      }
    });
  }
};
<|endoftext|>