<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Portal from './Portal';
import Fade from './Fade';
import {
  getOriginalBodyPadding,
  conditionallyUpdateScrollbar,
  setScrollbarWidth,
  mapToCssModules,
  omit,
  focusableElements,
  TransitionTimeouts,
  keyCodes,
  targetPropType,
  getTarget,
} from './utils';

function noop() {}

const FadePropTypes = PropTypes.shape(Fade.propTypes);

const propTypes = {
  /** */
  autoFocus: PropTypes.bool,
  /** Add backdrop to modal */
  backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['static'])]),
  /** add custom classname to backdrop */
  backdropClassName: PropTypes.string,
  backdropTransition: FadePropTypes,
  /** Vertically center the modal */
  centered: PropTypes.bool,
  /** Add children for the modal to wrap */
  children: PropTypes.node,
  /** Add custom className for modal content */
  contentClassName: PropTypes.string,
  className: PropTypes.string,
  container: targetPropType,
  cssModule: PropTypes.object,
  external: PropTypes.node,
  /** Enable/Disable animation */
  fade: PropTypes.bool,
  /** Make the modal fullscreen */
  fullscreen: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
  ]),
  innerRef: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string,
    PropTypes.func,
  ]),
  /** The status of the modal, either open or close */
  isOpen: PropTypes.bool,
  /** Allow modal to be closed with escape key. */
  keyboard: PropTypes.bool,
  /** Identifies the element (or elements) that labels the current element. */
  labelledBy: PropTypes.string,
  modalClassName: PropTypes.string,
  modalTransition: FadePropTypes,
  /** Function to be triggered on close */
  onClosed: PropTypes.func,
  /** Function to be triggered on enter */
  onEnter: PropTypes.func,
  /** Function to be triggered on exit */
  onExit: PropTypes.func,
  /** Function to be triggered on open */
  onOpened: PropTypes.func,
  /** Returns focus to the element that triggered opening of the modal */
  returnFocusAfterClose: PropTypes.bool,
  /** Accessibility role */
  role: PropTypes.string,
  /** Make the modal scrollable */
  scrollable: PropTypes.bool,
  /** Two optional sizes `lg` and `sm` */
  size: PropTypes.string,
  /** Function to toggle modal visibility */
  toggle: PropTypes.func,
  trapFocus: PropTypes.bool,
  /** Unmounts the modal when modal is closed */
  unmountOnClose: PropTypes.bool,
  wrapClassName: PropTypes.string,
  zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};

const propsToOmit = Object.keys(propTypes);

const defaultProps = {
  isOpen: false,
  autoFocus: true,
  centered: false,
  scrollable: false,
  role: 'dialog',
  backdrop: true,
  keyboard: true,
  zIndex: 1050,
  fade: true,
  onOpened: noop,
  onClosed: noop,
  modalTransition: {
    timeout: TransitionTimeouts.Modal,
  },
  backdropTransition: {
    mountOnEnter: true,
    timeout: TransitionTimeouts.Fade, // uses standard fade transition
  },
  unmountOnClose: true,
  returnFocusAfterClose: true,
  container: 'body',
  trapFocus: false,
};

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

    this._element = null;
    this._originalBodyPadding = null;
    this._originalBodyOverflow = null;
    this.getFocusableChildren = this.getFocusableChildren.bind(this);
    this.handleBackdropClick = this.handleBackdropClick.bind(this);
    this.handleBackdropMouseDown = this.handleBackdropMouseDown.bind(this);
    this.handleEscape = this.handleEscape.bind(this);
    this.handleStaticBackdropAnimation =
      this.handleStaticBackdropAnimation.bind(this);
    this.handleTab = this.handleTab.bind(this);
    this.onOpened = this.onOpened.bind(this);
    this.onClosed = this.onClosed.bind(this);
    this.manageFocusAfterClose = this.manageFocusAfterClose.bind(this);
    this.clearBackdropAnimationTimeout =
      this.clearBackdropAnimationTimeout.bind(this);
    this.trapFocus = this.trapFocus.bind(this);

    this.state = {
      isOpen: false,
      showStaticBackdropAnimation: false,
    };
  }

  componentDidMount() {
    const { isOpen, autoFocus, onEnter } = this.props;

    if (isOpen) {
      this.init();
      this.setState({ isOpen: true });
      if (autoFocus) {
        this.setFocus();
      }
    }

    if (onEnter) {
      onEnter();
    }

    // traps focus inside the Modal, even if the browser address bar is focused
    document.addEventListener('focus', this.trapFocus, true);

    this._isMounted = true;
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.isOpen && !prevProps.isOpen) {
      this.init();
      this.setState({ isOpen: true });
      // let render() renders Modal Dialog first
      return;
    }

    // now Modal Dialog is rendered and we can refer this._element and this._dialog
    if (this.props.autoFocus && this.state.isOpen && !prevState.isOpen) {
      this.setFocus();
    }

    if (this._element && prevProps.zIndex !== this.props.zIndex) {
      this._element.style.zIndex = this.props.zIndex;
    }
  }

  componentWillUnmount() {
    this.clearBackdropAnimationTimeout();

    if (this.props.onExit) {
      this.props.onExit();
    }

    if (this._element) {
      this.destroy();
      if (this.props.isOpen || this.state.isOpen) {
        this.close();
      }
    }

    document.removeEventListener('focus', this.trapFocus, true);
    this._isMounted = false;
  }

  // not mouseUp because scrollbar fires it, shouldn't close when user scrolls
  handleBackdropClick(e) {
    if (e.target === this._mouseDownElement) {
      e.stopPropagation();

      const backdrop = this._dialog ? this._dialog.parentNode : null;

      if (
        backdrop &&
        e.target === backdrop &&
        this.props.backdrop === 'static'
      ) {
        this.handleStaticBackdropAnimation();
      }

      if (!this.props.isOpen || this.props.backdrop !== true) return;

      if (backdrop && e.target === backdrop && this.props.toggle) {
        this.props.toggle(e);
      }
    }
  }

  handleTab(e) {
    if (e.which !== 9) return;
    if (this.modalIndex < Modal.openCount - 1) return; // last opened modal

    const focusableChildren = this.getFocusableChildren();
    const totalFocusable = focusableChildren.length;
    if (totalFocusable === 0) return;
    const currentFocus = this.getFocusedChild();

    let focusedIndex = 0;

    for (let i = 0; i < totalFocusable; i += 1) {
      if (focusableChildren[i] === currentFocus) {
        focusedIndex = i;
        break;
      }
    }

    if (e.shiftKey && focusedIndex === 0) {
      e.preventDefault();
      focusableChildren[totalFocusable - 1].focus();
    } else if (!e.shiftKey && focusedIndex === totalFocusable - 1) {
      e.preventDefault();
      focusableChildren[0].focus();
    }
  }

  handleBackdropMouseDown(e) {
    this._mouseDownElement = e.target;
  }

  handleEscape(e) {
    if (this.props.isOpen && e.keyCode === keyCodes.esc && this.props.toggle) {
      if (this.props.keyboard) {
        e.preventDefault();
        e.stopPropagation();

        this.props.toggle(e);
      } else if (this.props.backdrop === 'static') {
        e.preventDefault();
        e.stopPropagation();

        this.handleStaticBackdropAnimation();
      }
    }
  }

  handleStaticBackdropAnimation() {
    this.clearBackdropAnimationTimeout();
    this.setState({ showStaticBackdropAnimation: true });
    this._backdropAnimationTimeout = setTimeout(() => {
      this.setState({ showStaticBackdropAnimation: false });
    }, 100);
  }

  onOpened(node, isAppearing) {
    this.props.onOpened();
    (this.props.modalTransition.onEntered || noop)(node, isAppearing);
  }

  onClosed(node) {
    const { unmountOnClose } = this.props;
    // so all methods get called before it is unmounted
    this.props.onClosed();
    (this.props.modalTransition.onExited || noop)(node);

    if (unmountOnClose) {
      this.destroy();
    }
    this.close();

    if (this._isMounted) {
      this.setState({ isOpen: false });
    }
  }

  setFocus() {
    if (
      this._dialog &&
      this._dialog.parentNode &&
      typeof this._dialog.parentNode.focus === 'function'
    ) {
      this._dialog.parentNode.focus();
    }
  }

  getFocusableChildren() {
    return this._element.querySelectorAll(focusableElements.join(', '));
  }

  getFocusedChild() {
    let currentFocus;
    const focusableChildren = this.getFocusableChildren();

    try {
      currentFocus = document.activeElement;
    } catch (err) {
      currentFocus = focusableChildren[0];
    }
    return currentFocus;
  }

  trapFocus(ev) {
    if (!this.props.trapFocus) {
      return;
    }

    if (!this._element) {
      // element is not attached
      return;
    }

    if (this._dialog && this._dialog.parentNode === ev.target) {
      // initial focus when the Modal is opened
      return;
    }

    if (this.modalIndex < Modal.openCount - 1) {
      // last opened modal
      return;
    }

    const children = this.getFocusableChildren();

    for (let i = 0; i < children.length; i += 1) {
      // focus is already inside the Modal
      if (children[i] === ev.target) return;
    }

    if (children.length > 0) {
      // otherwise focus the first focusable element in the Modal
      ev.preventDefault();
      ev.stopPropagation();
      children[0].focus();
    }
  }

  init() {
    try {
      this._triggeringElement = document.activeElement;
    } catch (err) {
      this._triggeringElement = null;
    }

    if (!this._element) {
      this._element = document.createElement('div');
      this._element.setAttribute('tabindex', '-1');
      this._element.style.position = 'relative';
      this._element.style.zIndex = this.props.zIndex;
      this._mountContainer = getTarget(this.props.container);
      this._mountContainer.appendChild(this._element);
    }

    this._originalBodyPadding = getOriginalBodyPadding();
    this._originalBodyOverflow = window.getComputedStyle(
      document.body,
    ).overflow;
    conditionallyUpdateScrollbar();

    if (Modal.openCount === 0) {
      document.body.className = classNames(
        document.body.className,
        mapToCssModules('modal-open', this.props.cssModule),
      );
      document.body.style.overflow = 'hidden';
    }

    this.modalIndex = Modal.openCount;
    Modal.openCount += 1;
  }

  destroy() {
    if (this._element) {
      this._mountContainer.removeChild(this._element);
      this._element = null;
    }

    this.manageFocusAfterClose();
  }

  manageFocusAfterClose() {
    if (this._triggeringElement) {
      const { returnFocusAfterClose } = this.props;
      if (this._triggeringElement.focus && returnFocusAfterClose)
        this._triggeringElement.focus();
      this._triggeringElement = null;
    }
  }

  close() {
    if (Modal.openCount <= 1) {
      const modalOpenClassName = mapToCssModules(
        'modal-open',
        this.props.cssModule,
      );
      // Use regex to prevent matching `modal-open` as part of a different class, e.g. `my-modal-opened`
      const modalOpenClassNameRegex = new RegExp(
        `(^| )${modalOpenClassName}( |$)`,
      );
      document.body.className = document.body.className
        .replace(modalOpenClassNameRegex, ' ')
        .trim();
      document.body.style.overflow = this._originalBodyOverflow;
    }
    this.manageFocusAfterClose();
    Modal.openCount = Math.max(0, Modal.openCount - 1);

    setScrollbarWidth(this._originalBodyPadding);
  }

  clearBackdropAnimationTimeout() {
    if (this._backdropAnimationTimeout) {
      clearTimeout(this._backdropAnimationTimeout);
      this._backdropAnimationTimeout = undefined;
    }
  }

  renderModalDialog() {
    const attributes = omit(this.props, propsToOmit);
    const dialogBaseClass = 'modal-dialog';

    return (
      <div
        {...attributes}
        className={mapToCssModules(
          classNames(dialogBaseClass, this.props.className, {
            [`modal-${this.props.size}`]: this.props.size,
            [`${dialogBaseClass}-centered`]: this.props.centered,
            [`${dialogBaseClass}-scrollable`]: this.props.scrollable,
            'modal-fullscreen': this.props.fullscreen === true,
            [`modal-fullscreen-${this.props.fullscreen}-down`]:
              typeof this.props.fullscreen === 'string',
          }),
          this.props.cssModule,
        )}
        role="document"
        ref={(c) => {
          this._dialog = c;
        }}
      >
        <div
          className={mapToCssModules(
            classNames('modal-content', this.props.contentClassName),
            this.props.cssModule,
          )}
        >
          {this.props.children}
        </div>
      </div>
    );
  }

  render() {
    const { unmountOnClose } = this.props;

    if (!!this._element && (this.state.isOpen || !unmountOnClose)) {
      const isModalHidden =
        !!this._element && !this.state.isOpen && !unmountOnClose;
      this._element.style.display = isModalHidden ? 'none' : 'block';

      const {
        wrapClassName,
        modalClassName,
        backdropClassName,
        cssModule,
        isOpen,
        backdrop,
        role,
        labelledBy,
        external,
        innerRef,
      } = this.props;

      const modalAttributes = {
        onClick: this.handleBackdropClick,
        onMouseDown: this.handleBackdropMouseDown,
        onKeyUp: this.handleEscape,
        onKeyDown: this.handleTab,
        style: { display: 'block' },
        'aria-labelledby': labelledBy,
        role,
        tabIndex: '-1',
      };

      const hasTransition = this.props.fade;
      const modalTransition = {
        ...Fade.defaultProps,
        ...this.props.modalTransition,
        baseClass: hasTransition ? this.props.modalTransition.baseClass : '',
        timeout: hasTransition ? this.props.modalTransition.timeout : 0,
      };
      const backdropTransition = {
        ...Fade.defaultProps,
        ...this.props.backdropTransition,
        baseClass: hasTransition ? this.props.backdropTransition.baseClass : '',
        timeout: hasTransition ? this.props.backdropTransition.timeout : 0,
      };

      const Backdrop =
        backdrop &&
        (hasTransition ? (
          <Fade
            {...backdropTransition}
            in={isOpen && !!backdrop}
            cssModule={cssModule}
            className={mapToCssModules(
              classNames('modal-backdrop', backdropClassName),
              cssModule,
            )}
          />
        ) : (
          <div
            className={mapToCssModules(
              classNames('modal-backdrop', 'show', backdropClassName),
              cssModule,
            )}
          />
        ));

      return (
        <Portal node={this._element}>
          <div className={mapToCssModules(wrapClassName)}>
            <Fade
              {...modalAttributes}
              {...modalTransition}
              in={isOpen}
              onEntered={this.onOpened}
              onExited={this.onClosed}
              cssModule={cssModule}
              className={mapToCssModules(
                classNames(
                  'modal',
                  modalClassName,
                  this.state.showStaticBackdropAnimation && 'modal-static',
                ),
                cssModule,
              )}
              innerRef={innerRef}
            >
              {external}
              {this.renderModalDialog()}
            </Fade>
            {Backdrop}
          </div>
        </Portal>
      );
    }
    return null;
  }
}

Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;
Modal.openCount = 0;

