<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import RootCloseWrapper from 'react-overlays/lib/RootCloseWrapper';
import moment from 'moment';
import contains from 'dom-helpers/query/contains';
import debounce from 'lodash/debounce';

import Button from '../Button';
import TextField from '../TextField';
import Calendar from './Calendar';
import Icon from '../Icon';
import css from './Calendar.css';

const propTypes = {
  onSetDate: PropTypes.func,
  screenReaderMessage: PropTypes.string,
  value: PropTypes.string,
  date: PropTypes.object,
  dateFormat: PropTypes.string, // eslint-disable-line
  label: PropTypes.string,
  disabled: PropTypes.bool,
  required: PropTypes.bool,
  input: PropTypes.object,
  meta: PropTypes.object,
  id: PropTypes.string,
  onChange: PropTypes.func,
  useFocus: PropTypes.bool,
  hideOnChoose: PropTypes.bool,
  locale: PropTypes.string,
  readOnly: PropTypes.bool,
  backendDateStandard: PropTypes.string,
  showCalendar: PropTypes.bool,
  excludeDates: PropTypes.oneOfType([
    PropTypes.array,
    PropTypes.object,
    PropTypes.string,
  ]),

};

const defaultProps = {
  screenReaderMessage: '',
  hideOnChoose: true,
  useFocus: true,
  locale: 'en',
  backendDateStandard: 'ISO8601',
};

const contextTypes = {
  stripes: PropTypes.shape({
    locale: PropTypes.string.isRequired,
  }).isRequired,
};

class Datepicker extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.textfield = null;
    this.picker = null;
    this.container = null;
    this.srSpace = null;
    this.calendarButton = null;

    this.handleSetDate = this.handleSetDate.bind(this);
    this.handleFieldFocus = this.handleFieldFocus.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.clearDate = this.clearDate.bind(this);
    this.showCalendar = this.showCalendar.bind(this);
    this.handleFieldClick = this.handleFieldClick.bind(this);
    this.hideCalendar = this.hideCalendar.bind(this);
    this.dbhideCalendar = debounce(this.hideCalendar, 10);
    this.toggleCalendar = this.toggleCalendar.bind(this);
    this.handleRootClose = this.handleRootClose.bind(this);
    this.hideOnBlur = this.hideOnBlur.bind(this);
    this.standardizeDate = this.standardizeDate.bind(this);
    this.handleFieldChange = this.handleFieldChange.bind(this);

    moment.locale(this.context.stripes.locale);
    this._dateFormat = moment.localeData()._longDateFormat.L;

    let initDate;
    let inputValue = '';
    if (!this.props.input) {
      if (this.props.value === 'today' && this.props.date === 'undefined') {
        initDate = moment();
      } else {
        initDate = this.props.date;
      }
    } else if (this.props.input.value === '') {
      initDate = '';
    } else {
      initDate = moment(this.props.input.value, this._dateFormat);
      inputValue = moment(this.props.input.value).format(this._dateFormat);
    }

    this.state = {
      date: initDate,
      dateString: inputValue,
      showCalendar: this.props.showCalendar || false,
      srMessage: '',
    };
  }

  componentWillReceiveProps(nextProps) {
    let initDate;
    let inputValue;
    if (nextProps.input) {
      if (nextProps.input.value !== '' && nextProps.input.value !== this.props.input.value) {
        initDate = moment(nextProps.input.value, this._dateFormat);
        inputValue = moment(nextProps.input.value).format(this._dateFormat);
        this.setState({
          date: initDate,
          dateString: inputValue,
        });
      }
    } else if (this.props.value !== '' && this.props.date !== nextProps.date) {
      if (nextProps.value === 'today' && nextProps.date === 'undefined') {
        initDate = moment();
        inputValue = initDate.format(this._dateFormat);
      } else {
        initDate = moment(nextProps.date, this._dateFormat);
        inputValue = initDate.format(this._dateFormat);
      }
      this.setState({
        date: initDate,
        dateString: inputValue,
      });
    }
  }

  handleFieldFocus() {
    if (this.props.useFocus) {
      this.showCalendar();
    }
  }

  handleFieldClick() {
    if (this.props.useFocus) {
      if (!this.state.showCalendar) {
        this.showCalendar();
      }
    }
  }

  handleRootClose() {
    // be sure that nothing in the datepicker is in focus before closing the calendar.
    if (!contains(this.container, document.activeElement)) {
      this.hideCalendar();
    }
  }

  showCalendar() {
    this.setState({
      showCalendar: true,
    });
  }

  hideCalendar() {
    this.setState({
      showCalendar: false,
    });
  }

  toggleCalendar() {
    const current = this.state.showCalendar;
    this.setState({
      showCalendar: !current,
    });
  }

  handleKeyDown(e) {
    if (this.picker) {
      const curDate = this.picker.getCursorDate();
      const formattedDate = curDate.format(this._dateFormat);
      let dateString;
      switch (e.keyCode) {
        case 40: // down
          e.preventDefault();
          dateString = this.picker.nextWeek();
          break;
        case 38: // up
          e.preventDefault();
          dateString = this.picker.previousWeek();
          break;
        case 37: // left
          dateString = this.picker.previousDay();
          break;
        case 39: // right
          dateString = this.picker.nextDay();
          break;
        case 27: // escape
          e.preventDefault();
          this.hideCalendar();
          break;
        case 34: // pgDn
          e.preventDefault();
          if (!this.state.showCalendar) { return; }
          if (!e.altKey) {
            dateString = this.picker.nextMonth();
          } else {
            dateString = this.picker.nextYear();
          }
          break;
        case 33: // pgUp
          e.preventDefault();
          if (!this.state.showCalendar) { return; }
          if (!e.altKey) {
            dateString = this.picker.previousMonth();
          } else {
            dateString = this.picker.previousYear();
          }
          break;
        case 13: // enter
          e.preventDefault();
          if (this.state.showCalendar) {
            this.handleSetDate(e, curDate, formattedDate);
          } else {
            this.showCalendar();
          }
          dateString = `${formattedDate} selected`;
          break;
        case 9: // tab
          this.hideCalendar();
          break;
        default:
      }
      this.informScreenReader(`${dateString}`);
    } else {
      switch (e.keyCode) {
        case 13: // enter
        case 40: // down
          e.preventDefault();
          this.showCalendar();
          break;
        default:
      }
    }
  }

  handleSetDate(e, date, stringDate) {
    if (e.type === 'click' || e.type === 'keydown') {
      e.preventDefault();

      if (this.props.onChange) { this.props.onChange(e); }
      this.textfield.getInput().focus();

      if (this.props.input) {
        if (moment(stringDate, this._dateFormat, true).isValid()) {
          // convert date to ISO 8601 format for backend
          const standardizedDate = this.standardizeDate(stringDate);

          // redux-form handlers take the value rather than the event...
          this.props.input.onChange(standardizedDate);
          this.props.input.onBlur(standardizedDate);
        }
      }
      if (this.props.hideOnChoose) {
        // hiding calendar is debounced to wait for focus to properly catch up.
        this.dbhideCalendar();
      }

      this.setState({ dateString: stringDate, cursorDate: date, date });
    }
    if (e.type === 'change') {
      const tempString = e.target.value;
      // be sure that value is parseable as a date in the required format...
      // the boolean parameter suppresses moment's deprecation warning,
      // preventing it from attempting a parse using independable js Date object.
      if (moment(tempString, this._dateFormat, true).isValid()) {
        const tempDate = moment(tempString, this._dateFormat);
        this.setState({
          date: tempDate,
          cursorDate: tempDate,
          dateString: e.target.value,
        });
      } else {
        // if it's not a valid date, update field value only so that
        // we can still see what we typed in the field...
        this.setState({
          dateString: e.target.value,
        });
      }
      if (this.props.onChange) { this.props.onChange(e); }
      if (this.props.input) {
        if (moment(e.target.value, this._dateFormat, true).isValid()) {
          // convert date to ISO 8601 format for backend
          const standardizedDate = this.standardizeDate(e.target.value);

          // redux-form handlers take the value rather than the event...
          this.props.input.onChange(standardizedDate);
        }
      }
      if (this.props.onSetDate) { this.props.onSetDate(); }
    }
  }

  handleFieldChange(e) {
    this.handleSetDate(e, e.target.value, e.target.value);
  }

  clearDate(e) {
    this.setState({
      date: undefined,
      dateString: '',
    });
    if (this.props.onChange) { this.props.onChange(e); }
    if (this.props.input) {
      this.props.input.onChange('');
    }
  }

  cleanForScreenReader(str) { // eslint-disable-line class-methods-use-this
    return str.replace(/Y/g, 'Y ');
  }

  informScreenReader(str) {
    this.setState({ srMessage: str });
  }

  hideOnBlur(e) {
    if (!contains(this.container, document.activeElement) &&
      document.activeElement !== document.body) {
      if (this.props.input) {
        this.props.input.onBlur(e);
      }
      this.hideCalendar();
      this.setState({ srMessage: '' });
    }
  }

  standardizeDate(date) {
    const DATE_RFC2822 = 'ddd, MM MMM YYYY HH:mm:ss ZZ';
    switch (this.props.backendDateStandard) {
      case 'ISO 8601':
      case 'ISO8601':
      case 'ISO-8601':
        return moment(date, this._dateFormat, true).toISOString();
      case 'RFC 2822':
      case 'RFC2822':
      case 'RFC-2822':
        return moment(date, this._dateFormat, true).format(DATE_RFC2822);
      default:
        return dateMoment.format(this.props.backendDateStandard);
    }
  }

  render() {
    const {
      input,
      screenReaderMessage,
      label, id, readOnly,
      required,
      excludeDates,
      disabled,
    } = this.props;

    const screenReaderFormat = this.cleanForScreenReader(this._dateFormat);
    let ariaLabel;
    if (readOnly) {
      ariaLabel = `${label}`;
    } else {
      ariaLabel = `${label} format is ${screenReaderFormat} use arrow keys to navigate dates. Enter key to confirm. ${screenReaderMessage}`;
    }

    let endElement;
    if (this.state.dateString !== '') {
      endElement = (

          <Button
            key="clearButton"
            buttonStyle="fieldControl"
            onClick={this.clearDate}
            title="Clear field value"
            tabIndex="-1"
          >
            <Icon icon="closeX" />
          </Button>
          <Button
            key="calendarButton"
            buttonStyle="fieldControl"
            onClick={this.toggleCalendar}
            onKeyDown={this.handleKeyDown}
            title="Show/hide datepicker"
            ref={(ref) => { this.calendarButton = ref; }}
            tabIndex="-1"
          >
            <Icon icon="calendar" />
          </Button>

      );
    } else {
      endElement = (
        <Button
          key="calendarButton"
          buttonStyle="fieldControl"
          onClick={this.toggleCalendar}
          onKeyDown={this.handleKeyDown}
          title="Show/hide datepicker"
          ref={(ref) => { this.calendarButton = ref; }}
          tabIndex="-1"
        >
          <Icon icon="calendar" />
        </Button>
      );
    }

    let textfield;
    if (this.props.input && this.props.meta) {
      textfield = (
        <TextField
          ref={(ref) => { this.textfield = ref; }}
          input={input}
          value={this.state.dateString}
          label={label}
          aria-label={ariaLabel}
          endControl={!readOnly ? endElement : null}
          placeholder={` ${this._dateFormat}`}
          onKeyDown={!readOnly ? this.handleKeyDown : null}
          onClick={!readOnly ? this.handleFieldClick : null}
          onFocus={!readOnly ? this.handleFieldFocus : null}
          onBlur={this.hideOnBlur}
          meta={this.props.meta}
          onChange={this.handleFieldChange}
          readOnly={readOnly}
          disabled={disabled}
          id={id}
          required={required}
        />
      );
    } else {
      textfield = (
        <TextField
          ref={(ref) => { this.textfield = ref; }}
          label={label}
          value={this.state.dateString}
          onChange={this.handleSetDate}
          aria-label={ariaLabel}
          endControl={!readOnly ? endElement : null}
          placeholder={` ${this._dateFormat}`}
          onKeyDown={!readOnly ? this.handleKeyDown : null}
          onBlur={this.hideCalendar}
          onClick={!readOnly ? this.handleFieldClick : null}
          onFocus={!readOnly ? this.handleFieldFocus : null}
          readOnly={readOnly}
          disabled={disabled}
          id={id}
          required={required}
        />
      );
    }

    return (

 { this.container = ref; }}>

 { this.srSpace = ref; }}>