export default Modal;
<commit_msg>fix: ensure initial overflow status is stored when opening multiple modals<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Portal from './Portal';
import Fade from './Fade';
import {
  getOriginalBodyPadding,
  conditionallyUpdateScrollbar,
  setScrollbarWidth,
  mapToCssModules,
  omit,
  focusableElements,
  TransitionTimeouts,
  keyCodes,
  targetPropType,
  getTarget,
} from './utils';

function noop() {}

const FadePropTypes = PropTypes.shape(Fade.propTypes);

const propTypes = {
  /** */
  autoFocus: PropTypes.bool,
  /** Add backdrop to modal */
  backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['static'])]),
  /** add custom classname to backdrop */
  backdropClassName: PropTypes.string,
  backdropTransition: FadePropTypes,
  /** Vertically center the modal */
  centered: PropTypes.bool,
  /** Add children for the modal to wrap */
  children: PropTypes.node,
  /** Add custom className for modal content */
  contentClassName: PropTypes.string,
  className: PropTypes.string,
  container: targetPropType,
  cssModule: PropTypes.object,
  external: PropTypes.node,
  /** Enable/Disable animation */
  fade: PropTypes.bool,
  /** Make the modal fullscreen */
  fullscreen: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
  ]),
  innerRef: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string,
    PropTypes.func,
  ]),
  /** The status of the modal, either open or close */
  isOpen: PropTypes.bool,
  /** Allow modal to be closed with escape key. */
  keyboard: PropTypes.bool,
  /** Identifies the element (or elements) that labels the current element. */
  labelledBy: PropTypes.string,
  modalClassName: PropTypes.string,
  modalTransition: FadePropTypes,
  /** Function to be triggered on close */
  onClosed: PropTypes.func,
  /** Function to be triggered on enter */
  onEnter: PropTypes.func,
  /** Function to be triggered on exit */
  onExit: PropTypes.func,
  /** Function to be triggered on open */
  onOpened: PropTypes.func,
  /** Returns focus to the element that triggered opening of the modal */
  returnFocusAfterClose: PropTypes.bool,
  /** Accessibility role */
  role: PropTypes.string,
  /** Make the modal scrollable */
  scrollable: PropTypes.bool,
  /** Two optional sizes `lg` and `sm` */
  size: PropTypes.string,
  /** Function to toggle modal visibility */
  toggle: PropTypes.func,
  trapFocus: PropTypes.bool,
  /** Unmounts the modal when modal is closed */
  unmountOnClose: PropTypes.bool,
  wrapClassName: PropTypes.string,
  zIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};

const propsToOmit = Object.keys(propTypes);