{this.state.srMessage}

        {textfield}

        {this.state.showCalendar &&
          <RootCloseWrapper onRootClose={this.handleRootClose} >
            <Calendar
              onSetDate={this.handleSetDate}
              selectedDate={this.state.dateString}
              dateFormat={this._dateFormat}
              ref={(ref) => { this.picker = ref; }}
              onKeyDown={this.handleKeyDown}
              onBlur={this.hideCalendar}
              locale={this.context.stripes.locale || this.props.locale}
              excludeDates={excludeDates}
              buttonFocus={this.focusTextfield}
              onNavigation={this.handleDateNavigation}
            />
          </RootCloseWrapper>
        }

    );
  }
}

Datepicker.propTypes = propTypes;
Datepicker.contextTypes = contextTypes;
Datepicker.defaultProps = defaultProps;

export default Datepicker;
<commit_msg>Remind all occurrences within the standardizeDate function<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import RootCloseWrapper from 'react-overlays/lib/RootCloseWrapper';
import moment from 'moment';
import contains from 'dom-helpers/query/contains';
import debounce from 'lodash/debounce';

import Button from '../Button';
import TextField from '../TextField';
import Calendar from './Calendar';
import Icon from '../Icon';
import css from './Calendar.css';

const propTypes = {
  onSetDate: PropTypes.func,
  screenReaderMessage: PropTypes.string,
  value: PropTypes.string,
  date: PropTypes.object,
  dateFormat: PropTypes.string, // eslint-disable-line
  label: PropTypes.string,
  disabled: PropTypes.bool,
  required: PropTypes.bool,
  input: PropTypes.object,
  meta: PropTypes.object,
  id: PropTypes.string,
  onChange: PropTypes.func,
  useFocus: PropTypes.bool,
  hideOnChoose: PropTypes.bool,
  locale: PropTypes.string,
  readOnly: PropTypes.bool,
  backendDateStandard: PropTypes.string,
  showCalendar: PropTypes.bool,
  excludeDates: PropTypes.oneOfType([
    PropTypes.array,
    PropTypes.object,
    PropTypes.string,
  ]),

};

const defaultProps = {
  screenReaderMessage: '',
  hideOnChoose: true,
  useFocus: true,
  locale: 'en',
  backendDateStandard: 'ISO8601',
};

const contextTypes = {
  stripes: PropTypes.shape({
    locale: PropTypes.string.isRequired,
  }).isRequired,
};

class Datepicker extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.textfield = null;
    this.picker = null;
    this.container = null;
    this.srSpace = null;
    this.calendarButton = null;

    this.handleSetDate = this.handleSetDate.bind(this);
    this.handleFieldFocus = this.handleFieldFocus.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.clearDate = this.clearDate.bind(this);
    this.showCalendar = this.showCalendar.bind(this);
    this.handleFieldClick = this.handleFieldClick.bind(this);
    this.hideCalendar = this.hideCalendar.bind(this);
    this.dbhideCalendar = debounce(this.hideCalendar, 10);
    this.toggleCalendar = this.toggleCalendar.bind(this);
    this.handleRootClose = this.handleRootClose.bind(this);
    this.hideOnBlur = this.hideOnBlur.bind(this);
    this.standardizeDate = this.standardizeDate.bind(this);
    this.handleFieldChange = this.handleFieldChange.bind(this);

    moment.locale(this.context.stripes.locale);
    this._dateFormat = moment.localeData()._longDateFormat.L;

    let initDate;
    let inputValue = '';
    if (!this.props.input) {
      if (this.props.value === 'today' && this.props.date === 'undefined') {
        initDate = moment();
      } else {
        initDate = this.props.date;
      }
    } else if (this.props.input.value === '') {
      initDate = '';
    } else {
      initDate = moment(this.props.input.value, this._dateFormat);
      inputValue = moment(this.props.input.value).format(this._dateFormat);
    }

    this.state = {
      date: initDate,
      dateString: inputValue,
      showCalendar: this.props.showCalendar || false,
      srMessage: '',
    };
  }

  componentWillReceiveProps(nextProps) {
    let initDate;
    let inputValue;
    if (nextProps.input) {
      if (nextProps.input.value !== '' && nextProps.input.value !== this.props.input.value) {
        initDate = moment(nextProps.input.value, this._dateFormat);
        inputValue = moment(nextProps.input.value).format(this._dateFormat);
        this.setState({
          date: initDate,
          dateString: inputValue,
        });
      }
    } else if (this.props.value !== '' && this.props.date !== nextProps.date) {
      if (nextProps.value === 'today' && nextProps.date === 'undefined') {
        initDate = moment();
        inputValue = initDate.format(this._dateFormat);
      } else {
        initDate = moment(nextProps.date, this._dateFormat);
        inputValue = initDate.format(this._dateFormat);
      }
      this.setState({
        date: initDate,
        dateString: inputValue,
      });
    }
  }

  handleFieldFocus() {
    if (this.props.useFocus) {
      this.showCalendar();
    }
  }

  handleFieldClick() {
    if (this.props.useFocus) {
      if (!this.state.showCalendar) {
        this.showCalendar();
      }
    }
  }

  handleRootClose() {
    // be sure that nothing in the datepicker is in focus before closing the calendar.
    if (!contains(this.container, document.activeElement)) {
      this.hideCalendar();
    }
  }

  showCalendar() {
    this.setState({
      showCalendar: true,
    });
  }

  hideCalendar() {
    this.setState({
      showCalendar: false,
    });
  }

  toggleCalendar() {
    const current = this.state.showCalendar;
    this.setState({
      showCalendar: !current,
    });
  }

  handleKeyDown(e) {
    if (this.picker) {
      const curDate = this.picker.getCursorDate();
      const formattedDate = curDate.format(this._dateFormat);
      let dateString;
      switch (e.keyCode) {
        case 40: // down
          e.preventDefault();
          dateString = this.picker.nextWeek();
          break;
        case 38: // up
          e.preventDefault();
          dateString = this.picker.previousWeek();
          break;
        case 37: // left
          dateString = this.picker.previousDay();
          break;
        case 39: // right
          dateString = this.picker.nextDay();
          break;
        case 27: // escape
          e.preventDefault();
          this.hideCalendar();
          break;
        case 34: // pgDn
          e.preventDefault();
          if (!this.state.showCalendar) { return; }
          if (!e.altKey) {
            dateString = this.picker.nextMonth();
          } else {
            dateString = this.picker.nextYear();
          }
          break;
        case 33: // pgUp
          e.preventDefault();
          if (!this.state.showCalendar) { return; }
          if (!e.altKey) {
            dateString = this.picker.previousMonth();
          } else {
            dateString = this.picker.previousYear();
          }
          break;
        case 13: // enter
          e.preventDefault();
          if (this.state.showCalendar) {
            this.handleSetDate(e, curDate, formattedDate);
          } else {
            this.showCalendar();
          }
          dateString = `${formattedDate} selected`;
          break;
        case 9: // tab
          this.hideCalendar();
          break;
        default:
      }
      this.informScreenReader(`${dateString}`);
    } else {
      switch (e.keyCode) {
        case 13: // enter
        case 40: // down
          e.preventDefault();
          this.showCalendar();
          break;
        default:
      }
    }
  }

  handleSetDate(e, date, stringDate) {
    if (e.type === 'click' || e.type === 'keydown') {
      e.preventDefault();

      if (this.props.onChange) { this.props.onChange(e); }
      this.textfield.getInput().focus();

      if (this.props.input) {
        if (moment(stringDate, this._dateFormat, true).isValid()) {
          // convert date to ISO 8601 format for backend
          const standardizedDate = this.standardizeDate(stringDate);

          // redux-form handlers take the value rather than the event...
          this.props.input.onChange(standardizedDate);
          this.props.input.onBlur(standardizedDate);
        }
      }
      if (this.props.hideOnChoose) {
        // hiding calendar is debounced to wait for focus to properly catch up.
        this.dbhideCalendar();
      }

      this.setState({ dateString: stringDate, cursorDate: date, date });
    }
    if (e.type === 'change') {
      const tempString = e.target.value;
      // be sure that value is parseable as a date in the required format...
      // the boolean parameter suppresses moment's deprecation warning,
      // preventing it from attempting a parse using independable js Date object.
      if (moment(tempString, this._dateFormat, true).isValid()) {
        const tempDate = moment(tempString, this._dateFormat);
        this.setState({
          date: tempDate,
          cursorDate: tempDate,
          dateString: e.target.value,
        });
      } else {
        // if it's not a valid date, update field value only so that
        // we can still see what we typed in the field...
        this.setState({
          dateString: e.target.value,
        });
      }
      if (this.props.onChange) { this.props.onChange(e); }
      if (this.props.input) {
        if (moment(e.target.value, this._dateFormat, true).isValid()) {
          // convert date to ISO 8601 format for backend
          const standardizedDate = this.standardizeDate(e.target.value);

          // redux-form handlers take the value rather than the event...
          this.props.input.onChange(standardizedDate);
        }
      }
      if (this.props.onSetDate) { this.props.onSetDate(); }
    }
  }

  handleFieldChange(e) {
    this.handleSetDate(e, e.target.value, e.target.value);
  }

  clearDate(e) {
    this.setState({
      date: undefined,
      dateString: '',
    });
    if (this.props.onChange) { this.props.onChange(e); }
    if (this.props.input) {
      this.props.input.onChange('');
    }
  }

  cleanForScreenReader(str) { // eslint-disable-line class-methods-use-this
    return str.replace(/Y/g, 'Y ');
  }

  informScreenReader(str) {
    this.setState({ srMessage: str });
  }

  hideOnBlur(e) {
    if (!contains(this.container, document.activeElement) &&
      document.activeElement !== document.body) {
      if (this.props.input) {
        this.props.input.onBlur(e);
      }
      this.hideCalendar();
      this.setState({ srMessage: '' });
    }
  }

  standardizeDate(date) {
    const DATE_RFC2822 = 'ddd, MM MMM YYYY HH:mm:ss ZZ';
    switch (this.props.backendDateStandard) {
      case 'ISO 8601':
      case 'ISO8601':
      case 'ISO-8601':
        return moment(date, this._dateFormat, true).toISOString();
      case 'RFC 2822':
      case 'RFC2822':
      case 'RFC-2822':
        return moment(date, this._dateFormat, true).format(DATE_RFC2822);
      default:
        return moment(date, this._dateFormat, true).format(this.props.backendDateStandard);
    }
  }

  render() {
    const {
      input,
      screenReaderMessage,
      label, id, readOnly,
      required,
      excludeDates,
      disabled,
    } = this.props;

    const screenReaderFormat = this.cleanForScreenReader(this._dateFormat);
    let ariaLabel;
    if (readOnly) {
      ariaLabel = `${label}`;
    } else {
      ariaLabel = `${label} format is ${screenReaderFormat} use arrow keys to navigate dates. Enter key to confirm. ${screenReaderMessage}`;
    }

    let endElement;
    if (this.state.dateString !== '') {
      endElement = (

          <Button
            key="clearButton"
            buttonStyle="fieldControl"
            onClick={this.clearDate}
            title="Clear field value"
            tabIndex="-1"
          >
            <Icon icon="closeX" />
          </Button>
          <Button
            key="calendarButton"
            buttonStyle="fieldControl"
            onClick={this.toggleCalendar}
            onKeyDown={this.handleKeyDown}
            title="Show/hide datepicker"
            ref={(ref) => { this.calendarButton = ref; }}
            tabIndex="-1"
          >
            <Icon icon="calendar" />
          </Button>

      );
    } else {
      endElement = (
        <Button
          key="calendarButton"
          buttonStyle="fieldControl"
          onClick={this.toggleCalendar}
          onKeyDown={this.handleKeyDown}
          title="Show/hide datepicker"
          ref={(ref) => { this.calendarButton = ref; }}
          tabIndex="-1"
        >
          <Icon icon="calendar" />
        </Button>
      );
    }

    let textfield;
    if (this.props.input && this.props.meta) {
      textfield = (
        <TextField
          ref={(ref) => { this.textfield = ref; }}
          input={input}
          value={this.state.dateString}
          label={label}
          aria-label={ariaLabel}
          endControl={!readOnly ? endElement : null}
          placeholder={` ${this._dateFormat}`}
          onKeyDown={!readOnly ? this.handleKeyDown : null}
          onClick={!readOnly ? this.handleFieldClick : null}
          onFocus={!readOnly ? this.handleFieldFocus : null}
          onBlur={this.hideOnBlur}
          meta={this.props.meta}
          onChange={this.handleFieldChange}
          readOnly={readOnly}
          disabled={disabled}
          id={id}
          required={required}
        />
      );
    } else {
      textfield = (
        <TextField
          ref={(ref) => { this.textfield = ref; }}
          label={label}
          value={this.state.dateString}
          onChange={this.handleSetDate}
          aria-label={ariaLabel}
          endControl={!readOnly ? endElement : null}
          placeholder={` ${this._dateFormat}`}
          onKeyDown={!readOnly ? this.handleKeyDown : null}
          onBlur={this.hideCalendar}
          onClick={!readOnly ? this.handleFieldClick : null}
          onFocus={!readOnly ? this.handleFieldFocus : null}
          readOnly={readOnly}
          disabled={disabled}
          id={id}
          required={required}
        />
      );
    }

    return (

 { this.container = ref; }}>

 { this.srSpace = ref; }}>