const defaultProps = {
  isOpen: false,
  autoFocus: true,
  centered: false,
  scrollable: false,
  role: 'dialog',
  backdrop: true,
  keyboard: true,
  zIndex: 1050,
  fade: true,
  onOpened: noop,
  onClosed: noop,
  modalTransition: {
    timeout: TransitionTimeouts.Modal,
  },
  backdropTransition: {
    mountOnEnter: true,
    timeout: TransitionTimeouts.Fade, // uses standard fade transition
  },
  unmountOnClose: true,
  returnFocusAfterClose: true,
  container: 'body',
  trapFocus: false,
};

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

    this._element = null;
    this._originalBodyPadding = null;
    this.getFocusableChildren = this.getFocusableChildren.bind(this);
    this.handleBackdropClick = this.handleBackdropClick.bind(this);
    this.handleBackdropMouseDown = this.handleBackdropMouseDown.bind(this);
    this.handleEscape = this.handleEscape.bind(this);
    this.handleStaticBackdropAnimation =
      this.handleStaticBackdropAnimation.bind(this);
    this.handleTab = this.handleTab.bind(this);
    this.onOpened = this.onOpened.bind(this);
    this.onClosed = this.onClosed.bind(this);
    this.manageFocusAfterClose = this.manageFocusAfterClose.bind(this);
    this.clearBackdropAnimationTimeout =
      this.clearBackdropAnimationTimeout.bind(this);
    this.trapFocus = this.trapFocus.bind(this);

    this.state = {
      isOpen: false,
      showStaticBackdropAnimation: false,
    };
  }

  componentDidMount() {
    const { isOpen, autoFocus, onEnter } = this.props;

    if (isOpen) {
      this.init();
      this.setState({ isOpen: true });
      if (autoFocus) {
        this.setFocus();
      }
    }

    if (onEnter) {
      onEnter();
    }

    // traps focus inside the Modal, even if the browser address bar is focused
    document.addEventListener('focus', this.trapFocus, true);

    this._isMounted = true;
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.props.isOpen && !prevProps.isOpen) {
      this.init();
      this.setState({ isOpen: true });
      // let render() renders Modal Dialog first
      return;
    }

    // now Modal Dialog is rendered and we can refer this._element and this._dialog
    if (this.props.autoFocus && this.state.isOpen && !prevState.isOpen) {
      this.setFocus();
    }

    if (this._element && prevProps.zIndex !== this.props.zIndex) {
      this._element.style.zIndex = this.props.zIndex;
    }
  }

  componentWillUnmount() {
    this.clearBackdropAnimationTimeout();

    if (this.props.onExit) {
      this.props.onExit();
    }

    if (this._element) {
      this.destroy();
      if (this.props.isOpen || this.state.isOpen) {
        this.close();
      }
    }

    document.removeEventListener('focus', this.trapFocus, true);
    this._isMounted = false;
  }

  // not mouseUp because scrollbar fires it, shouldn't close when user scrolls
  handleBackdropClick(e) {
    if (e.target === this._mouseDownElement) {
      e.stopPropagation();

      const backdrop = this._dialog ? this._dialog.parentNode : null;

      if (
        backdrop &&
        e.target === backdrop &&
        this.props.backdrop === 'static'
      ) {
        this.handleStaticBackdropAnimation();
      }

      if (!this.props.isOpen || this.props.backdrop !== true) return;

      if (backdrop && e.target === backdrop && this.props.toggle) {
        this.props.toggle(e);
      }
    }
  }

  handleTab(e) {
    if (e.which !== 9) return;
    if (this.modalIndex < Modal.openCount - 1) return; // last opened modal

    const focusableChildren = this.getFocusableChildren();
    const totalFocusable = focusableChildren.length;
    if (totalFocusable === 0) return;
    const currentFocus = this.getFocusedChild();

    let focusedIndex = 0;

    for (let i = 0; i < totalFocusable; i += 1) {
      if (focusableChildren[i] === currentFocus) {
        focusedIndex = i;
        break;
      }
    }

    if (e.shiftKey && focusedIndex === 0) {
      e.preventDefault();
      focusableChildren[totalFocusable - 1].focus();
    } else if (!e.shiftKey && focusedIndex === totalFocusable - 1) {
      e.preventDefault();
      focusableChildren[0].focus();
    }
  }

  handleBackdropMouseDown(e) {
    this._mouseDownElement = e.target;
  }

  handleEscape(e) {
    if (this.props.isOpen && e.keyCode === keyCodes.esc && this.props.toggle) {
      if (this.props.keyboard) {
        e.preventDefault();
        e.stopPropagation();

        this.props.toggle(e);
      } else if (this.props.backdrop === 'static') {
        e.preventDefault();
        e.stopPropagation();

        this.handleStaticBackdropAnimation();
      }
    }
  }

  handleStaticBackdropAnimation() {
    this.clearBackdropAnimationTimeout();
    this.setState({ showStaticBackdropAnimation: true });
    this._backdropAnimationTimeout = setTimeout(() => {
      this.setState({ showStaticBackdropAnimation: false });
    }, 100);
  }

  onOpened(node, isAppearing) {
    this.props.onOpened();
    (this.props.modalTransition.onEntered || noop)(node, isAppearing);
  }

  onClosed(node) {
    const { unmountOnClose } = this.props;
    // so all methods get called before it is unmounted
    this.props.onClosed();
    (this.props.modalTransition.onExited || noop)(node);

    if (unmountOnClose) {
      this.destroy();
    }
    this.close();

    if (this._isMounted) {
      this.setState({ isOpen: false });
    }
  }

  setFocus() {
    if (
      this._dialog &&
      this._dialog.parentNode &&
      typeof this._dialog.parentNode.focus === 'function'
    ) {
      this._dialog.parentNode.focus();
    }
  }

  getFocusableChildren() {
    return this._element.querySelectorAll(focusableElements.join(', '));
  }

  getFocusedChild() {
    let currentFocus;
    const focusableChildren = this.getFocusableChildren();

    try {
      currentFocus = document.activeElement;
    } catch (err) {
      currentFocus = focusableChildren[0];
    }
    return currentFocus;
  }

  trapFocus(ev) {
    if (!this.props.trapFocus) {
      return;
    }

    if (!this._element) {
      // element is not attached
      return;
    }

    if (this._dialog && this._dialog.parentNode === ev.target) {
      // initial focus when the Modal is opened
      return;
    }

    if (this.modalIndex < Modal.openCount - 1) {
      // last opened modal
      return;
    }

    const children = this.getFocusableChildren();

    for (let i = 0; i < children.length; i += 1) {
      // focus is already inside the Modal
      if (children[i] === ev.target) return;
    }

    if (children.length > 0) {
      // otherwise focus the first focusable element in the Modal
      ev.preventDefault();
      ev.stopPropagation();
      children[0].focus();
    }
  }

  init() {
    try {
      this._triggeringElement = document.activeElement;
    } catch (err) {
      this._triggeringElement = null;
    }

    if (!this._element) {
      this._element = document.createElement('div');
      this._element.setAttribute('tabindex', '-1');
      this._element.style.position = 'relative';
      this._element.style.zIndex = this.props.zIndex;
      this._mountContainer = getTarget(this.props.container);
      this._mountContainer.appendChild(this._element);
    }

    this._originalBodyPadding = getOriginalBodyPadding();
    if (Modal.openCount < 1) {
      Modal.originalBodyOverflow = window.getComputedStyle(
        document.body,
        ).overflow;
    };
    conditionallyUpdateScrollbar();

    if (Modal.openCount === 0) {
      document.body.className = classNames(
        document.body.className,
        mapToCssModules('modal-open', this.props.cssModule),
      );
      document.body.style.overflow = 'hidden';
    }

    this.modalIndex = Modal.openCount;
    Modal.openCount += 1;
  }

  destroy() {
    if (this._element) {
      this._mountContainer.removeChild(this._element);
      this._element = null;
    }

    this.manageFocusAfterClose();
  }

  manageFocusAfterClose() {
    if (this._triggeringElement) {
      const { returnFocusAfterClose } = this.props;
      if (this._triggeringElement.focus && returnFocusAfterClose)
        this._triggeringElement.focus();
      this._triggeringElement = null;
    }
  }

  close() {
    if (Modal.openCount <= 1) {
      const modalOpenClassName = mapToCssModules(
        'modal-open',
        this.props.cssModule,
      );
      // Use regex to prevent matching `modal-open` as part of a different class, e.g. `my-modal-opened`
      const modalOpenClassNameRegex = new RegExp(
        `(^| )${modalOpenClassName}( |$)`,
      );
      document.body.className = document.body.className
        .replace(modalOpenClassNameRegex, ' ')
        .trim();
      document.body.style.overflow = Modal.originalBodyOverflow;
    }
    this.manageFocusAfterClose();
    Modal.openCount = Math.max(0, Modal.openCount - 1);

    setScrollbarWidth(this._originalBodyPadding);
  }

  clearBackdropAnimationTimeout() {
    if (this._backdropAnimationTimeout) {
      clearTimeout(this._backdropAnimationTimeout);
      this._backdropAnimationTimeout = undefined;
    }
  }

  renderModalDialog() {
    const attributes = omit(this.props, propsToOmit);
    const dialogBaseClass = 'modal-dialog';

    return (
      <div
        {...attributes}
        className={mapToCssModules(
          classNames(dialogBaseClass, this.props.className, {
            [`modal-${this.props.size}`]: this.props.size,
            [`${dialogBaseClass}-centered`]: this.props.centered,
            [`${dialogBaseClass}-scrollable`]: this.props.scrollable,
            'modal-fullscreen': this.props.fullscreen === true,
            [`modal-fullscreen-${this.props.fullscreen}-down`]:
              typeof this.props.fullscreen === 'string',
          }),
          this.props.cssModule,
        )}
        role="document"
        ref={(c) => {
          this._dialog = c;
        }}
      >
        <div
          className={mapToCssModules(
            classNames('modal-content', this.props.contentClassName),
            this.props.cssModule,
          )}
        >
          {this.props.children}
        </div>
      </div>
    );
  }

  render() {
    const { unmountOnClose } = this.props;

    if (!!this._element && (this.state.isOpen || !unmountOnClose)) {
      const isModalHidden =
        !!this._element && !this.state.isOpen && !unmountOnClose;
      this._element.style.display = isModalHidden ? 'none' : 'block';

      const {
        wrapClassName,
        modalClassName,
        backdropClassName,
        cssModule,
        isOpen,
        backdrop,
        role,
        labelledBy,
        external,
        innerRef,
      } = this.props;

      const modalAttributes = {
        onClick: this.handleBackdropClick,
        onMouseDown: this.handleBackdropMouseDown,
        onKeyUp: this.handleEscape,
        onKeyDown: this.handleTab,
        style: { display: 'block' },
        'aria-labelledby': labelledBy,
        role,
        tabIndex: '-1',
      };

      const hasTransition = this.props.fade;
      const modalTransition = {
        ...Fade.defaultProps,
        ...this.props.modalTransition,
        baseClass: hasTransition ? this.props.modalTransition.baseClass : '',
        timeout: hasTransition ? this.props.modalTransition.timeout : 0,
      };
      const backdropTransition = {
        ...Fade.defaultProps,
        ...this.props.backdropTransition,
        baseClass: hasTransition ? this.props.backdropTransition.baseClass : '',
        timeout: hasTransition ? this.props.backdropTransition.timeout : 0,
      };

      const Backdrop =
        backdrop &&
        (hasTransition ? (
          <Fade
            {...backdropTransition}
            in={isOpen && !!backdrop}
            cssModule={cssModule}
            className={mapToCssModules(
              classNames('modal-backdrop', backdropClassName),
              cssModule,
            )}
          />
        ) : (
          <div
            className={mapToCssModules(
              classNames('modal-backdrop', 'show', backdropClassName),
              cssModule,
            )}
          />
        ));

      return (
        <Portal node={this._element}>
          <div className={mapToCssModules(wrapClassName)}>
            <Fade
              {...modalAttributes}
              {...modalTransition}
              in={isOpen}
              onEntered={this.onOpened}
              onExited={this.onClosed}
              cssModule={cssModule}
              className={mapToCssModules(
                classNames(
                  'modal',
                  modalClassName,
                  this.state.showStaticBackdropAnimation && 'modal-static',
                ),
                cssModule,
              )}
              innerRef={innerRef}
            >
              {external}
              {this.renderModalDialog()}
            </Fade>
            {Backdrop}
          </div>
        </Portal>
      );
    }
    return null;
  }
}

Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;
Modal.openCount = 0;
Modal.originalBodyOverflow = null;

export default Modal;
<|endoftext|>


<commit_before>var Unit = {};

Unit.labels = { ohms : '\u2126', kilo_ohms : 'k\u2126', mega_ohms : 'M\u2126' };

Unit.normalizeToOhms = function(value, unit) {
    switch (unit) {
    case Unit.labels.ohms:
        return value;
    case Unit.labels.kilo_ohms:
        return value * 1000;
    case Unit.labels.mega_ohms:
        return value * 1e6;
    }
    return null;
};

Unit.ohmCompatible = function(unit) {
    if (unit == Unit.labels.ohms || unit == Unit.labels.kilo_ohms ||
        unit == Unit.labels.mega_ohms)
    {
        return true;
    }
    return false;
};

// Return a string with a unit representing the resistance value.
// value: resistance value in ohms
Unit.res_str = function(value) {
    var vstr;
    var unit;

    if (value < 1000) {
        vstr = String(value);
        unit = Unit.labels.ohms;
    }
    else {
        vstr = String(value / 1000);
        unit = Unit.labels.kilo_ohms;
    }

    var n = vstr.match('.') ?  4 : 3;
    if (vstr.length > n) {
        vstr = vstr.substring(0, n);
    }
    return vstr + unit;
};
<commit_msg>Fixed string representation of correct resistance<commit_after>var Unit = {};

Unit.labels = { ohms : '\u2126', kilo_ohms : 'k\u2126', mega_ohms : 'M\u2126' };

Unit.normalizeToOhms = function(value, unit) {
    switch (unit) {
    case Unit.labels.ohms:
        return value;
    case Unit.labels.kilo_ohms:
        return value * 1000;
    case Unit.labels.mega_ohms:
        return value * 1e6;
    }
    return null;
};

Unit.ohmCompatible = function(unit) {
    if (unit == Unit.labels.ohms || unit == Unit.labels.kilo_ohms ||
        unit == Unit.labels.mega_ohms)
    {
        return true;
    }
    return false;
};

// Return a string with a unit representing the resistance value.
// value: resistance value in ohms
Unit.res_str = function(value) {
    var vstr;
    var unit;

    if (value < 1000) {
        vstr = String(value);
        unit = Unit.labels.ohms;
    }
    else {
        vstr = String(value / 1000);
        unit = Unit.labels.kilo_ohms;
    }

    var n = vstr.match('.') ?  4 : 3;
    if (vstr.charAt(0) == '1') { ++n };
    if (vstr.length > n) {
        vstr = vstr.substring(0, n);
    }
    return vstr + unit;
};
<|endoftext|>


<commit_before>// Debug tasks with:
// iron-node $(which gulp) build

'use strict';

var gulp          = require('gulp');
var filter        = require('gulp-filter');
var util          = require('gulp-util');
var htmlmin       = require('gulp-htmlmin');
var ignore        = require('gulp-ignore');
var rename        = require('gulp-rename');
var markedGulp    = require('gulp-markdown');
var frontMatter   = require('gulp-front-matter');