{this.state.srMessage}

        {textfield}

        {this.state.showCalendar &&
          <RootCloseWrapper onRootClose={this.handleRootClose} >
            <Calendar
              onSetDate={this.handleSetDate}
              selectedDate={this.state.dateString}
              dateFormat={this._dateFormat}
              ref={(ref) => { this.picker = ref; }}
              onKeyDown={this.handleKeyDown}
              onBlur={this.hideCalendar}
              locale={this.context.stripes.locale || this.props.locale}
              excludeDates={excludeDates}
              buttonFocus={this.focusTextfield}
              onNavigation={this.handleDateNavigation}
            />
          </RootCloseWrapper>
        }

    );
  }
}

Datepicker.propTypes = propTypes;
Datepicker.contextTypes = contextTypes;
Datepicker.defaultProps = defaultProps;

export default Datepicker;
<|endoftext|>


<commit_before>SearchResponse = (() => {

  let _public = {};
  let _private = {};

  _public = {

    searchResponse: {},
    docs: [],
    facets: {},
    nestedCategories: {},
    loggedSearchId: '',

    init(searchResponse) {
      _public.searchResponse = searchResponse;
      _private.parseDocs();
      _private.parseFacets();
      _private.buildNestedCategories();
      _private.logSearch();
    },

    totalResults() {
      return this.searchResponse.response.numFound;
    }
  };

  _private = {

    parseDocs() {
      const docs = _public.searchResponse.response.docs;
      const highlighting = _public.searchResponse.highlighting;
      docs.forEach(doc => {
        doc._id = doc.id;
        if (highlighting[doc.id] && highlighting[doc.id].content) {
          let content = highlighting[doc.id].content;
          if (content) {
            content = content.replace(/ /g, ' ');
          }
          doc.content = '…' + content.join(' … ') + '…';
        } else {
          let content = doc.summary;
          if (content) {
            content = content.replace(/ /g, ' ');
          }
          doc.content = content + '…';
        }
      });
      _public.docs = docs;
    },

    parseFacets() {
      const facetCounts = _public.searchResponse.facet_counts;
      if (facetCounts) {
        const facets = {};
        _.keys(facetCounts.facet_fields).forEach((facet) => {
          facets[facet] = [];
          const facetValues = facetCounts.facet_fields[facet];
          for (let i = 0; i < facetValues.length; i += 2) {
            const name = facetValues[i];
            const count = facetValues[i + 1];
            if (count > 0) {
              facets[facet].push({
                name,
                count
              });
            }
          }
        });
        _public.facets = facets;
      }
    },

    buildNestedCategories() {
      const hierarchicalFacets = Object.keys(SearchConfig.hierarchicalFacets);
      hierarchicalFacets.forEach((nestedCategoryField) => {
        const splitCategories = [];
        _public.facets[nestedCategoryField].forEach((value) => {
          splitCategories.push({
            names: value.name.substring(0, value.name.length - 1).split('/'),
            count: value.count
          });
        });
        const nestedCategory = Object.create(NestedCategory);
        nestedCategory.init();
        _public.nestedCategories[nestedCategoryField] =
          nestedCategory.build(splitCategories);
      });
    },

    logSearch() {
      _public.loggedSearchId = App.utilities.searchLogger.logSearchToDatabase(
        _public.searchResponse.responseHeader.params.q,
        _public.searchResponse.responseHeader.params.fq,
        _public.searchResponse.response.numFound,
        Meteor.user()
      );
    }

  };

  return _public;
})();
<commit_msg>Adjustments to nbsp cleanser.<commit_after>SearchResponse = (() => {

  let _public = {};
  let _private = {};

  _public = {

    searchResponse: {},
    docs: [],
    facets: {},
    nestedCategories: {},
    loggedSearchId: '',

    init(searchResponse) {
      _public.searchResponse = searchResponse;
      _private.parseDocs();
      _private.parseFacets();
      _private.buildNestedCategories();
      _private.logSearch();
    },

    totalResults() {
      return this.searchResponse.response.numFound;
    }
  };

  _private = {

    parseDocs() {
      const docs = _public.searchResponse.response.docs;
      const highlighting = _public.searchResponse.highlighting;
      docs.forEach(doc => {
        doc._id = doc.id;
        if (highlighting[doc.id] && highlighting[doc.id].content) {
          doc.content = '… '
          let contents = highlighting[doc.id].content;
          contents.forEach((content) => {
            content = content.replace(/ /g, ' ');
            doc.content += content + ' … ';
          })
          // doc.content = '…' + content.join(' … ') + '…';
        } else {
          let content = doc.summary;
          if (content) {
            content = content.replace(/ /g, ' ');
          }
          doc.content = content + ' …';
        }
      });
      _public.docs = docs;
    },

    parseFacets() {
      const facetCounts = _public.searchResponse.facet_counts;
      if (facetCounts) {
        const facets = {};
        _.keys(facetCounts.facet_fields).forEach((facet) => {
          facets[facet] = [];
          const facetValues = facetCounts.facet_fields[facet];
          for (let i = 0; i < facetValues.length; i += 2) {
            const name = facetValues[i];
            const count = facetValues[i + 1];
            if (count > 0) {
              facets[facet].push({
                name,
                count
              });
            }
          }
        });
        _public.facets = facets;
      }
    },

    buildNestedCategories() {
      const hierarchicalFacets = Object.keys(SearchConfig.hierarchicalFacets);
      hierarchicalFacets.forEach((nestedCategoryField) => {
        const splitCategories = [];
        _public.facets[nestedCategoryField].forEach((value) => {
          splitCategories.push({
            names: value.name.substring(0, value.name.length - 1).split('/'),
            count: value.count
          });
        });
        const nestedCategory = Object.create(NestedCategory);
        nestedCategory.init();
        _public.nestedCategories[nestedCategoryField] =
          nestedCategory.build(splitCategories);
      });
    },

    logSearch() {
      _public.loggedSearchId = App.utilities.searchLogger.logSearchToDatabase(
        _public.searchResponse.responseHeader.params.q,
        _public.searchResponse.responseHeader.params.fq,
        _public.searchResponse.response.numFound,
        Meteor.user()
      );
    }

  };

  return _public;
})();
<|endoftext|>


<commit_before>/*!
 * dragtable
 *
 * @Version 2.0.0
 *
 * Copyright (c) 2010, Andres Koetter akottr@gmail.com
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * Inspired by the the dragtable from Dan Vanderkam (danvk.org/dragtable/)
 * Thanks to the jquery and jqueryui comitters
 *
 * Any comment, bug report, feature-request is welcome
 * Feel free to contact me.
 */

/* TOKNOW:
 * For IE7 you need this css rule:
 * table {
 *   border-collapse: collapse;
 * }
 * Or take a clean reset.css (see http://meyerweb.com/eric/tools/css/reset/)
 */

/* TODO: investigate
 * Does not work properly with css rule:
 * html {
 *      overflow: -moz-scrollbars-vertical;
 *  }
 * Workaround:
 * Fixing Firefox issues by scrolling down the page
 * http://stackoverflow.com/questions/2451528/jquery-ui-sortable-scroll-helper-element-offset-firefox-issue
 *
 * var start = $.noop;
 * var beforeStop = $.noop;
 * if($.browser.mozilla) {
 * var start = function (event, ui) {
 *               if( ui.helper !== undefined )
 *                 ui.helper.css('position','absolute').css('margin-top', $(window).scrollTop() );
 *               }
 * var beforeStop = function (event, ui) {
 *              if( ui.offset !== undefined )
 *                ui.helper.css('margin-top', 0);
 *              }
 * }
 *
 * and pass this as start and stop function to the sortable initialisation
 * start: start,
 * beforeStop: beforeStop
 */

/* TODO: support colgroups
 */

(function($) {
  $.widget("akottr.dragtable", {
    options: {
       revert:true                  // smooth revert
      ,dragHandle:'.table-handle'  // handle for moving cols, if not exists the whole 'th' is the handle
      ,maxMovingRows:40            // 1 -> only header. 40 row should be enough, the rest is usually not in the viewport
      ,onlyHeaderThreshold:100     // TODO: not implemented yet, switch automatically between entire col moving / only header moving
      ,dragaccept:null             // draggable cols -> default all
      ,persistState:null           // url or function -> plug in your custom persistState function right here. function call is persistState(originalTable)
      ,restoreState:null           // JSON-Object or function: some kind of experimental aka Quick-Hack TODO: do it better
      ,beforeStart:$.noop
      ,beforeMoving:$.noop
      ,beforeReorganize:$.noop
      ,beforeStop:$.noop
    },
    originalTable:{
       el:null
      ,selectedHandle:null
      ,sortOrder:null
      ,startIndex:0
      ,endIndex:0
    },
    sortableTable:{
       el:$()
      ,selectedHandle:$()
      ,movingRow:$()
    },
    _swapNodes: function(a, b) {
      var aparent= a.parentNode;
      var asibling= a.nextSibling===b? a : a.nextSibling;
      b.parentNode.insertBefore(a, b);
      aparent.insertBefore(b, asibling);
    },
    persistState: function() {
      var _this = this;
      this.originalTable.el.find('th').each(function(i) {
          if(this.id != '') {_this.originalTable.sortOrder[this.id]=i;}
        });
        $.ajax({url: this.options.persistState,
                data: this.originalTable.sortOrder});
    },
    /*
     * persistObj looks like
     * {'id1':'2','id3':'3','id2':'1'}
     * table looks like
     * |   id2  |   id1   |   id3   |
     */
    _restoreState: function(persistObj) {;
      for(n in persistObj) {
        this.originalTable.startIndex = $('#'+n).closest('th').prevAll().size() + 1;
        this.originalTable.endIndex = parseInt(persistObj[n] + 1);
        this._bubbleCols();
      }
    },
    // bubble the moved col left or right
    _bubbleCols: function() {
      var from = this.originalTable.startIndex;
      var to = this.originalTable.endIndex;
      /* Find children thead and tbody.
       * Only to process the immediate tr-children. Bugfix for inner tables
       */
      var thtb = this.originalTable.el.children();
      if(from < to) {
        for(var i = from; i < to; i++) {
          var row1 = thtb.find('> tr > td:nth-child('+i+')')
                         .add(thtb.find('> tr > th:nth-child('+i+')'));
          var row2 = thtb.find('> tr > td:nth-child('+(i+1)+')')
                         .add(thtb.find('> tr > th:nth-child('+(i+1)+')'));
          for(var j = 0; j < row1.length; j++) {
            this._swapNodes(row1[j],row2[j]);
          }
        }
      }
      else {
        for(var i = from; i > to; i--) {
          var row1 = thtb.find('> tr > td:nth-child('+i+')')
                         .add(thtb.find('> tr > th:nth-child('+i+')'));
          var row2 = thtb.find('> tr > td:nth-child('+(i-1)+')')
                         .add(thtb.find('> tr > th:nth-child('+(i-1)+')'));
          for(var j = 0; j < row1.length; j++) {
            this._swapNodes(row1[j],row2[j]);
          }
        }
      }
    },
    _rearrangeTableBackroundProcessing: function() {
      var _this = this;
      return function() {
        _this._bubbleCols();
        _this.options.beforeStop(this.originalTable);
        _this.sortableTable.el.remove();
        // persist state if necessary
        if(_this.options.persistState !== null) {
          $.isFunction(_this.options.persistState) ? _this.options.persistState(_this.originalTable) : _this.persistState();
        }
      };
    },
    _rearrangeTable: function() {
      var _this = this;
      return function() {
      // remove handler-class -> handler is now finished
      _this.originalTable.selectedHandle.removeClass('dragtable-handle-selected');
      // add disabled class -> reorgorganisation starts soon
      _this.sortableTable.el.sortable("disable");
      _this.sortableTable.el.addClass('dragtable-disabled');
      _this.options.beforeReorganize(_this.originalTable,_this.sortableTable);
      // do reorganisation asynchronous
      // for chrome a little bit more than 1 ms because we want to force a rerender
      _this.originalTable.endIndex = _this.sortableTable.movingRow.prevAll().size() + 1;
      setTimeout(_this._rearrangeTableBackroundProcessing(),50);
      }
    },
    /*
     * Disrupts the table. The original table stays the same.
     * But on a layer above the original table we are constructing a list (ul > li)
     * each li with a separate table representig a single col of the original table.
     */
    _generateSortable:function(e) {
      var _this = this;
      // table attributes
      var attrs = this.originalTable.el[0].attributes;
      var attrsString = '';
      for(var i=0; i < attrs.length;i++) {
        if(attrs[i].nodeValue) {
          attrsString += attrs[i].nodeName + '="' + attrs[i].nodeValue+'" ';
        }
      }

      // row attributes
      var rowAttrsArr = [];
      //compute height, special handling for ie needed :-(
      var heightArr = [];
      this.originalTable.el.find('tr').slice(0,this.options.maxMovingRows).each(function(i,v) {
        // row attributes
        var attrs = this.attributes;
        var attrsString = "";
        for(var j=0; j < attrs.length;j++) {
          if(attrs[j].nodeValue) {
            attrsString += " " + attrs[j].nodeName + '="' + attrs[j].nodeValue+'"';
          }
        }
        rowAttrsArr.push(attrsString);
        heightArr.push($(this).height());
      });

      // compute width, no special handling for ie needed :-)
      var widthArr = [];
      // compute total width, needed for not wrapping around after the screen ends (floating)
      var totalWidth=0;
      /* Find children thead and tbody.
       * Only to process the immediate tr-children. Bugfix for inner tables
       */
      var thtb = _this.originalTable.el.children();
      thtb.find('> tr > th').each(function(i,v) {
        // one extra px on right and left side
        totalWidth+=$(this).outerWidth()+2;
        widthArr.push($(this).width());
      });

      var sortableHtml = '
';
      // assemble the needed html
      thtb.find('> tr > th').each(function(i,v) {
        sortableHtml += '
';
        sortableHtml += '
';
        var row = thtb.find('> tr > th:nth-child('+(i+1)+')');
        if(_this.options.maxMovingRows > 1) {
          row = row.add(thtb.find('> tr > td:nth-child('+(i+1)+')').slice(0,_this.options.maxMovingRows-1));
        }
        row.each(function(j) {
          // TODO: May cause duplicate style-Attribute
          sortableHtml += '
';
          sortableHtml += $(this).clone().wrap('

').parent().html();
          sortableHtml += '
';
        });
        sortableHtml += '
';
        sortableHtml += '
';
      });
      sortableHtml += '
';
      this.sortableTable.el = this.originalTable.el.before(sortableHtml).prev();
      // set width if necessary
      this.sortableTable.el.find('th').each(function(i,v) {
        var _this = $(this);
         if(widthArr[i] > _this.width()) {
           _this.css({'width':widthArr[i]});
         }
      });

      // assign this.sortableTable.selectedHandle
      this.sortableTable.selectedHandle = this.sortableTable.el.find('th')
                                                               .find('.dragtable-handle-selected');

      var items = !this.options.dragaccept ? 'li' : 'li:has(' + this.options.dragaccept + ')';
      this.sortableTable.el.sortable({stop:this._rearrangeTable(),
                                      items:items,
                                      revert:this.options.revert,
                                      distance: 0
                                     })
                           .disableSelection();

      // assign start index
      this.originalTable.startIndex = $(e.target).closest('th').prevAll().size() + 1;

      this.options.beforeMoving(this.originalTable, this.sortableTable);
      // Start moving by delegating the original event to the new sortable table
      this.sortableTable.movingRow = this.sortableTable.el.find('li:nth-child('+this.originalTable.startIndex+')');
      // TODO: learn more about events. Is this the right way?

      // create down event and delegate it to sortable
	  var mousedownEvt = $.Event('mousedown');
      mousedownEvt.pageX=e.pageX;
      mousedownEvt.pageY=e.pageY;
      mousedownEvt.which=1;
      this.sortableTable.movingRow.trigger(mousedownEvt);

	  if($.support.noCloneEvent) {
	    // create move event and delegate it to sortable
		var mousemoveEvt = $.Event('mousemove');
        mousemoveEvt.pageX=e.pageX+5;
        mousemoveEvt.pageY=e.pageY+5;
        this.sortableTable.movingRow.trigger(mousemoveEvt);
      }
      // Some inner divs to deliver the posibillity to style the placeholder more sophisticated
      this.sortableTable.el.find('.ui-sortable-placeholder').html('

');
    },
    /* Start asynchronously to be able to give the user a feedback on mousedown (rerender the dom)
     * Currently disabled. Is it a bug in jQuery?
     * TODO: Bugreport jQuery -> data.events not undefined-save line 4519, when using delayed events
     * It issues only warnings in IE, but that offends me anyway.
     */
    _delayedStart:function(evt) {
      return function() {
        this._generateSortable(evt);
      };
    },
    _create: function(){
      this.originalTable = {
         el:this.element
        ,selectedHandle:$()
        ,sortOrder:{}
        ,startIndex:0
        ,endIndex:0
      };
      // bind draggable to 'th' by default
      var bindTo = this.originalTable.el.find('th');
      // filter only the cols that are accepted
      if(this.options.dragaccept) { bindTo = bindTo.filter(this.options.dragaccept); }
      // bind draggable to handle if exists
      if(bindTo.find(this.options.dragHandle).size() > 0) { bindTo = bindTo.find(this.options.dragHandle);}
      // restore state if necessary
      if(this.options.restoreState !== null) {
        $.isFunction(this.options.restoreState) ? this.options.restoreState(this.originalTable) : this._restoreState(this.options.restoreState);
      }
      var _this = this;
      bindTo.bind('mousedown',function(evt) {
        _this.originalTable.selectedHandle = $(this);
        _this.originalTable.selectedHandle.addClass('dragtable-handle-selected');
        _this.options.beforeStart(this.originalTable);
        // take a breath and rerender before creating sortable table
        // setTimeout(this._delayedStart(evt),10);
        // for immediate start (no delay)
        _this._generateSortable(evt);
      });
    },
    destroy: function() {
        $.Widget.prototype.destroy.apply(this, arguments); // default destroy
         // now do other stuff particular to this widget
    }
  });
})(jQuery);
<commit_msg>Expose more sortable options, improve mouse handling<commit_after>/*!
 * dragtable
 *
 * @Version 2.0.1
 *
 * Copyright (c) 2010, Andres Koetter akottr@gmail.com
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * Inspired by the the dragtable from Dan Vanderkam (danvk.org/dragtable/)
 * Thanks to the jquery and jqueryui comitters
 *
 * Any comment, bug report, feature-request is welcome
 * Feel free to contact me.
 */

/* TOKNOW:
 * For IE7 you need this css rule:
 * table {
 *   border-collapse: collapse;
 * }
 * Or take a clean reset.css (see http://meyerweb.com/eric/tools/css/reset/)
 */

/* TODO: investigate
 * Does not work properly with css rule:
 * html {
 *      overflow: -moz-scrollbars-vertical;
 *  }
 * Workaround:
 * Fixing Firefox issues by scrolling down the page
 * http://stackoverflow.com/questions/2451528/jquery-ui-sortable-scroll-helper-element-offset-firefox-issue
 *
 * var start = $.noop;
 * var beforeStop = $.noop;
 * if($.browser.mozilla) {
 * var start = function (event, ui) {
 *               if( ui.helper !== undefined )
 *                 ui.helper.css('position','absolute').css('margin-top', $(window).scrollTop() );
 *               }
 * var beforeStop = function (event, ui) {
 *              if( ui.offset !== undefined )
 *                ui.helper.css('margin-top', 0);
 *              }
 * }
 *
 * and pass this as start and stop function to the sortable initialisation
 * start: start,
 * beforeStop: beforeStop
 */

/* TODO: support colgroups
 */

/*
 * Thx to kriswill, https://github.com/akottr/dragtable/pull/9
 */

!function($) {
  $.widget("akottr.dragtable", {
    options: {
      revert: true,                 // smooth revert
      dragHandle: '.table-handle',  // handle for moving cols, if not exists the whole 'th' is the handle
      maxMovingRows: 40,            // 1 -> only header. 40 row should be enough, the rest is usually not in the viewport
      onlyHeaderThreshold: 100,     // TODO:  not implemented yet, switch automatically between entire col moving / only header moving
      dragaccept: null,             // draggable cols -> default all
      persistState: null,           // url or function -> plug in your custom persistState function right here. function call is persistState(originalTable)
      restoreState: null,           // JSON-Object or function:  some kind of experimental aka Quick-Hack TODO: do it better
      clickDelay: 10,               // ms to wait before rendering sortable list and delegating click event
      containment: 'parent',        // @see http://api.jqueryui.com/sortable/#option-containment, use it if you want to move in 2 dimesnions (together with axis: null)
      cursor: 'move',               // @see http://api.jqueryui.com/sortable/#option-cursor
      cursorAt: false,              // @see http://api.jqueryui.com/sortable/#option-cursorAt
      distance: 0,                  // @see http://api.jqueryui.com/sortable/#option-distance, for immediate feedback use "0"
      tolerance: 'pointer',         // @see http://api.jqueryui.com/sortable/#option-tolerance
      axis: 'x',                    // @see http://api.jqueryui.com/sortable/#option-axis, Only vertical moving is allowed. Use 'x' or null. Use this in conjunction with the 'containment' setting
      beforeStart: $.noop,
      beforeMoving: $.noop,
      beforeReorganize: $.noop,
      beforeStop: $.noop
    },
    originalTable: {
      el: null,
      selectedHandle: null,
      sortOrder: null,
      startIndex: 0,
      endIndex: 0
    },
    sortableTable: {
      el: $(),
      selectedHandle: $(),
      movingRow: $()
    },
    persistState: function() {
      var _this = this;
      this.originalTable.el.find('th').each(function(i) {
        if (this.id != '') {
          _this.originalTable.sortOrder[this.id] = i;
        }
      });
      $.ajax({
        url: this.options.persistState,
        data: this.originalTable.sortOrder
      });
    },
    /*
     * persistObj looks like
     * {'id1':'2','id3':'3','id2':'1'}
     * table looks like
     * |   id2  |   id1   |   id3   |
     */
    _restoreState: function(persistObj) {
      for (n in persistObj) {
        this.originalTable.startIndex = $('#' + n).closest('th').prevAll().size() + 1;
        this.originalTable.endIndex = parseInt(persistObj[n] + 1);
        this._bubbleCols();
      }
    },
    // bubble the moved col left or right
    _bubbleCols: function() {
      var from = this.originalTable.startIndex;
      var to = this.originalTable.endIndex;
      /* Find children thead and tbody.
       * Only to process the immediate tr-children. Bugfix for inner tables
       */
      var thtb = this.originalTable.el.children();
      if (from < to) {
        for (var i = from; i < to; i++) {
          var row1 = thtb.find('> tr > td:nth-child(' + i + ')')
            .add(thtb.find('> tr > th:nth-child(' + i + ')'));
          var row2 = thtb.find('> tr > td:nth-child(' + (i + 1) + ')')
            .add(thtb.find('> tr > th:nth-child(' + (i + 1) + ')'));
          for (var j = 0; j < row1.length; j++) {
            swapNodes(row1[j], row2[j]);
          }
        }
      } else {
        for (var i = from; i > to; i--) {
          var row1 = thtb.find('> tr > td:nth-child(' + i + ')')
            .add(thtb.find('> tr > th:nth-child(' + i + ')'));
          var row2 = thtb.find('> tr > td:nth-child(' + (i - 1) + ')')
            .add(thtb.find('> tr > th:nth-child(' + (i - 1) + ')'));
          for (var j = 0; j < row1.length; j++) {
            swapNodes(row1[j], row2[j]);
          }
        }
      }
    },
    _rearrangeTableBackroundProcessing: function() {
      var _this = this;
      return function() {
        _this._bubbleCols();
        _this.options.beforeStop(this.originalTable);
        _this.sortableTable.el.remove();
        restoreTextSelection();
        // persist state if necessary
        if (_this.options.persistState !== null) {
          $.isFunction(_this.options.persistState) ? _this.options.persistState(_this.originalTable) : _this.persistState();
        }
      };
    },
    _rearrangeTable: function() {
      var _this = this;
      return function() {
        // remove handler-class -> handler is now finished
        _this.originalTable.selectedHandle.removeClass('dragtable-handle-selected');
        // add disabled class -> reorgorganisation starts soon
        _this.sortableTable.el.sortable("disable");
        _this.sortableTable.el.addClass('dragtable-disabled');
        _this.options.beforeReorganize(_this.originalTable, _this.sortableTable);
        // do reorganisation asynchronous
        // for chrome a little bit more than 1 ms because we want to force a rerender
        _this.originalTable.endIndex = _this.sortableTable.movingRow.prevAll().size() + 1;
        setTimeout(_this._rearrangeTableBackroundProcessing(), 50);
      }
    },
    /*
     * Disrupts the table. The original table stays the same.
     * But on a layer above the original table we are constructing a list (ul > li)
     * each li with a separate table representig a single col of the original table.
     */
    _generateSortable: function(e) {
      !e.cancelBubble && (e.cancelBubble = true)
      var _this = this;
      // table attributes
      var attrs = this.originalTable.el[0].attributes;
      var attrsString = '';
      for (var i = 0; i < attrs.length; i++) {
        if (attrs[i].nodeValue) {
          attrsString += attrs[i].nodeName + '="' + attrs[i].nodeValue + '" ';
        }
      }

      // row attributes
      var rowAttrsArr = [];
      //compute height, special handling for ie needed :-(
      var heightArr = [];
      this.originalTable.el.find('tr').slice(0, this.options.maxMovingRows).each(function(i, v) {
        // row attributes
        var attrs = this.attributes;
        var attrsString = "";
        for (var j = 0; j < attrs.length; j++) {
          if (attrs[j].nodeValue) {
            attrsString += " " + attrs[j].nodeName + '="' + attrs[j].nodeValue + '"';
          }
        }
        rowAttrsArr.push(attrsString);
        heightArr.push($(this).height());
      });

      // compute width, no special handling for ie needed :-)
      var widthArr = [];
      // compute total width, needed for not wrapping around after the screen ends (floating)
      var totalWidth = 0;
      /* Find children thead and tbody.
       * Only to process the immediate tr-children. Bugfix for inner tables
       */
      var thtb = _this.originalTable.el.children();
      thtb.find('> tr > th').each(function(i, v) {
        // one extra px on right and left side
        totalWidth += $(this).outerWidth() + 2;
        widthArr.push($(this).width());
      });

      var sortableHtml = '
';
      // assemble the needed html
      thtb.find('> tr > th').each(function(i, v) {
        sortableHtml += '
';
        sortableHtml += '
';
        var row = thtb.find('> tr > th:nth-child(' + (i + 1) + ')');
        if (_this.options.maxMovingRows > 1) {
          row = row.add(thtb.find('> tr > td:nth-child(' + (i + 1) + ')').slice(0, _this.options.maxMovingRows - 1));
        }
        row.each(function(j) {
          // TODO: May cause duplicate style-Attribute
          var row_content = $(this).clone().wrap('

').parent().html()
          if (row_content.toLowerCase().indexOf('<th') === 0) sortableHtml += "
"
          sortableHtml += '
';
          sortableHtml += row_content;
          if (row_content.toLowerCase().indexOf(' _this.width()) {
          _this.css({
            'width': widthArr[i]
          });
        }
      });

      // assign this.sortableTable.selectedHandle
      this.sortableTable.selectedHandle = this.sortableTable.el.find('th .dragtable-handle-selected');

      var items = !this.options.dragaccept ? 'li' : 'li:has(' + this.options.dragaccept + ')';
      this.sortableTable.el.sortable({
        items: items,
        stop: this._rearrangeTable(),
        // pass thru options for sortable widget
        revert: this.options.revert,
        tolerance: this.options.tolerance,
        containment: this.options.containment,
        cursor: this.options.cursor,
        cursorAt: this.options.cursorAt,
        distance: this.options.distance,
        axis: this.options.axis
      });

      // assign start index
      this.originalTable.startIndex = $(e.target).closest('th').prevAll().size() + 1;

      this.options.beforeMoving(this.originalTable, this.sortableTable);
      // Start moving by delegating the original event to the new sortable table
      this.sortableTable.movingRow = this.sortableTable.el.find('li:nth-child(' + this.originalTable.startIndex + ')');

      // prevent the user from drag selecting "highlighting" surrounding page elements
      disableTextSelection();
      // clone the initial event and trigger the sort with it
      this.sortableTable.movingRow.trigger($.extend($.Event(e.type), {
        which: 1,
        clientX: e.clientX,
        clientY: e.clientY,
        pageX: e.pageX,
        pageY: e.pageY,
        screenX: e.screenX,
        screenY: e.screenY
      }));

      // Some inner divs to deliver the posibillity to style the placeholder more sophisticated
      this.sortableTable.el.find('.ui-sortable-placeholder').html('

');
    },
    _create: function() {
      this.originalTable = {
        el: this.element,
        selectedHandle: $(),
        sortOrder: {},
        startIndex: 0,
        endIndex: 0
      };
      // bind draggable to 'th' by default
      var bindTo = this.originalTable.el.find('th');
      // filter only the cols that are accepted
      if (this.options.dragaccept) {
        bindTo = bindTo.filter(this.options.dragaccept);
      }
      // bind draggable to handle if exists
      if (bindTo.find(this.options.dragHandle).size() > 0) {
        bindTo = bindTo.find(this.options.dragHandle);
      }
      // restore state if necessary
      if (this.options.restoreState !== null) {
        $.isFunction(this.options.restoreState) ? this.options.restoreState(this.originalTable) : this._restoreState(this.options.restoreState);
      }
      var _this = this;
      bindTo.mousedown(function(evt) {
        clearTimeout(this.downTimer);
        this.downTimer = setTimeout(function() {
          _this.originalTable.selectedHandle = $(this);
          _this.originalTable.selectedHandle.addClass('dragtable-handle-selected');
          _this.options.beforeStart(this.originalTable);
          _this._generateSortable(evt);
        }, _this.options.clickDelay);
      }).mouseup(function(evt) {
        clearTimeout(this.downTimer);
      })
    },
    destroy: function() {
      this._mouseDestroy();
      $.Widget.prototype.destroy.apply(this, arguments); // default destroy
      // now do other stuff particular to this widget
    }
  });

  /** closure-scoped "private" functions **/

  var body_onselectstart_save = $(document.body).attr('onselectstart')
    , body_unselectable_save = $(document.body).attr('unselectable');

  // css properties to disable user-select on the body tag by appending a ');
    $(document.head).append($style);
    $(document.body).attr('onselectstart', 'return false;').attr('unselectable', 'on');
    if (window.getSelection) {
      window.getSelection().removeAllRanges()
    } else {
      document.selection.empty(); // MSIE http://msdn.microsoft.com/en-us/library/ms535869%28v=VS.85%29.aspx
    }
  }

  // remove the


<commit_before>import React, { PropTypes } from 'react'
import keymirror from 'keymirror'
import styles from './Icon.css'

export const ICON_TYPES = keymirror({
  CLEAR: null,
  FAVORITE: null,
  IMAGE: null
})

export const ICON_SIZES = {
  SMALL: 18,
  NORMAL: 24,
  LARGE: 48
}

export function getIcon (type) {
  switch (type) {
    case ICON_TYPES.CLEAR:
      return <path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/>
    case ICON_TYPES.FAVORITE:
      return <path d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/>
    case ICON_TYPES.IMAGE:
      return <path d='M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z'/>
  }
}

export function Icon ({ size = ICON_SIZES.NORMAL, type }) {
  return (
    <svg
      className={styles.icon}
      height={size}
      preserveAspectRatio='xMidYMid meet'
      viewBox='0 0 24 24'
      width={size}
      xmlns='http://www.w3.org/2000/svg'
    >
      <g>
        {getIcon(type)}
      </g>
    </svg>
  )
}

Icon.propTypes = {
  size: PropTypes.oneOf(Object.values(ICON_SIZES)),
  type: PropTypes.oneOf(Object.keys(ICON_TYPES)).isRequired
}
<commit_msg>Ordering<commit_after>import keymirror from 'keymirror'
import React, { PropTypes } from 'react'
import styles from './Icon.css'

export const ICON_TYPES = keymirror({
  CLEAR: null,
  FAVORITE: null,
  IMAGE: null
})

export const ICON_SIZES = {
  LARGE: 48,
  NORMAL: 24,
  SMALL: 18
}

export function getIcon (type) {
  switch (type) {
    case ICON_TYPES.CLEAR:
      return <path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/>
    case ICON_TYPES.FAVORITE:
      return <path d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/>
    case ICON_TYPES.IMAGE:
      return <path d='M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z'/>
  }
}

export function Icon ({ size = ICON_SIZES.NORMAL, type }) {
  return (
    <svg
      className={styles.icon}
      height={size}
      preserveAspectRatio='xMidYMid meet'
      viewBox='0 0 24 24'
      width={size}
      xmlns='http://www.w3.org/2000/svg'
    >
      <g>
        {getIcon(type)}
      </g>
    </svg>
  )
}

Icon.propTypes = {
  size: PropTypes.oneOf(Object.values(ICON_SIZES)),
  type: PropTypes.oneOf(Object.keys(ICON_TYPES)).isRequired
}
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Cover from './Cover';
import Body from './Body';
import ExecutionEnvironment from 'exenv';
import { css } from 'emotion';
import { connect } from 'react-redux';
import { checkValidUsername, unmountProfile } from 'redux/actions/UserActions';
import NotFound from 'components/NotFound';
import Loading from 'components/Loading';

class Profile extends Component {
  static propTypes = {
    checkValidUsername: PropTypes.func.isRequired,
    history: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    match: PropTypes.object.isRequired,
    profile: PropTypes.object.isRequired,
    unmountProfile: PropTypes.func.isRequired,
    userId: PropTypes.number,
    username: PropTypes.string
  };

  constructor({ profile: { profileTheme } }) {
    super();
    this.state = {
      selectedTheme: profileTheme || 'logoBlue'
    };
  }

  componentDidMount() {
    const { checkValidUsername, match } = this.props;
    const { username } = match.params;
    if (ExecutionEnvironment.canUseDOM) checkValidUsername(username);
  }

  componentDidUpdate(prevProps) {
    const {
      checkValidUsername,
      history,
      userId,
      profile: { unavailable },
      match
    } = this.props;
    if (
      prevProps.match.params.username &&
      prevProps.match.params.username !== match.params.username
    ) {
      return checkValidUsername(match.params.username);
    }

    if (
      match.params.username === 'undefined' &&
      !prevProps.userId &&
      userId &&
      unavailable
    ) {
      history.push(`/${this.props.username}`);
    }

    if (prevProps.profile.id !== this.props.profile.id) {
      this.setState({
        selectedTheme: this.props.profile.profileTheme || 'logoBlue'
      });
    }
  }

  componentWillUnmount() {
    const { unmountProfile } = this.props;
    unmountProfile();
  }

  render() {
    const {
      history,
      location,
      match,
      profile: { unavailable, id },
      profile,
      userId
    } = this.props;
    const { selectedTheme } = this.state;
    return !unavailable ? (

        {!id && <Loading text="Loading Profile..." />}
        {id && (

            <Cover
              profile={profile}
              onSelectColor={color => this.setState({ selectedTheme: color })}
              selectedTheme={selectedTheme}
            />

        )}

    ) : (
      <NotFound
        title={!userId ? 'For Registered Users Only' : ''}
        text={!userId ? 'Please Log In or Sign Up' : ''}
      />
    );
  }
}

export default connect(
  state => ({
    userId: state.UserReducer.userId,
    username: state.UserReducer.username,
    profile: state.UserReducer.profile
  }),
  { checkValidUsername, unmountProfile }
)(Profile);
<commit_msg>minor change<commit_after>import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Cover from './Cover';
import Body from './Body';
import ExecutionEnvironment from 'exenv';
import { css } from 'emotion';
import { connect } from 'react-redux';
import { checkValidUsername, unmountProfile } from 'redux/actions/UserActions';
import NotFound from 'components/NotFound';
import Loading from 'components/Loading';

class Profile extends Component {
  static propTypes = {
    checkValidUsername: PropTypes.func.isRequired,
    history: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    match: PropTypes.object.isRequired,
    profile: PropTypes.object.isRequired,
    unmountProfile: PropTypes.func.isRequired,
    userId: PropTypes.number,
    username: PropTypes.string
  };

  constructor({ profile: { profileTheme } }) {
    super();
    this.state = {
      selectedTheme: profileTheme || 'logoBlue'
    };
  }

  componentDidMount() {
    const { checkValidUsername, match } = this.props;
    const { username } = match.params;
    if (ExecutionEnvironment.canUseDOM) checkValidUsername(username);
  }

  componentDidUpdate(prevProps) {
    const {
      checkValidUsername,
      history,
      userId,
      profile: { unavailable },
      match
    } = this.props;
    if (
      prevProps.match.params.username &&
      prevProps.match.params.username !== match.params.username
    ) {
      return checkValidUsername(match.params.username);
    }

    if (
      match.params.username === 'undefined' &&
      !prevProps.userId &&
      userId &&
      unavailable
    ) {
      history.push(`/${this.props.username}`);
    }

    if (prevProps.profile.id !== this.props.profile.id) {
      this.setState({
        selectedTheme: this.props.profile.profileTheme || 'logoBlue'
      });
    }
  }

  componentWillUnmount() {
    const { unmountProfile } = this.props;
    unmountProfile();
  }

  render() {
    const {
      history,
      location,
      match,
      profile: { unavailable, id },
      profile,
      userId
    } = this.props;
    const { selectedTheme } = this.state;
    return (

        {!unavailable ? (
          <>
            {!id && <Loading text="Loading Profile..." />}
            {id && (

                <Cover
                  profile={profile}
                  onSelectColor={color =>
                    this.setState({ selectedTheme: color })
                  }
                  selectedTheme={selectedTheme}
                />

            )}
          </>
        ) : (
          <NotFound
            title={!userId ? 'For Registered Users Only' : ''}
            text={!userId ? 'Please Log In or Sign Up' : ''}
          />
        )}

    );
  }
}

export default connect(
  state => ({
    userId: state.UserReducer.userId,
    username: state.UserReducer.username,
    profile: state.UserReducer.profile
  }),
  { checkValidUsername, unmountProfile }
)(Profile);
<|endoftext|>


<commit_before>dc.ui.Paginator = dc.View.extend({

  DEFAULT_PAGE_SIZE : 10,
  MINI_PAGE_SIZE    : 30,

  id        : 'paginator',
  className : 'interface',

  query : null,
  page  : null,

  callbacks : {
    '.prev.click':          'previousPage',
    '.next.click':          'nextPage',
    '.enumeration.change':  'goToPage',
    '#size_toggle.click':   'toggleSize'
  },

  constructor : function() {
    this.base();
    this.mini = false;
  },

  setQuery : function(query) {
    this.query = query;
    this.page  = query.page;
    $(document.body).addClass('paginated');
    this.render();
  },

  hide : function() {
    $(document.body).removeClass('paginated');
  },

  // Keep in sync with search.rb on the server.
  pageSize : function() {
    return this.mini ? this.MINI_PAGE_SIZE : this.DEFAULT_PAGE_SIZE;
  },

  pageCount : function() {
    return Math.ceil(this.query.total / this.pageSize());
  },

  render : function() {
    var el = $(this.el);
    el.html('');
    if (!this.query) return this;
    el.html(JST.paginator({q : this.query, page_size : this.pageSize(), page_count : this.pageCount()}));
    this.setCallbacks();
    return this;
  },

  toggleSize: function(callback) {
    this.mini = !this.mini;
    callback = _.isFunction(callback) ? callback : null;
    dc.app.searchBox.search(dc.app.searchBox.value(), this.page || 1, callback);
    $(document.body).toggleClass('minidocs', this.mini);
  },

  // TODO: Move all these into the searchBox and clean it up.

  previousPage : function() {
    dc.app.searchBox.search(dc.app.searchBox.value(), (this.page || 1) - 1);
  },

  nextPage : function() {
    dc.app.searchBox.search(dc.app.searchBox.value(), (this.page || 1) + 1);
  },

  goToPage : function(e) {
    dc.app.searchBox.search(dc.app.searchBox.value(), $(e.target).val());
  }

});<commit_msg>proper pageFactor translations between the views ... for a consistent starting document...<commit_after>dc.ui.Paginator = dc.View.extend({

  DEFAULT_PAGE_SIZE : 10,
  MINI_PAGE_SIZE    : 30,

  id        : 'paginator',
  className : 'interface',

  query : null,
  page  : null,

  callbacks : {
    '.prev.click':          'previousPage',
    '.next.click':          'nextPage',
    '.enumeration.change':  'goToPage',
    '#size_toggle.click':   'toggleSize'
  },

  constructor : function() {
    this.base();
    this.mini = false;
  },

  setQuery : function(query) {
    this.query = query;
    this.page  = query.page;
    $(document.body).addClass('paginated');
    this.render();
  },

  hide : function() {
    $(document.body).removeClass('paginated');
  },

  // Keep in sync with search.rb on the server.
  pageSize : function() {
    return this.mini ? this.MINI_PAGE_SIZE : this.DEFAULT_PAGE_SIZE;
  },

  pageFactor : function() {
    return this.mini ? this.MINI_PAGE_SIZE / this.DEFAULT_PAGE_SIZE :
                       this.DEFAULT_PAGE_SIZE / this.MINI_PAGE_SIZE;
  },

  pageCount : function() {
    return Math.ceil(this.query.total / this.pageSize());
  },

  render : function() {
    var el = $(this.el);
    el.html('');
    if (!this.query) return this;
    el.html(JST.paginator({q : this.query, page_size : this.pageSize(), page_count : this.pageCount()}));
    this.setCallbacks();
    return this;
  },

  toggleSize: function(callback) {
    this.mini = !this.mini;
    callback = _.isFunction(callback) ? callback : null;
    var page = Math.floor(((this.page || 1) - 1) / this.pageFactor()) + 1;
    dc.app.searchBox.search(dc.app.searchBox.value(), page, callback);
    $(document.body).toggleClass('minidocs', this.mini);
  },

  // TODO: Move all these into the searchBox and clean it up.

  previousPage : function() {
    dc.app.searchBox.search(dc.app.searchBox.value(), (this.page || 1) - 1);
  },

  nextPage : function() {
    dc.app.searchBox.search(dc.app.searchBox.value(), (this.page || 1) + 1);
  },

  goToPage : function(e) {
    dc.app.searchBox.search(dc.app.searchBox.value(), $(e.target).val());
  }

});<|endoftext|>


<commit_before>var Guid = require('./Guid');
var ContentFinder = require('./ContentFinder');

// Manages making changes to the content
//
class ContentManager {
  constructor(content) {
    this.content = content;
    this.finder = new ContentFinder(content);
  }

  // add / remove / combine blocks

  appendBlock(guids) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var text  = block.text;
    var starts = text.substring(0, 1);
    var newBlock;

    // finish list
    if (block.type == 'li' && text === '') {
      newBlock = this._finishList(guid);

    // add to a list
    } else if (block.type == 'li') {
      newBlock = this._addListItem(guid);

    // create new list
    } else if (['-', '*', '1'].indexOf(starts) != -1) {
      newBlock = this._createList(guid, starts);

    // normal new block
    } else {
      newBlock = this._insertBlock('p', 'after', guid);
    }
    return { block: newBlock, offset: 0 };
  }
  prependBlock(guids) {
    var guid = guids.anchor;
    var block = this.finder.findBlock(guid);
    var newBlock = this._insertBlock('p', 'before', guid);
    return { block: block, offset: 0 };
  }
  splitBlock(guids, offsets) {
    var range = this.finder.findRange(guids);
    var first = range.shift();
    var last  = range.pop();

    var anchor = this.finder.findBlock(first);
    var focus  = last ? this.finder.findBlock(last) : anchor;

    // delete the rest
    range.forEach( (guid) => { this._removeBlock(guid); });

    var end  = focus.text.substring(offsets.focus);
    var type = focus.type;
    anchor.text = anchor.text.substring(0, offsets.anchor);

    if (anchor.id != focus.id) { this._removeBlock(focus.id); }

    var newBlock = this._insertBlock(type, 'after', guids.anchor, end);
    return { block: newBlock, offset: 0 };
  }
  combineBlocks(guids, offsets) {
    var range = this.finder.findRange(guids);
    var first = range.shift();
    var last  = range.pop();

    var anchor = this.finder.findBlock(first);
    var focus  = last ? this.finder.findBlock(last) : anchor;

    // delete the rest
    range.forEach( (guid) => { this._removeBlock(guid); });

    anchor.text = anchor.text.substring(0, offsets.anchor) +
                  focus.text.substring(offsets.focus);
    if (anchor.id != focus.id) { this._removeBlock(focus.id); }

    return { block: anchor, offset: offsets.anchor };
  }
  combineBlockWithPrevious(guids) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var prev  = this.finder.findPreviousBlock(guid);

    if (prev) {
      var offset = prev.text.length;
      prev.text = prev.text + block.text;
      this._removeBlock(block.id);
      return { block: prev, offset: offset };

    } else {
      return { block: null, offset: 0 };
    }
  }
  combineBlockWithNext(guids) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var next  = this.finder.findNextBlock(guid);

    if (next) {
      var offset = block.text.length;
      block.text = block.text + next.text;
      this._removeBlock(next.id);
      return { block: next, offset: offset };

    } else {
      return { block: null, offset: 0 };
    }
  }

  _addListItem(guid) {
    return this._insertBlock('li', 'after', guid);
  }
  _createList(guid, starts) {
    var type = starts == '1' ? 'ol' : 'ul';

    var block = this.finder.findBlock(guid);
    var text = block.text;
    delete block.text

    block.type = type;
    var items = [
      this._newBlock('li', text.replace(/^[-*\d]\.?\s?/g, '')),
      this._newBlock('li', '')
    ]
    block.blocks = items;
    return items[1];
  }
  _finishList(guid) {
    var parentGuid = this.finder.findParentBlock(guid).id;
    this._removeBlock(guid);
    return this._insertBlock('p', 'after', parentGuid);
  }
  _removeBlock(guid) {
    var blocks = this.finder.findBlocks(guid);
    var index  = this.finder.findBlockPosition(guid);

    blocks.splice(index, 1);
  }
  _insertBlock(type, position, guid, text) {
    var blocks = this.finder.findBlocks(guid);
    var index  = this.finder.findBlockPosition(guid);

    var block = this._newBlock(type, text || "");
    var index = position == 'after' ? index + 1 : index;

    blocks.splice(index, 0, block);

    return block;
  }
  _newBlock(type, text) {
    return { "id": Guid.unique(), "type": type, "text": text }
  }

  // text

  updateText(guids, text) {
    var block = this.finder.findBlock(guids.anchor);
    block.text = text;
  }

  // modifying block type / markup

  toggleCenter(guids, offsets) {
    var range = this.finder.findRange(guids, offsets)
    var blocks = range.map( (guid) => {
      return this.finder.findBlock(guid);
    });

    // check how many are centered
    var notCentered = blocks.filter( (block) => {
      return !block.meta || block.meta.align != 'center';
    })

    if (notCentered.length > 0) {
      notCentered.forEach( (block) => {
        block.meta = block.meta || {}
        block.meta.align = "center";
      });
    } else {
      blocks.forEach( (block) => {
        delete block.meta.align;
      });
    }
  }

  toggleBlockType(guids, offsets, tagName) {
    var range = this.finder.findRange(guids, offsets)
    var blocks = range.map( (guid) => {
      return this.finder.findBlock(guid);
    });

    var notOfType = blocks.filter( (block) => {
      return block.type != tagName;
    })

    if (notOfType.length > 0) {
      var type = notOfType[0].type;
      notOfType.forEach( (block) => { block.type = tagName; });
      return { oldType: type, newType: tagName };

    } else {
      blocks.forEach( (block) => { block.type = 'p'; });
      return { oldType: tagName, newType: 'p' };
    }
  }

  toggleMarkup(guids, offsets, type, value) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var markup = { "range": [offsets.anchor, offsets.focus] };
    if (value) { markup.value = value; }

    block.markups = block.markups || {}
    block.markups[type] = block.markups[type] || [];

    // no markups yet for this block
    if (block.markups[type].length === 0) {
      return block.markups[type].push(markup);
    }

    // this can't get too huge or we'll exceed call stack
    var slots = Array.apply(null, Array(block.text.length)).map(Boolean);
    console.log(slots.length)

    // ranges overlap
    if (this._markupRangesOverlap(block.markups[type], markup)) {

    // no overlaps - add to ranges
    } else {
      return block.markups[type].push(markup);
    }
  }

  _markupRangesOverlap(existing, selection) {
    for (var i = 0, j = existing.length; i < j; i++) {
      var range = existing[i];
      if (range.range[0] <= selection.range[0] ||
          range.range[1] >= selection.range[1]) {
        return true;
      }
    }
    return false;
  }
}

module.exports = ContentManager;
<commit_msg>get single block add/remove overlapping markup working correctly!<commit_after>var Guid = require('./Guid');
var RangeSet = require('./RangeSet');
var ContentFinder = require('./ContentFinder');

// Manages making changes to the content
//
class ContentManager {
  constructor(content) {
    this.content = content;
    this.finder = new ContentFinder(content);
  }

  // add / remove / combine blocks

  appendBlock(guids) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var text  = block.text;
    var starts = text.substring(0, 1);
    var newBlock;

    // finish list
    if (block.type == 'li' && text === '') {
      newBlock = this._finishList(guid);

    // add to a list
    } else if (block.type == 'li') {
      newBlock = this._addListItem(guid);

    // create new list
    } else if (['-', '*', '1'].indexOf(starts) != -1) {
      newBlock = this._createList(guid, starts);

    // normal new block
    } else {
      newBlock = this._insertBlock('p', 'after', guid);
    }
    return { block: newBlock, offset: 0 };
  }
  prependBlock(guids) {
    var guid = guids.anchor;
    var block = this.finder.findBlock(guid);
    var newBlock = this._insertBlock('p', 'before', guid);
    return { block: block, offset: 0 };
  }
  splitBlock(guids, offsets) {
    var range = this.finder.findRange(guids);
    var first = range.shift();
    var last  = range.pop();

    var anchor = this.finder.findBlock(first);
    var focus  = last ? this.finder.findBlock(last) : anchor;

    // delete the rest
    range.forEach( (guid) => { this._removeBlock(guid); });

    var end  = focus.text.substring(offsets.focus);
    var type = focus.type;
    anchor.text = anchor.text.substring(0, offsets.anchor);

    if (anchor.id != focus.id) { this._removeBlock(focus.id); }

    var newBlock = this._insertBlock(type, 'after', guids.anchor, end);
    return { block: newBlock, offset: 0 };
  }
  combineBlocks(guids, offsets) {
    var range = this.finder.findRange(guids);
    var first = range.shift();
    var last  = range.pop();

    var anchor = this.finder.findBlock(first);
    var focus  = last ? this.finder.findBlock(last) : anchor;

    // delete the rest
    range.forEach( (guid) => { this._removeBlock(guid); });

    anchor.text = anchor.text.substring(0, offsets.anchor) +
                  focus.text.substring(offsets.focus);
    if (anchor.id != focus.id) { this._removeBlock(focus.id); }

    return { block: anchor, offset: offsets.anchor };
  }
  combineBlockWithPrevious(guids) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var prev  = this.finder.findPreviousBlock(guid);

    if (prev) {
      var offset = prev.text.length;
      prev.text = prev.text + block.text;
      this._removeBlock(block.id);
      return { block: prev, offset: offset };

    } else {
      return { block: null, offset: 0 };
    }
  }
  combineBlockWithNext(guids) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var next  = this.finder.findNextBlock(guid);

    if (next) {
      var offset = block.text.length;
      block.text = block.text + next.text;
      this._removeBlock(next.id);
      return { block: next, offset: offset };

    } else {
      return { block: null, offset: 0 };
    }
  }

  _addListItem(guid) {
    return this._insertBlock('li', 'after', guid);
  }
  _createList(guid, starts) {
    var type = starts == '1' ? 'ol' : 'ul';

    var block = this.finder.findBlock(guid);
    var text = block.text;
    delete block.text

    block.type = type;
    var items = [
      this._newBlock('li', text.replace(/^[-*\d]\.?\s?/g, '')),
      this._newBlock('li', '')
    ]
    block.blocks = items;
    return items[1];
  }
  _finishList(guid) {
    var parentGuid = this.finder.findParentBlock(guid).id;
    this._removeBlock(guid);
    return this._insertBlock('p', 'after', parentGuid);
  }
  _removeBlock(guid) {
    var blocks = this.finder.findBlocks(guid);
    var index  = this.finder.findBlockPosition(guid);

    blocks.splice(index, 1);
  }
  _insertBlock(type, position, guid, text) {
    var blocks = this.finder.findBlocks(guid);
    var index  = this.finder.findBlockPosition(guid);

    var block = this._newBlock(type, text || "");
    var index = position == 'after' ? index + 1 : index;

    blocks.splice(index, 0, block);

    return block;
  }
  _newBlock(type, text) {
    return { "id": Guid.unique(), "type": type, "text": text }
  }

  // text

  updateText(guids, text) {
    var block = this.finder.findBlock(guids.anchor);
    block.text = text;
  }

  // modifying block type / markup

  toggleCenter(guids, offsets) {
    var range = this.finder.findRange(guids, offsets)
    var blocks = range.map( (guid) => {
      return this.finder.findBlock(guid);
    });

    // check how many are centered
    var notCentered = blocks.filter( (block) => {
      return !block.meta || block.meta.align != 'center';
    })

    if (notCentered.length > 0) {
      notCentered.forEach( (block) => {
        block.meta = block.meta || {}
        block.meta.align = "center";
      });
    } else {
      blocks.forEach( (block) => {
        delete block.meta.align;
      });
    }
  }

  toggleBlockType(guids, offsets, tagName) {
    var range = this.finder.findRange(guids, offsets)
    var blocks = range.map( (guid) => {
      return this.finder.findBlock(guid);
    });

    var notOfType = blocks.filter( (block) => {
      return block.type != tagName;
    })

    if (notOfType.length > 0) {
      var type = notOfType[0].type;
      notOfType.forEach( (block) => { block.type = tagName; });
      return { oldType: type, newType: tagName };

    } else {
      blocks.forEach( (block) => { block.type = 'p'; });
      return { oldType: tagName, newType: 'p' };
    }
  }

  toggleMarkup(guids, offsets, type, value) {
    var guid  = guids.anchor;
    var block = this.finder.findBlock(guid);
    var markup = { "range": [offsets.anchor, offsets.focus] };
    if (value) { markup.value = value; }

    block.markups = block.markups || {}
    block.markups[type] = block.markups[type] || [];

    var set = new RangeSet(block.markups[type]);
    if (set.includes(markup)) {
      block.markups[type] = set.remove(markup);
    } else {
      block.markups[type] = set.add(markup);
    }
  }
}