var through2      = require('through2');
var es            = require('event-stream');
var runSequence   = require('run-sequence');
var webpack       = require('webpack');
var settings      = require('./config/settings');
var argv          = require('minimist')(process.argv.slice(2));
var path          = require('path');
var _             = require("lodash");
var fs            = require("fs");
var mkdirp        = require("mkdirp");
var ejs           = require('ejs');
var moment        = require('moment');
var marked        = require('marked');
var highlight     = require('highlight.js');

// Settings
var release       = argv.release;
var stage         = release ? "production" : "development";
var outputPath    = release ? settings.prodOutput : settings.devOutput;
var webpackConfig = require('./config/webpack.config')(stage);
var webpackStats;

var defaultLayout   = 'application.html';
var tagsTemplate    = fs.readFileSync(path.join(__dirname, './html/partials/_tag.html'), 'utf8');
var archiveTemplate = fs.readFileSync(path.join(__dirname, './html/partials/_archive.html'), 'utf8');

var dateRegEx     = /(\d{4})-(\d{1,2})-(\d{1,2})-(.*)/; // Regex used to parse date from file name

var site          = require('./site.json');
site.time         = new Date();

var markedOptions = {
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: true,
  smartypants: false,
  highlight: function (code, language){
    if(language){
      // Check whether the given language is valid for highlight.js.
      const validLang = !!(language && highlight.getLanguage(language));
      // Highlight only if the language is valid.
      const highlighted = validLang ? highlight.highlight(language, code).value : code;
      // Render the highlighted code with `hljs` class.
      return `<pre><code class="hljs ${language}">${highlighted}</code></pre>`;
    } else {
      return require('highlight.js').highlightAuto(code).value;
    }
  }
};
marked.setOptions(markedOptions);

// -----------------------------------------------------------------------------
// Clean up
// -----------------------------------------------------------------------------
gulp.task('clean', function(cb){
  var rimraf = require('rimraf');
  rimraf(outputPath, cb);
});

// -----------------------------------------------------------------------------
// Copy vendor files
// -----------------------------------------------------------------------------
// Use this task to copy assets (ie fonts) from node modules
gulp.task('vendor', function(){
  return es.merge(
    gulp.src('./node_modules/bootstrap-sass/assets/fonts/**')
      .pipe(gulp.dest(outputPath + '/fonts')),
    gulp.src('./node_modules/font-awesome/fonts/**')
      .pipe(gulp.dest(outputPath + '/fonts'))
  );
});

// -----------------------------------------------------------------------------
// Build markdown files
// -----------------------------------------------------------------------------
gulp.task('markdown', function(){

  return gulp.src(['./html/**/*.md', './html/**/*.markdown'])
    .pipe(frontMatter({property: 'metadata', remove: true}))  // Strips front matter and adds it to the metadata object
    .pipe(collectMetaData('<!--more-->'))                     // Finds all files with the layout "post" and adds 'summary' to metadata object. Summarize posts by adding <!--more--> to the html
    .pipe(markedGulp(markedOptions))
    .pipe(applyLayout(defaultLayout))
    .pipe(filename2date())
    .pipe(applyWebpack()) // Change to webpack hashed file names in release
    .pipe(!release ? util.noop() : htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      minifyJS: true
    }))
    .pipe(rename(function (path) {
      path.extname = ".html";
      var match = dateRegEx.exec(path.basename);
      if (match){
        var year = match[1];
        var month = match[2];
        var day = match[3];

        path.dirname = year + '/' + month + '/' + day;
        path.basename = match[4];
      }
    }))
    .pipe(rename({extname: '.html'}))
    .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Process files in the html diretory
// -----------------------------------------------------------------------------
gulp.task('html', ['markdown'], function(){

  var htmlFilter = filter('**/*.html', {restore: true});

  return gulp.src('./html/**/*')
    .pipe(ignore.exclude('layouts/**'))
    .pipe(ignore.exclude('layouts'))
    .pipe(ignore.exclude('partials/**'))
    .pipe(ignore.exclude('partials'))
    .pipe(ignore.exclude('**/*.md'))
    .pipe(htmlFilter)
    .pipe(applyLayout(defaultLayout))
    .pipe(applyWebpack()) // Change to webpack hashed file names in release
    .pipe(!release ? util.noop() : htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      minifyJS: true
    }))
    .pipe(htmlFilter.restore)
    .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Build pages for navigating with tags