module.exports = ContentManager;
<|endoftext|>


<commit_before>const discord = require('discord.js');

module.exports = {
  usage: 'server - prints info about the server',
  run: (bot, message) => {
      const embed = new discord.RichEmbed();
      embed.setTitle('Server Owner')
            .setColor('#ff7260')
            .setAuthor(message.guild.name, message.guild.iconURL)
            .setDescription(message.guild.owner.user.username)
            .addField('Members', message.guild.members.size, true)
            .addField('Created', message.guild.createdAt.toString(), true)
            .addField('Emojis',
            message.guild.emojis.size > 0 ? message.guild.emojis.map(d => d.toString()).join(' ') : 'None');
      message.channel.sendEmbed(embed);
    },
};
<commit_msg>Ok, this better be fine<commit_after>const discord = require('discord.js');

module.exports = {
  usage: 'server - prints info about the server',
  run: (bot, message) => {
    const embed = new discord.RichEmbed();
    embed.setTitle('Server Owner')
            .setColor('#ff7260')
            .setAuthor(message.guild.name, message.guild.iconURL)
            .setDescription(message.guild.owner.user.username)
            .addField('Members', message.guild.members.size, true)
            .addField('Created', message.guild.createdAt.toString(), true)
            .addField('Emojis',
            message.guild.emojis.size > 0 ? message.guild.emojis.map(d => d.toString()).join(' ') : 'None');
    message.channel.sendEmbed(embed);
  },
};
<|endoftext|>


<commit_before>/** @jsx React.DOM */
/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2015 Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var bows = require('bows');
var cx = require('classnames');
var moment = require('moment');
var React = require('react');

var debug = bows('Calendar');

var basicsActions = require('../logic/actions');

var ADay = React.createClass({
  propTypes: {
    dayAbbrevMask: React.PropTypes.string.isRequired,
    fullDateMask: React.PropTypes.string.isRequired,
    chart: React.PropTypes.func.isRequired,
    data: React.PropTypes.object,
    date: React.PropTypes.string.isRequired
  },
  getDefaultProps: function() {
    return {
      dayAbbrevMask: 'dd',
      fullDateMask: 'ddd, MMM D'
    };
  },
  render: function() {
    return (

          {moment(this.props.date).format(this.props.dayAbbrevMask)}

        <this.props.chart data={this.props.data}
          date={this.props.date}
          hover={false} />

    );
  }
});

var CalendarContainer = React.createClass({
  propTypes: {
    section: React.PropTypes.string.isRequired,
    component: React.PropTypes.string.isRequired,
    chart: React.PropTypes.func.isRequired,
    data: React.PropTypes.object.isRequired,
    days: React.PropTypes.array.isRequired,
    open: React.PropTypes.bool.isRequired,
    title: React.PropTypes.string.isRequired,
    type: React.PropTypes.string.isRequired
  },
  render: function() {
    var self = this;

    var iconClass = cx({
      'icon-down': this.props.open,
      'icon-right': !this.props.open
    });
    var containerClass = cx({
      'Calendar-container': true,
      'Calendar-container--closed': !this.props.open
    });

    var days = this.renderDays();

    return (

          {this.props.title}

            {days}

    );
  },
  renderDays: function(numWeeks) {
    var self = this;

    return _.map(self.props.days, function(day) {
      return (
        <ADay key={day}
          chart={self.props.chart}
          data={self.props.data[self.props.type]}
          date={day} />
      );
    });
  },
  handleToggleComponent: function(e) {
    debug('Clicked to toggle', this.props.component);
    if (e) {
      e.preventDefault();
    }
    basicsActions.toggleComponent(this.props.section, this.props.component);
  },
  setHeight: function() {
    var content = this.refs.content.getDOMNode();
    var container = this.refs.container.getDOMNode();
    container.style.height = content.offsetHeight + 'px';
  }
});