// -----------------------------------------------------------------------------
gulp.task('tags', ['markdown'], function () {
  return tags()
    .pipe(applyLayout(defaultLayout))
    .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Add paging for old posts
// -----------------------------------------------------------------------------
gulp.task('archive', ['markdown'], function () {
  return posts('page', 10)
      .pipe(applyLayout(defaultLayout))
      .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Create JavaScript bundle
// -----------------------------------------------------------------------------
gulp.task('javascript', function(cb){

  var bundler = webpack(webpackConfig);
  function bundle(err, stats){
    webpackStats = stats.toJson();
    if (err){
      throw new util.PluginError('webpack', err);
    }
    //util.log('[webpack]', stats.toString({colors: true}));
    cb();
  }
  bundler.run(bundle);
});

// -----------------------------------------------------------------------------
// Build the app from source code
// -----------------------------------------------------------------------------
gulp.task('build', ['clean'], function(cb){
  runSequence('javascript', ['markdown', 'html', 'archive', 'tags'], cb);
});

// -----------------------------------------------------------------------------
// Watch for changes to html files and rebuild as needed. Webpack watches everything else
// and serves js, css, etc from memory. Html files are served from the build directory.
// -----------------------------------------------------------------------------
gulp.task('watch', ['markdown', 'html'], function() {
  gulp.watch(['html/**/*'], ['markdown', 'html']);
});

// -----------------------------------------------------------------------------
// The default task
// -----------------------------------------------------------------------------
gulp.task('default', ['watch']);

// *****************************************************************************
// A bit hackish. This function can't be called until after the javascript task has been run
// because webpackStats is undefined until webpack has run.
// *****************************************************************************
function getHashed(entryPoint, ext){
  return _.find(webpackStats.assetsByChunkName[entryPoint], function(hashEntry){
    return path.extname(hashEntry).toLowerCase() === '.' + ext;
  });
}

// *****************************************************************************
// Changes webpack paths as needed.
// *****************************************************************************
function applyWebpack(){
  return through2.obj(function (html, enc, cb) { // replace entry points with names generated by webpack
    if(release){
      var result = String(html.contents);
      _.each(webpackConfig.entry, function(path, entry){
        if(_.has(settings.entries, entry)){
          result = result.replace(entry + settings.buildSuffix, getHashed(entry, 'js'));
        } else if(_.has(settings.cssEntries, entry)){
          result = result.replace(entry + '.css', getHashed(entry, 'css'));
        }
      });
      html.contents = new Buffer(result);
    }
    this.push(html);
    cb();
  });
}

// *****************************************************************************
// Apply layouts to content
// *****************************************************************************
function applyLayout(defaultLayout){
  return through2.obj(function (file, enc, cb) {

    var data = {
      site: site,
      metadata: file.metadata || {},
      moment: moment,
      "_": _
    };

    // Allow ejs code in content
    data.content = ejs.compile(String(file.contents), {
      cache: false,
      filename: file.path
    })(data);

    // If the user has specified a layout in the front matter user that.
    // Otherwise use the default
    var layout = defaultLayout;

    if(file.metadata && file.metadata.layout){
      layout = file.metadata.layout;
    }

    // Home page is a special case
    if(file.path == "index.html"){
      layout = "home";
    }

    layout = path.extname(layout) ? layout : layout + '.html';
    layout = path.join(__dirname, './html/layouts/' + layout);

    var template = ejs.compile(fs.readFileSync(layout, 'utf8'), {
      cache: false,
      filename: layout
    });

    file.contents = new Buffer(template(data), 'utf8');
    this.push(file);
    cb();

  });
}

// *****************************************************************************
// Collects all files with layout "post" and adds the page and it's tags to arrays on the
// site object called "posts" and "tags". This can later be used to generate a home page.
// *****************************************************************************
function collectMetaData(marker) {
  var posts = [];
  var tags = {};
  return through2.obj(function(file, enc, cb) {

    // Only collect files with layout "post"
    if(file.metadata.layout == "post"){

      file.metadata.content = file.contents.toString();
      file.metadata.tags = _.reduce(file.metadata.tags, function(acc, tag){ acc[tag] = cleanTag(tag); return acc;}, {});

      var summary;
      if(_.includes(file.metadata.content, marker)){
        summary = file.metadata.content.split(marker)[0];
      } else {
        summary = _.truncate(file.metadata.content, {
          'length': 1000,
          'separator': ' '
        });
      }

      file.metadata.summary = marked(summary);

      tags = _.merge(tags, file.metadata.tags);
      posts.push(file.metadata);
    }

    this.push(file);
    cb();
  },
  function(cb) {
    posts.sort(function(a, b) {
      return b.date - a.date;
    });
    site.posts = posts;
    site.tags = tags;
    cb();
  });
}

// *****************************************************************************
// Make tags snake case
// *****************************************************************************
function cleanTag(tag){
  return _.snakeCase(tag);
}

// *****************************************************************************
// Use the filename to build a directory of dates
// *****************************************************************************
function filename2date() {
  return through2.obj(function(file, enc, cb) {
    var basename = path.basename(file.path, '.md');
    var match = dateRegEx.exec(basename);
    if(match) {
      var year = match[1];
      var month = match[2];
      var day = match[3];
      var basename = match[4];
      file.metadata.date = moment(month + '-' + day + '-' + year, "MM-DD-YYYY");
      file.metadata.url = '/' + year + '/' + month + '/' + day + '/' + basename + '.html';
    }

    this.push(file);
    cb();
  });
}

// *****************************************************************************
// Generate a file for every tag found. These will later have a template applied
// and their internal content evaluated by ejs.
// *****************************************************************************
function tags(){
  var stream = through2.obj(function(file, enc, cb) {
    this.push(file);
    cb();
  });

  if(site.tags){
    _.each(site.tags, function(cleanTag, tag) {
      var file = new util.File({
        path: path.join(site.tagsPath, cleanTag) + '.html',
        contents: new Buffer(tagsTemplate)
      });
      file.metadata = {
        title: tag,
        tag: tag,
        cleanTag: cleanTag
      };
      stream.write(file);
    });
  }

  stream.end();
  stream.emit("end");

  return stream;
}

// *****************************************************************************
// Generate files for paging through all posts
// *****************************************************************************
function posts(basename, count) {

  var stream = through2.obj(function(file, enc, cb) {
    this.push(file);
    cb();
  });

  if(site.posts){
    var c     = 0;
    var page  = 0;
    var posts = [];
    _.each(site.posts, function(post){
      posts.push(post);
      c++;
      if (c == count){
        var file = new util.File({
          path: basename + (page == 0 ? '' : page) + '.html',
          contents: new Buffer('')
        });
        console.log('page=' + page + ' c=' + c + ' posts.length=' + site.posts.length);
        file.metadata = {
          posts: posts,
          prevPage: page != 0 ? basename + ((page-1) == 0 ? '' : page-1) + '.html' : null,
          nextPage: (page+1) * count < site.posts.length ? basename + (page+1) + '.html' : null,
        };
        stream.write(file);

        c = 0;
        posts = [];
        page++;
      }
    });

    if (posts.length != 0) {
      var file = new util.File({
        path: basename + (page == 0 ? '' : page) + '.html',
        contents: new Buffer(archiveTemplate)
      });
      file.metadata = {
        posts: posts,
        prevPage: page != 0 ? basename + ((page-1) == 0 ? '' : page) + '.html' : null,
        nextPage: null,
      };
      stream.write(file);
    }
  }

  stream.end();
  stream.emit("end");

  return stream;
}

<commit_msg>Excludes markdown from html task<commit_after>// Debug tasks with:
// iron-node $(which gulp) build

'use strict';

var gulp          = require('gulp');
var filter        = require('gulp-filter');
var util          = require('gulp-util');
var htmlmin       = require('gulp-htmlmin');
var ignore        = require('gulp-ignore');
var rename        = require('gulp-rename');
var markedGulp    = require('gulp-markdown');
var frontMatter   = require('gulp-front-matter');

var through2      = require('through2');
var es            = require('event-stream');
var runSequence   = require('run-sequence');
var webpack       = require('webpack');
var settings      = require('./config/settings');
var argv          = require('minimist')(process.argv.slice(2));
var path          = require('path');
var _             = require("lodash");
var fs            = require("fs");
var mkdirp        = require("mkdirp");
var ejs           = require('ejs');
var moment        = require('moment');
var marked        = require('marked');
var highlight     = require('highlight.js');

// Settings
var release       = argv.release;
var stage         = release ? "production" : "development";
var outputPath    = release ? settings.prodOutput : settings.devOutput;
var webpackConfig = require('./config/webpack.config')(stage);
var webpackStats;

var defaultLayout   = 'application.html';
var tagsTemplate    = fs.readFileSync(path.join(__dirname, './html/partials/_tag.html'), 'utf8');
var archiveTemplate = fs.readFileSync(path.join(__dirname, './html/partials/_archive.html'), 'utf8');

var dateRegEx     = /(\d{4})-(\d{1,2})-(\d{1,2})-(.*)/; // Regex used to parse date from file name

var site          = require('./site.json');
site.time         = new Date();

var markedOptions = {
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: true,
  smartypants: false,
  highlight: function (code, language){
    if(language){
      // Check whether the given language is valid for highlight.js.
      const validLang = !!(language && highlight.getLanguage(language));
      // Highlight only if the language is valid.
      const highlighted = validLang ? highlight.highlight(language, code).value : code;
      // Render the highlighted code with `hljs` class.
      return `<pre><code class="hljs ${language}">${highlighted}</code></pre>`;
    } else {
      return require('highlight.js').highlightAuto(code).value;
    }
  }
};
marked.setOptions(markedOptions);

// -----------------------------------------------------------------------------
// Clean up
// -----------------------------------------------------------------------------
gulp.task('clean', function(cb){
  var rimraf = require('rimraf');
  rimraf(outputPath, cb);
});

// -----------------------------------------------------------------------------
// Copy vendor files
// -----------------------------------------------------------------------------
// Use this task to copy assets (ie fonts) from node modules
gulp.task('vendor', function(){
  return es.merge(
    gulp.src('./node_modules/bootstrap-sass/assets/fonts/**')
      .pipe(gulp.dest(outputPath + '/fonts')),
    gulp.src('./node_modules/font-awesome/fonts/**')
      .pipe(gulp.dest(outputPath + '/fonts'))
  );
});

// -----------------------------------------------------------------------------
// Build markdown files
// -----------------------------------------------------------------------------
gulp.task('markdown', function(){

  return gulp.src(['./html/**/*.md', './html/**/*.markdown'])
    .pipe(frontMatter({property: 'metadata', remove: true}))  // Strips front matter and adds it to the metadata object
    .pipe(collectMetaData('<!--more-->'))                     // Finds all files with the layout "post" and adds 'summary' to metadata object. Summarize posts by adding <!--more--> to the html
    .pipe(markedGulp(markedOptions))
    .pipe(applyLayout(defaultLayout))
    .pipe(filename2date())
    .pipe(applyWebpack()) // Change to webpack hashed file names in release
    .pipe(!release ? util.noop() : htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      minifyJS: true
    }))
    .pipe(rename(function (path) {
      path.extname = ".html";
      var match = dateRegEx.exec(path.basename);
      if (match){
        var year = match[1];
        var month = match[2];
        var day = match[3];

        path.dirname = year + '/' + month + '/' + day;
        path.basename = match[4];
      }
    }))
    .pipe(rename({extname: '.html'}))
    .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Process files in the html diretory
// -----------------------------------------------------------------------------
gulp.task('html', ['markdown'], function(){

  var htmlFilter = filter('**/*.html', {restore: true});

  return gulp.src('./html/**/*')
    .pipe(ignore.exclude('layouts/**'))
    .pipe(ignore.exclude('layouts'))
    .pipe(ignore.exclude('partials/**'))
    .pipe(ignore.exclude('partials'))
    .pipe(ignore.exclude('**/*.md'))
    .pipe(ignore.exclude('**/*.markdown'))
    .pipe(htmlFilter)
    .pipe(applyLayout(defaultLayout))
    .pipe(applyWebpack()) // Change to webpack hashed file names in release
    .pipe(!release ? util.noop() : htmlmin({
      removeComments: true,
      collapseWhitespace: true,
      minifyJS: true
    }))
    .pipe(htmlFilter.restore)
    .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Build pages for navigating with tags
// -----------------------------------------------------------------------------
gulp.task('tags', ['markdown'], function () {
  return tags()
    .pipe(applyLayout(defaultLayout))
    .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Add paging for old posts
// -----------------------------------------------------------------------------
gulp.task('archive', ['markdown'], function () {
  return posts('page', 10)
      .pipe(applyLayout(defaultLayout))
      .pipe(gulp.dest(outputPath));
});

// -----------------------------------------------------------------------------
// Create JavaScript bundle
// -----------------------------------------------------------------------------
gulp.task('javascript', function(cb){

  var bundler = webpack(webpackConfig);
  function bundle(err, stats){
    webpackStats = stats.toJson();
    if (err){
      throw new util.PluginError('webpack', err);
    }
    //util.log('[webpack]', stats.toString({colors: true}));
    cb();
  }
  bundler.run(bundle);
});

// -----------------------------------------------------------------------------
// Build the app from source code
// -----------------------------------------------------------------------------
gulp.task('build', ['clean'], function(cb){
  runSequence('javascript', ['markdown', 'html', 'archive', 'tags'], cb);
});

// -----------------------------------------------------------------------------
// Watch for changes to html files and rebuild as needed. Webpack watches everything else
// and serves js, css, etc from memory. Html files are served from the build directory.
// -----------------------------------------------------------------------------
gulp.task('watch', ['markdown', 'html'], function() {
  gulp.watch(['html/**/*'], ['markdown', 'html']);
});

// -----------------------------------------------------------------------------
// The default task
// -----------------------------------------------------------------------------
gulp.task('default', ['watch']);

// *****************************************************************************
// A bit hackish. This function can't be called until after the javascript task has been run
// because webpackStats is undefined until webpack has run.
// *****************************************************************************
function getHashed(entryPoint, ext){
  return _.find(webpackStats.assetsByChunkName[entryPoint], function(hashEntry){
    return path.extname(hashEntry).toLowerCase() === '.' + ext;
  });
}

// *****************************************************************************
// Changes webpack paths as needed.
// *****************************************************************************
function applyWebpack(){
  return through2.obj(function (html, enc, cb) { // replace entry points with names generated by webpack
    if(release){
      var result = String(html.contents);
      _.each(webpackConfig.entry, function(path, entry){
        if(_.has(settings.entries, entry)){
          result = result.replace(entry + settings.buildSuffix, getHashed(entry, 'js'));
        } else if(_.has(settings.cssEntries, entry)){
          result = result.replace(entry + '.css', getHashed(entry, 'css'));
        }
      });
      html.contents = new Buffer(result);
    }
    this.push(html);
    cb();
  });
}

// *****************************************************************************
// Apply layouts to content
// *****************************************************************************
function applyLayout(defaultLayout){
  return through2.obj(function (file, enc, cb) {

    var data = {
      site: site,
      metadata: file.metadata || {},
      moment: moment,
      "_": _
    };

    // Allow ejs code in content
    data.content = ejs.compile(String(file.contents), {
      cache: false,
      filename: file.path
    })(data);

    // If the user has specified a layout in the front matter user that.
    // Otherwise use the default
    var layout = defaultLayout;

    if(file.metadata && file.metadata.layout){
      layout = file.metadata.layout;
    }

    // Home page is a special case
    if(file.path == "index.html"){
      layout = "home";
    }

    layout = path.extname(layout) ? layout : layout + '.html';
    layout = path.join(__dirname, './html/layouts/' + layout);

    var template = ejs.compile(fs.readFileSync(layout, 'utf8'), {
      cache: false,
      filename: layout
    });

    file.contents = new Buffer(template(data), 'utf8');
    this.push(file);
    cb();

  });
}

// *****************************************************************************
// Collects all files with layout "post" and adds the page and it's tags to arrays on the
// site object called "posts" and "tags". This can later be used to generate a home page.
// *****************************************************************************
function collectMetaData(marker) {
  var posts = [];
  var tags = {};
  return through2.obj(function(file, enc, cb) {

    // Only collect files with layout "post"
    if(file.metadata.layout == "post"){

      file.metadata.content = file.contents.toString();
      file.metadata.tags = _.reduce(file.metadata.tags, function(acc, tag){ acc[tag] = cleanTag(tag); return acc;}, {});

      var summary;
      if(_.includes(file.metadata.content, marker)){
        summary = file.metadata.content.split(marker)[0];
      } else {
        summary = _.truncate(file.metadata.content, {
          'length': 1000,
          'separator': ' '
        });
      }

      file.metadata.summary = marked(summary);

      tags = _.merge(tags, file.metadata.tags);
      posts.push(file.metadata);
    }

    this.push(file);
    cb();
  },
  function(cb) {
    posts.sort(function(a, b) {
      return b.date - a.date;
    });
    site.posts = posts;
    site.tags = tags;
    cb();
  });
}

// *****************************************************************************
// Make tags snake case
// *****************************************************************************
function cleanTag(tag){
  return _.snakeCase(tag);
}

// *****************************************************************************
// Use the filename to build a directory of dates
// *****************************************************************************
function filename2date() {
  return through2.obj(function(file, enc, cb) {
    var basename = path.basename(file.path, '.md');
    var match = dateRegEx.exec(basename);
    if(match) {
      var year = match[1];
      var month = match[2];
      var day = match[3];
      var basename = match[4];
      file.metadata.date = moment(month + '-' + day + '-' + year, "MM-DD-YYYY");
      file.metadata.url = '/' + year + '/' + month + '/' + day + '/' + basename + '.html';
    }

    this.push(file);
    cb();
  });
}

// *****************************************************************************
// Generate a file for every tag found. These will later have a template applied
// and their internal content evaluated by ejs.
// *****************************************************************************
function tags(){
  var stream = through2.obj(function(file, enc, cb) {
    this.push(file);
    cb();
  });

  if(site.tags){
    _.each(site.tags, function(cleanTag, tag) {
      var file = new util.File({
        path: path.join(site.tagsPath, cleanTag) + '.html',
        contents: new Buffer(tagsTemplate)
      });
      file.metadata = {
        title: tag,
        tag: tag,
        cleanTag: cleanTag
      };
      stream.write(file);
    });
  }

  stream.end();
  stream.emit("end");

  return stream;
}

// *****************************************************************************
// Generate files for paging through all posts
// *****************************************************************************
function posts(basename, count) {

  var stream = through2.obj(function(file, enc, cb) {
    this.push(file);
    cb();
  });

  if(site.posts){
    var c     = 0;
    var page  = 0;
    var posts = [];
    _.each(site.posts, function(post){
      posts.push(post);
      c++;
      if (c == count){
        var file = new util.File({
          path: basename + (page == 0 ? '' : page) + '.html',
          contents: new Buffer('')
        });
        console.log('page=' + page + ' c=' + c + ' posts.length=' + site.posts.length);
        file.metadata = {
          posts: posts,
          prevPage: page != 0 ? basename + ((page-1) == 0 ? '' : page-1) + '.html' : null,
          nextPage: (page+1) * count < site.posts.length ? basename + (page+1) + '.html' : null,
        };
        stream.write(file);

        c = 0;
        posts = [];
        page++;
      }
    });

    if (posts.length != 0) {
      var file = new util.File({
        path: basename + (page == 0 ? '' : page) + '.html',
        contents: new Buffer(archiveTemplate)
      });
      file.metadata = {
        posts: posts,
        prevPage: page != 0 ? basename + ((page-1) == 0 ? '' : page) + '.html' : null,
        nextPage: null,
      };
      stream.write(file);
    }
  }

  stream.end();
  stream.emit("end");

  return stream;
}

<|endoftext|>


<commit_before>const Promise = require('bluebird');

const path = require('path');
const gutil = require('gulp-util');
const through = require('through2');
const html2pug = require('html2pug');
const streamToString = require('stream-to-string');

const PluginError = gutil.PluginError;

// consts
const PLUGIN_NAME = 'gulp-html2pug';

function getHtml(file, enc) {
  if (file.isBuffer()) {
    return Promise.resolve(file.contents.toString(enc));
  } else if (file.isStream()) {
    return streamToString(file.contents);
  }

  throw new PluginError(PLUGIN_NAME, 'Invalid file');
}

// plugin level function (dealing with files)
function gulpHtml2pug() {
  // creating a stream through which each file will pass
  return through.obj(function (file, enc, cb) {
    const newFile = file.clone();

    getHtml(file, enc)
    .then(html => {
      const pug = html2pug(html);

      if (file.isBuffer()) {
        newFile.contents = new Buffer(pug);
      } else if (file.isStream()) {
        // start the transformation
        newFile.contents = through();
        newFile.contents.write(pug);
        newFile.contents.end();
      } else {
        throw new PluginError(PLUGIN_NAME, 'Invalid file');
      }

      const dirname = path.dirname(file.path);
      const basename = path.basename(file.path, path.extname(file.path));
      newFile.path = path.join(dirname, `${basename}.pug`);
      // make sure the file goes through the next gulp plugin
      this.push(newFile);
      cb();
    });
  });
}

// exporting the plugin main function
module.exports = gulpHtml2pug;
<commit_msg>Use es6 modules<commit_after>import Promise from 'bluebird';

import path from 'path';
import through from 'through2';
import html2pug from 'html2pug';
import streamToString from 'stream-to-string';
import { PluginError } from 'gulp-util';

// consts
const PLUGIN_NAME = 'gulp-html2pug';

function getHtml(file, enc) {
  if (file.isBuffer()) {
    return Promise.resolve(file.contents.toString(enc));
  } else if (file.isStream()) {
    return streamToString(file.contents);
  }

  throw new PluginError(PLUGIN_NAME, 'Invalid file');
}

// plugin level function (dealing with files)
function gulpHtml2pug() {
  // creating a stream through which each file will pass
  return through.obj(function (file, enc, cb) {
    const newFile = file.clone();

    getHtml(file, enc)
    .then(html => {
      const pug = html2pug(html);

      if (file.isBuffer()) {
        newFile.contents = new Buffer(pug);
      } else if (file.isStream()) {
        // start the transformation
        newFile.contents = through();
        newFile.contents.write(pug);
        newFile.contents.end();
      } else {
        throw new PluginError(PLUGIN_NAME, 'Invalid file');
      }

      const dirname = path.dirname(file.path);
      const basename = path.basename(file.path, path.extname(file.path));
      newFile.path = path.join(dirname, `${basename}.pug`);
      // make sure the file goes through the next gulp plugin
      this.push(newFile);
      cb();
    });
  });
}

// exporting the plugin main function
module.exports = gulpHtml2pug;
<|endoftext|>


<commit_before>fbf = {}

fbf.FRAMES_PER_SECOND = 25
fbf.PLAYER_ID = "movie_player"

fbf.prevFrame = function() {
    // Based on YouTube enhancer userscript, http://userscripts.org/scripts/show/33042.
    player = document.getElementById(fbf.PLAYER_ID)
    player.pauseVideo()
    player.seekBy(-1 * (1/fbf.FRAMES_PER_SECOND))
}

fbf.nextFrame = function() {
    // Based on YouTube enhancer userscript, http://userscripts.org/scripts/show/33042.
    player = document.getElementById(fbf.PLAYER_ID)
    player.pauseVideo()
    player.seekBy(1 * (1/fbf.FRAMES_PER_SECOND))
}

fbf.injectControls = function() {
    /*
    Injects extra player controls into the page.
    */
    // Really basic controls to get started.
    controls_html = "(prev/next)"
    title_element = document.getElementById("watch-headline-title")
    title_element.innerHTML = controls_html + title_element.innerHTML
}

fbf.injectControls()<commit_msg>Added basic styling<commit_after>fbf = {}

fbf.FRAMES_PER_SECOND = 25
fbf.PLAYER_ID = "movie_player"

fbf.prevFrame = function() {
    // Based on YouTube enhancer userscript, http://userscripts.org/scripts/show/33042.
    player = document.getElementById(fbf.PLAYER_ID)
    player.pauseVideo()
    player.seekBy(-1 * (1/fbf.FRAMES_PER_SECOND))
}

fbf.nextFrame = function() {
    // Based on YouTube enhancer userscript, http://userscripts.org/scripts/show/33042.
    player = document.getElementById(fbf.PLAYER_ID)
    player.pauseVideo()
    player.seekBy(1 * (1/fbf.FRAMES_PER_SECOND))
}

fbf.injectControls = function() {
    /*
    Injects extra player controls into the page.
    */
    // Really basic controls to get started.
    controls_html = "(prev"
        + "/"
        + "next)"
    title_element = document.getElementById("watch-headline-title")
    title_element.innerHTML = controls_html + title_element.innerHTML
}

fbf.injectControls()<|endoftext|>


<commit_before>var Speech = (function() {

	var answerFinal = "";
	var playing = "";

	function init(settings){
		//No Initialization as of now.
		document.getElementById('speaklang').innerHTML = settings.language;
		meSpeak.loadConfig("mespeak_config.json");
		loadVoice(settings.language);
	}

	function getBotReply(question){
		var aimlInterpreter = new AIMLInterpreter({name:'WireInterpreter', age:'42'});
		var filesArray = ['js/alice/star.aiml'];
		var words = question.split(' ');
		var marked = new Array(256);
		for(i=0;i<256;i++){
			marked[i] = 0;
		}
		for(i=0; i<words.length; i++){
			if(words[i][0] >= 'a' && words[i][0] <= 'z'){
				if (marked[(words[i].charCodeAt(0)+'A'.charCodeAt(0)-'a'.charCodeAt(0))] == 0){
					marked[(words[i].charCodeAt(0)+'A'.charCodeAt(0)-'a'.charCodeAt(0))] = 1;
					filesArray.unshift("js/alice/" + String.fromCharCode(words[i].charCodeAt(0)+'A'.charCodeAt(0)-'a'.charCodeAt(0)) + ".aiml");
				}
			}
			if((words[i][0] >= 'A' && words[i][0] <= 'Z') || (words[i][0] >= '0' && words[i][0] <= '9')){
				if (marked[words[i].charCodeAt(0)] == 0){
					marked[words[i].charCodeAt(0)] = 1;
					filesArray.unshift("js/alice/" + words[i][0] + ".aiml");
				}
			}
		}
		//console.log(filesArray);
		aimlInterpreter.loadAIMLFilesIntoArray(filesArray);
		answerFinal = "Please ask me something else";

		var callback = function(answer, wildCardArray, input){
			if(answer == null){
				answer = "Please ask me something else";
			}
		    answerFinal = answer.split('.')[0];
		};

		aimlInterpreter.findAnswerInLoadedAIMLFiles(question, callback);
	}

    function testVoice(voice, callback) {
    	var req=new XMLHttpRequest();
		req.url = voice;
    	try {
    		req.open('GET', voice);
    		req.onreadystatechange=function() {
				if (req.readyState==4) {
					callback((req.status==200 || req.status==0) && req.response.length > 0);
				}
			};
    		req.send('');
    	}
    	catch(e) {
    		callback(false);
    	}
    }

	function loadVoice(name, callback) {
		var defaultVoice = meSpeak.getDefaultVoice();
		if (defaultVoice == name || defaultVoice.indexOf(name) == 0) {
			if (callback) callback();
			return;
		}
		var fname = "voices/"+name+".json";
		testVoice(fname, function(exist) {
			if (exist) {
				meSpeak.loadVoice(fname, callback);
			} else {
				document.getElementById('speaklang').innerHTML = "en";
				meSpeak.loadVoice("voices/en.json", callback);
			}
		});
	}

	function playVoice(language, text) {
      playing = text;
      if(document.getElementById('mode').innerHTML=="2"){
	    //After the voice is loaded, playSound callback is called
	    getBotReply(text);
	    setTimeout(function(){
			loadVoice(language, playSound);
		}, 4000);
  	  }
  	  else{
		loadVoice(language, playSound);
  	  }
    }

    function playSound(){
		var text = playing;
		var pitch = document.getElementById('pitch').innerHTML;
		var speed = document.getElementById('rate').innerHTML;
		if(document.getElementById('mode').innerHTML=="2"){
			text = answerFinal;
			console.log(answerFinal);
		}

		function soundComplete(){
			document.getElementById('speaking').innerHTML = "0";
		}

		if (!/iPad|iPhone/.test(navigator.userAgent)) {
			meSpeak.speak(text, {speed: speed, pitch: pitch}, soundComplete);
		} else {
			// HACK: On iOS, need to be played into an HTMLAudio control
			var myDataUrl = meSpeak.speak(text, {speed: speed, pitch: pitch, rawdata: 'data-url'}, soundComplete);
			var sound = new Audio(myDataUrl);
			sound.addEventListener("ended", function(){
				document.getElementById('speaking').innerHTML = "0";
			});
			sound.play();
		}
    }

	return {
        init: init,
		playVoice: playVoice
    };

});
<commit_msg>Array for recent talks<commit_after>var Speech = (function() {

	var answerFinal = "";
	var playing = "";

	function init(settings){
		//No Initialization as of now.
		document.getElementById('speaklang').innerHTML = settings.language;
		meSpeak.loadConfig("mespeak_config.json");
		loadVoice(settings.language);
	}

	function getBotReply(question){
		var aimlInterpreter = new AIMLInterpreter({name:'WireInterpreter', age:'42'});
		var filesArray = ['js/alice/star.aiml'];
		var words = question.split(' ');
		var marked = new Array(256);
		for(i=0;i<256;i++){
			marked[i] = 0;
		}
		for(i=0; i<words.length; i++){
			if(words[i][0] >= 'a' && words[i][0] <= 'z'){
				if (marked[(words[i].charCodeAt(0)+'A'.charCodeAt(0)-'a'.charCodeAt(0))] == 0){
					marked[(words[i].charCodeAt(0)+'A'.charCodeAt(0)-'a'.charCodeAt(0))] = 1;
					filesArray.unshift("js/alice/" + String.fromCharCode(words[i].charCodeAt(0)+'A'.charCodeAt(0)-'a'.charCodeAt(0)) + ".aiml");
				}
			}
			if((words[i][0] >= 'A' && words[i][0] <= 'Z') || (words[i][0] >= '0' && words[i][0] <= '9')){
				if (marked[words[i].charCodeAt(0)] == 0){
					marked[words[i].charCodeAt(0)] = 1;
					filesArray.unshift("js/alice/" + words[i][0] + ".aiml");
				}
			}
		}
		//console.log(filesArray);
		aimlInterpreter.loadAIMLFilesIntoArray(filesArray);
		answerFinal = "Please ask me something else";

		var callback = function(answer, wildCardArray, input){
			if(answer == null){
				answer = "Please ask me something else";
			}
		    answerFinal = answer.split('.')[0];
		};

		aimlInterpreter.findAnswerInLoadedAIMLFiles(question, callback);
	}

    function testVoice(voice, callback) {
    	var req=new XMLHttpRequest();
		req.url = voice;
    	try {
    		req.open('GET', voice);
    		req.onreadystatechange=function() {
				if (req.readyState==4) {
					callback((req.status==200 || req.status==0) && req.response.length > 0);
				}
			};
    		req.send('');
    	}
    	catch(e) {
    		callback(false);
    	}
    }

	function loadVoice(name, callback) {
		var defaultVoice = meSpeak.getDefaultVoice();
		if (defaultVoice == name || defaultVoice.indexOf(name) == 0) {
			if (callback) callback();
			return;
		}
		var fname = "voices/"+name+".json";
		testVoice(fname, function(exist) {
			if (exist) {
				meSpeak.loadVoice(fname, callback);
			} else {
				document.getElementById('speaklang').innerHTML = "en";
				meSpeak.loadVoice("voices/en.json", callback);
			}
		});
	}

	hiddenArray(); // Access speakArray

	// Function containing the speakArray, which saves the recent talk array
	function hiddenArray() {
		speakArray = [];
	}

	function playVoice(language, text) {
      playing = text;

			speakArray.push(playing); // Adds recent talks to speakArray
			//console.log(speakArray);

      if(document.getElementById('mode').innerHTML=="2"){
	    //After the voice is loaded, playSound callback is called
	    getBotReply(text);
	    setTimeout(function(){
			loadVoice(language, playSound);
		}, 4000);
  	  }
  	  else{
				loadVoice(language, playSound);
  	  }
    }

  function playSound(){
		var text = playing;
		var pitch = document.getElementById('pitch').innerHTML;
		var speed = document.getElementById('rate').innerHTML;
		if(document.getElementById('mode').innerHTML=="2"){
			text = answerFinal;
			console.log(answerFinal);
		}

		function soundComplete(){
			document.getElementById('speaking').innerHTML = "0";
		}

		if (!/iPad|iPhone/.test(navigator.userAgent)) {
			meSpeak.speak(text, {speed: speed, pitch: pitch}, soundComplete);
		} else {
			// HACK: On iOS, need to be played into an HTMLAudio control
			var myDataUrl = meSpeak.speak(text, {speed: speed, pitch: pitch, rawdata: 'data-url'}, soundComplete);
			var sound = new Audio(myDataUrl);
			sound.addEventListener("ended", function(){
				document.getElementById('speaking').innerHTML = "0";
			});
			sound.play();
		}
    }

	return {
        init: init,
		playVoice: playVoice
    };

});
<|endoftext|>


<commit_before>/**
 * ownCloud - owncollab_timetracker
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author andy <info@eventgineering.de>
 * @copyright andy 2016
 */
var instanceUrl = OC.generateUrl('/apps/owncollab_timetracker/');

(function (OC, window, $, undefined) {
'use strict';

$(document).ready(function () {

var translations = {
    newEvent: $('#new-event-string').text()
};

// this events object holds all our events
var Events = function (baseUrl) {
    this._baseUrl = baseUrl;
    this._events = [];
    this._activeEvent = undefined;
};

Events.prototype = {
    load: function (id) {
        var self = this;
        this._events.forEach(function (event) {
            if (event.id === id) {
                event.active = true;
                self._activeEvent = event;
            } else {
                event.active = false;
            }
        });

    },
    getActive: function () {
        return this._activeEvent;
    },
    removeActive: function () {
        var index;
        var deferred = $.Deferred();
        var id = this._activeEvent.id;
        this._events.forEach(function (event, counter) {
            if (event.id === id) {
                index = counter;
            }
        });

        if (index !== undefined) {
            // delete cached active event if necessary
            if (this._activeEvent === this._events[index]) {
                delete this._activeEvent;
            }

            this._events.splice(index, 1);

            $.ajax({
                url: this._baseUrl + '/' + id,
                method: 'DELETE'
            }).done(function () {
                deferred.resolve();
            }).fail(function () {
                deferred.reject();
            });
        } else {
            deferred.reject();
        }
        return deferred.promise();
    },
    create: function (event) {
        var deferred = $.Deferred();
        var self = this;
        $.ajax({
            url: this._baseUrl,
            method: 'POST',
            contentType: 'application/json',
            data: JSON.stringify(event)
        }).done(function (event) {
            self._events.push(event);
            self._activeEvent = event;
            self.load(event.id);
            deferred.resolve();
        }).fail(function () {
            deferred.reject();
        });
        return deferred.promise();
    },
    getAll: function () {
        return this._events;
    },
    loadAll: function () {
        var deferred = $.Deferred();
        var self = this;
        $.get(this._baseUrl).done(function (events) {
            self._activeEvent = undefined;
            self._events = events;
            deferred.resolve();
        }).fail(function () {
            deferred.reject();
        });
        return deferred.promise();
    },
    updateActive: function (title, content, startdate, starttime, enddate, endtime, clientid, projectid, jobid) {
        var event = this.getActive();
        event.title = title;
        event.content = content;
        event.startdate = startdate;
        event.starttime = starttime;
        event.enddate = enddate;
        event.endtime = endtime;
        event.clientid = clientid;
        event.projectid = projectid;
        event.jobid = jobid;
        return $.ajax({
            url: this._baseUrl + '/' + event.id,
            method: 'PUT',
            contentType: 'application/json',
            data: JSON.stringify(event)
        });
    }
};

// this will be the view that is used to update the html
var View = function (events) {
    this._events = events;
};

View.prototype = {
    renderContent: function () {
        var source = $('#content-tpl').html();
        var template = Handlebars.compile(source);
        var html = template({event: this._events.getActive()});
        $('#editor').html(html);
        if (this._events.getActive()){
                if (this._events.getActive().clientid == 0){
                        $('#select_client').changeSelect(instanceUrl, 'clients');
			$('#select_client').change(function () {$('#select_project').changebySelect(instanceUrl, 'projects', this.value);});
                }
		else{
			$('#select_client').changeSelectbyID(instanceUrl, 'clients'), this._events.getActive().clientid);
//			$.getJSON(instanceUrl+'clients', function(result){
//				var items="";
//				$.each(result, function(i, data){
//					if(data.id == this._events.getActive().clientid){
//						console.log('id equal');
//					}
//				});
//			});
		}
                if (this._events.getActive().jobid == 0){
                        $('#select_job').changeSelect(instanceUrl, 'jobs');
		};
        };

        // handle saves
        var textarea = $('#app-content textarea');
        var self = this;
        $('#app-content button').click(function () {
            if ($('#startdate').val()==''){
                var startdate = $('#startdate').attr('placeholder');
            }
            else{
                var startdate = $('#startdate').val();
            }
            if ($('#starttime').val()==''){
                var starttime = $('#starttime').attr('placeholder');
            }
            else{
                var starttime = $('#starttime').val();
            }
            if ($('#enddate').val()==''){
                var enddate = $('#enddate').attr('placeholder');
            }
            else{
                var enddate = $('#enddate').val();
            }
            if ($('#endtime').val()==''){
                var endtime = $('#endtime').attr('placeholder');
            }
            else{
                var endtime = $('#endtime').val();
            }
            if (textarea.val()==''){
                var content = textarea.attr('placeholder');
            }
            else{
                var content = textarea.val();
            }

            var title = content.split('\n')[0]; // first line is the title
            var clientid = $('#select_client').val();
            var projectid = $('#select_project').val();
            var jobid = $('#select_job').val();
            self._events.updateActive(title, content, startdate, starttime, enddate, endtime, clientid, projectid, jobid).done(function () {
                self.render();
            }).fail(function () {
                alert('Could not update event, not found');
            });
        });
    },
    renderNavigation: function () {
        var d = new Date();
        var now = ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2);
        var today = ("0" + d.getDate()).slice(-2) + "." + ("0"+(d.getMonth()+1)).slice(-2) + "." + d.getFullYear();
        var source = $('#navigation-tpl').html();
        var template = Handlebars.compile(source);
        var html = template({events: this._events.getAll()});

        $('#app-navigation ul').html(html);

        // create a new event
        var self = this;
        $('#new-event').click(function () {
            var event = {
                title: translations.newEvent,
                content: 'content here . . .'
            };

            self._events.create(event).done(function() {
                self.render();
                startdate.placeholder=today;
                enddate.placeholder=today;
                $( "#startdate" ).datepicker({
    	            minDate: new Date(2016, 1 - 1, 1)});
                $( "#enddate" ).datepicker();
            	$('#starttime').timepicki({
                	show_meridian:false,
                	min_hour_value:0,
                	max_hour_value:23,
                	overflow_minutes:true
                });
                starttime.placeholder=now;
                $('#endtime').timepicki({
                	show_meridian:false,
                	min_hour_value:0,
                	max_hour_value:23,
                	overflow_minutes:true
                });
                endtime.placeholder=now;
                $('#select_client').changeSelect(instanceUrl, 'clients');
                $('#select_client').change(function () {$('#select_project').changebySelect(instanceUrl, 'projects', this.value);});
		$('#select_job').changeSelect(instanceUrl, 'jobs');
                $('#editor textarea').focus();
            }).fail(function () {
                alert('Could not create event');
            });

        });

        // show app menu
        $('#app-navigation .app-navigation-entry-utils-menu-button').click(function () {
            var entry = $(this).closest('.event');
            entry.find('.app-navigation-entry-menu').toggleClass('open');
        });

        // delete a event
        $('#app-navigation .event .delete').click(function () {
            var entry = $(this).closest('.event');
            entry.find('.app-navigation-entry-menu').removeClass('open');

            self._events.removeActive().done(function () {
                self.render();
            }).fail(function () {
                alert('Could not delete event, not found');
            });
        });

        // load a event
        $('#app-navigation .event > a').click(function () {

	    var d = new Date();
            var now = ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2);
            var today = ("0" + d.getDate()).slice(-2) + "." + ("0"+(d.getMonth()+1)).slice(-2) + "." + d.getFullYear();
            var id = parseInt($(this).parent().data('id'), 10);
            self._events.load(id);
            self.render();
            if ($('#startdate').val()==''){startdate.placeholder=today;}
            if ($('#enddate').val()==''){enddate.placeholder=today;}
            $( "#startdate" ).datepicker({
                minDate: new Date(2016, 1 - 1, 1)});
            $( "#enddate" ).datepicker();
            $('#starttime').timepicki({
                show_meridian:false,
                min_hour_value:0,
                max_hour_value:23,
                overflow_minutes:true
                });
            if ($('#starttime').val()==''){starttime.placeholder=now;}
            $('#endtime').timepicki({
                show_meridian:false,
                min_hour_value:0,
                max_hour_value:23,
                overflow_minutes:true
                });
            if ($('#endtime').val()==''){endtime.placeholder=now;}
            $('#editor textarea').focus();

        });
    },
    render: function () {
        this.renderNavigation();
        this.renderContent();
    }
};

var events = new Events(OC.generateUrl('/apps/owncollab_timetracker/events'));
var view = new View(events);
events.loadAll().done(function () {
    view.render();
}).fail(function () {
    alert('Could not load events');
});

});

})(OC, window, jQuery);
<commit_msg>little fix<commit_after>/**
 * ownCloud - owncollab_timetracker
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author andy <info@eventgineering.de>
 * @copyright andy 2016
 */
var instanceUrl = OC.generateUrl('/apps/owncollab_timetracker/');

(function (OC, window, $, undefined) {
'use strict';

$(document).ready(function () {

var translations = {
    newEvent: $('#new-event-string').text()
};

// this events object holds all our events
var Events = function (baseUrl) {
    this._baseUrl = baseUrl;
    this._events = [];
    this._activeEvent = undefined;
};

Events.prototype = {
    load: function (id) {
        var self = this;
        this._events.forEach(function (event) {
            if (event.id === id) {
                event.active = true;
                self._activeEvent = event;
            } else {
                event.active = false;
            }
        });

    },
    getActive: function () {
        return this._activeEvent;
    },
    removeActive: function () {
        var index;
        var deferred = $.Deferred();
        var id = this._activeEvent.id;
        this._events.forEach(function (event, counter) {
            if (event.id === id) {
                index = counter;
            }
        });

        if (index !== undefined) {
            // delete cached active event if necessary
            if (this._activeEvent === this._events[index]) {
                delete this._activeEvent;
            }

            this._events.splice(index, 1);

            $.ajax({
                url: this._baseUrl + '/' + id,
                method: 'DELETE'
            }).done(function () {
                deferred.resolve();
            }).fail(function () {
                deferred.reject();
            });
        } else {
            deferred.reject();
        }
        return deferred.promise();
    },
    create: function (event) {
        var deferred = $.Deferred();
        var self = this;
        $.ajax({
            url: this._baseUrl,
            method: 'POST',
            contentType: 'application/json',
            data: JSON.stringify(event)
        }).done(function (event) {
            self._events.push(event);
            self._activeEvent = event;
            self.load(event.id);
            deferred.resolve();
        }).fail(function () {
            deferred.reject();
        });
        return deferred.promise();
    },
    getAll: function () {
        return this._events;
    },
    loadAll: function () {
        var deferred = $.Deferred();
        var self = this;
        $.get(this._baseUrl).done(function (events) {
            self._activeEvent = undefined;
            self._events = events;
            deferred.resolve();
        }).fail(function () {
            deferred.reject();
        });
        return deferred.promise();
    },
    updateActive: function (title, content, startdate, starttime, enddate, endtime, clientid, projectid, jobid) {
        var event = this.getActive();
        event.title = title;
        event.content = content;
        event.startdate = startdate;
        event.starttime = starttime;
        event.enddate = enddate;
        event.endtime = endtime;
        event.clientid = clientid;
        event.projectid = projectid;
        event.jobid = jobid;
        return $.ajax({
            url: this._baseUrl + '/' + event.id,
            method: 'PUT',
            contentType: 'application/json',
            data: JSON.stringify(event)
        });
    }
};

// this will be the view that is used to update the html
var View = function (events) {
    this._events = events;
};

View.prototype = {
    renderContent: function () {
        var source = $('#content-tpl').html();
        var template = Handlebars.compile(source);
        var html = template({event: this._events.getActive()});
        $('#editor').html(html);
        if (this._events.getActive()){
                if (this._events.getActive().clientid == 0){
                        $('#select_client').changeSelect(instanceUrl, 'clients');
			$('#select_client').change(function () {$('#select_project').changebySelect(instanceUrl, 'projects', this.value);});
                }
		else{
			$('#select_client').changeSelectbyID(instanceUrl, 'clients', this._events.getActive().clientid));
//			$.getJSON(instanceUrl+'clients', function(result){
//				var items="";
//				$.each(result, function(i, data){
//					if(data.id == this._events.getActive().clientid){
//						console.log('id equal');
//					}
//				});
//			});
		}
                if (this._events.getActive().jobid == 0){
                        $('#select_job').changeSelect(instanceUrl, 'jobs');
		};
        };

        // handle saves
        var textarea = $('#app-content textarea');
        var self = this;
        $('#app-content button').click(function () {
            if ($('#startdate').val()==''){
                var startdate = $('#startdate').attr('placeholder');
            }
            else{
                var startdate = $('#startdate').val();
            }
            if ($('#starttime').val()==''){
                var starttime = $('#starttime').attr('placeholder');
            }
            else{
                var starttime = $('#starttime').val();
            }
            if ($('#enddate').val()==''){
                var enddate = $('#enddate').attr('placeholder');
            }
            else{
                var enddate = $('#enddate').val();
            }
            if ($('#endtime').val()==''){
                var endtime = $('#endtime').attr('placeholder');
            }
            else{
                var endtime = $('#endtime').val();
            }
            if (textarea.val()==''){
                var content = textarea.attr('placeholder');
            }
            else{
                var content = textarea.val();
            }

            var title = content.split('\n')[0]; // first line is the title
            var clientid = $('#select_client').val();
            var projectid = $('#select_project').val();
            var jobid = $('#select_job').val();
            self._events.updateActive(title, content, startdate, starttime, enddate, endtime, clientid, projectid, jobid).done(function () {
                self.render();
            }).fail(function () {
                alert('Could not update event, not found');
            });
        });
    },
    renderNavigation: function () {
        var d = new Date();
        var now = ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2);
        var today = ("0" + d.getDate()).slice(-2) + "." + ("0"+(d.getMonth()+1)).slice(-2) + "." + d.getFullYear();
        var source = $('#navigation-tpl').html();
        var template = Handlebars.compile(source);
        var html = template({events: this._events.getAll()});

        $('#app-navigation ul').html(html);

        // create a new event
        var self = this;
        $('#new-event').click(function () {
            var event = {
                title: translations.newEvent,
                content: 'content here . . .'
            };

            self._events.create(event).done(function() {
                self.render();
                startdate.placeholder=today;
                enddate.placeholder=today;
                $( "#startdate" ).datepicker({
    	            minDate: new Date(2016, 1 - 1, 1)});
                $( "#enddate" ).datepicker();
            	$('#starttime').timepicki({
                	show_meridian:false,
                	min_hour_value:0,
                	max_hour_value:23,
                	overflow_minutes:true
                });
                starttime.placeholder=now;
                $('#endtime').timepicki({
                	show_meridian:false,
                	min_hour_value:0,
                	max_hour_value:23,
                	overflow_minutes:true
                });
                endtime.placeholder=now;
                $('#select_client').changeSelect(instanceUrl, 'clients');
                $('#select_client').change(function () {$('#select_project').changebySelect(instanceUrl, 'projects', this.value);});
		$('#select_job').changeSelect(instanceUrl, 'jobs');
                $('#editor textarea').focus();
            }).fail(function () {
                alert('Could not create event');
            });

        });

        // show app menu
        $('#app-navigation .app-navigation-entry-utils-menu-button').click(function () {
            var entry = $(this).closest('.event');
            entry.find('.app-navigation-entry-menu').toggleClass('open');
        });

        // delete a event
        $('#app-navigation .event .delete').click(function () {
            var entry = $(this).closest('.event');
            entry.find('.app-navigation-entry-menu').removeClass('open');

            self._events.removeActive().done(function () {
                self.render();
            }).fail(function () {
                alert('Could not delete event, not found');
            });
        });

        // load a event
        $('#app-navigation .event > a').click(function () {

	    var d = new Date();
            var now = ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2);
            var today = ("0" + d.getDate()).slice(-2) + "." + ("0"+(d.getMonth()+1)).slice(-2) + "." + d.getFullYear();
            var id = parseInt($(this).parent().data('id'), 10);
            self._events.load(id);
            self.render();
            if ($('#startdate').val()==''){startdate.placeholder=today;}
            if ($('#enddate').val()==''){enddate.placeholder=today;}
            $( "#startdate" ).datepicker({
                minDate: new Date(2016, 1 - 1, 1)});
            $( "#enddate" ).datepicker();
            $('#starttime').timepicki({
                show_meridian:false,
                min_hour_value:0,
                max_hour_value:23,
                overflow_minutes:true
                });
            if ($('#starttime').val()==''){starttime.placeholder=now;}
            $('#endtime').timepicki({
                show_meridian:false,
                min_hour_value:0,
                max_hour_value:23,
                overflow_minutes:true
                });
            if ($('#endtime').val()==''){endtime.placeholder=now;}
            $('#editor textarea').focus();

        });
    },
    render: function () {
        this.renderNavigation();
        this.renderContent();
    }
};

var events = new Events(OC.generateUrl('/apps/owncollab_timetracker/events'));
var view = new View(events);
events.loadAll().done(function () {
    view.render();
}).fail(function () {
    alert('Could not load events');
});

});

})(OC, window, jQuery);
<|endoftext|>