module.exports = CalendarContainer;<commit_msg>remove inner section header<commit_after>/** @jsx React.DOM */
/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2015 Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var bows = require('bows');
var cx = require('classnames');
var moment = require('moment');
var React = require('react');

var debug = bows('Calendar');

var basicsActions = require('../logic/actions');

var ADay = React.createClass({
  propTypes: {
    dayAbbrevMask: React.PropTypes.string.isRequired,
    fullDateMask: React.PropTypes.string.isRequired,
    chart: React.PropTypes.func.isRequired,
    data: React.PropTypes.object,
    date: React.PropTypes.string.isRequired
  },
  getDefaultProps: function() {
    return {
      dayAbbrevMask: 'dd',
      fullDateMask: 'ddd, MMM D'
    };
  },
  render: function() {
    return (

          {moment(this.props.date).format(this.props.dayAbbrevMask)}

        <this.props.chart data={this.props.data}
          date={this.props.date}
          hover={false} />

    );
  }
});

var CalendarContainer = React.createClass({
  propTypes: {
    section: React.PropTypes.string.isRequired,
    component: React.PropTypes.string.isRequired,
    chart: React.PropTypes.func.isRequired,
    data: React.PropTypes.object.isRequired,
    days: React.PropTypes.array.isRequired,
    open: React.PropTypes.bool.isRequired,
    title: React.PropTypes.string.isRequired,
    type: React.PropTypes.string.isRequired
  },
  render: function() {
    var self = this;

    var iconClass = cx({
      'icon-down': this.props.open,
      'icon-right': !this.props.open
    });
    var containerClass = cx({
      'Calendar-container': true,
      'Calendar-container--closed': !this.props.open
    });

    var days = this.renderDays();

    return (

            {days}

    );
  },
  renderDays: function(numWeeks) {
    var self = this;

    return _.map(self.props.days, function(day) {
      return (
        <ADay key={day}
          chart={self.props.chart}
          data={self.props.data[self.props.type]}
          date={day} />
      );
    });
  },
  handleToggleComponent: function(e) {
    debug('Clicked to toggle', this.props.component);
    if (e) {
      e.preventDefault();
    }
    basicsActions.toggleComponent(this.props.section, this.props.component);
  },
  setHeight: function() {
    var content = this.refs.content.getDOMNode();
    var container = this.refs.container.getDOMNode();
    container.style.height = content.offsetHeight + 'px';
  }
});

module.exports = CalendarContainer;<|endoftext|>