<commit_before>/** @jsx React.DOM */
/* global module, require, console */

/*
 * Copyright (C) 2014 Johannes Baiter <johannes.baiter@gmail.com>
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.

 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

(function() {
  'use strict';

  var React = require('react/addons'),
      _ = require('underscore'),
      Mousetrap = require('mousetrap'),
      foundation = require('./foundation.js'),
      ModelMixin = require('../../vendor/backbonemixin.js'),
      LoadingOverlay = require('./overlays.js').Activity,
      lightbox = require('./overlays.js').LightBox,
      PluginWidget = require('./config.js').PluginWidget,
      CropWidget = require('./cropdialog.js'),
      util = require('../util.js'),
      row = foundation.row,
      column = foundation.column,
      fnButton = foundation.button,
      confirmModal = foundation.confirmModal,
      modal = foundation.modal,
      placeholderImg;

  placeholderImg = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4AQMAAABPbGssAAAAA1BMVEWZmZl86KQWAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gQIFjciiRhnwgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAZSURBVEjH7cEBDQAAAMKg909tDwcUAAAPBgnYAAHW6F1SAAAAAElFTkSuQmCC";

  /**
   * Screen component to control the capture process.
   *
   * @property {Workflow} workflow - Workflow to control capture on
   */
  module.exports = React.createClass({
    displayName: "CaptureScreen",

    /** Enables two-way databinding with Backbone model */
    mixins: [ModelMixin],

    /** Activates databinding for `workflow` model property. */
    getBackboneModels: function() {
      return [this.props.workflow];
    },
    getInitialState: function() {
      return {
        /** Display activity overlay? */
        waiting: false,
        /** Initial number of pages shot */
        initialPageCount: this.props.workflow.get('images').length,
        /** Message for activity overlay */
        waitMessage: undefined,
        /** Time of first capture */
        captureStart: undefined,
        /** Validation errors for device configuration */
        validationErrors: {},
        /** Crop parameters */
        cropParams: {}
      };
    },
    /**
     * Triggers preparation of capture on workflow and displays the activity
     * overlay until the process is finished.
     */
    componentWillMount: function() {
      this.toggleWaiting("Please wait while the devices  are being prepared " +
                          "for capture");
      this.props.workflow.on('capture-triggered', function(){
        this.toggleWaiting("Please wait for the capture to finish...");
      }, this);
      this.props.workflow.on('capture-succeeded', this.toggleWaiting, this);
      _.each(window.config.core.capture_keys, function(key) {
        if (key === ' ') key = 'space';
        Mousetrap.bind(key, this.handleCapture);
      }, this);
      Mousetrap.bind('r', this.handleRetake);
      Mousetrap.bind('f', this.handleFinish);
      this.props.workflow.prepareCapture(this.toggleWaiting);
    },
    /**
     * Triggers finish of capture on workflow.
     */
    componentWillUnmount: function() {
      console.log("Wrapping up capture process");
      _.each(window.config.core.capture_key, function(key) {
        if (key === ' ') key = 'space';
        Mousetrap.unbind(key);
      });
      Mousetrap.unbind('r');
      Mousetrap.unbind('f');
      if (!_.isEmpty(this.state.cropParams)) {
        this.cropLast();
      }
      this.props.workflow.finishCapture();
    },
    /**
     * Trigger a single capture, display activity overlay until it is finished
     */
    handleCapture: function() {
      if (this.state.waiting) {
        // There is already a capture (or preparation) in progress.
        return;
      }
      if (!_.isEmpty(this.state.cropParams)) {
        this.cropLast();
      }
      console.log("Triggering capture");
      this.props.workflow.triggerCapture(false, function() {
        if (this.state.refreshReview) {
          this.setState({refreshReview: false});
        }
      }.bind(this));
    },
    cropLast: function() {
      var workflow = this.props.workflow,
          oddImage = workflow.get('images').slice(-2)[0],
          evenImage = workflow.get('images').slice(-2)[1];
      console.log("Cropping last capture")
      this.props.workflow.cropImage(oddImage, this.state.cropParams.odd);
      this.props.workflow.cropImage(evenImage, this.state.cropParams.even);
    },
    /**
     * Trigger a retake (= delete last <num_devices> captures and take new
     * ones, display activity overlay until it is finished.
     */
    handleRetake: function() {
      if (this.state.waiting) {
        // There is already a capture (or preparation) in progress.
        return;
      }
      console.log("Re-taking last shot");
      this.props.workflow.triggerCapture(true, function() {
        if (!this.state.refreshReview) {
          this.setState({refreshReview: true});
        }
      }.bind(this));
    },
    /**
     * Finish capture and navigate back to workflow list screen
     */
    handleFinish: function() {
      window.router.navigate('/', {trigger: true});
    },
    /**
     * Toggle display of activity overlay.
     *
     * @param {string} message - Message to display on overlay
     */
    toggleWaiting: function(message) {
      if (!this.state.waiting) {
        this.setState({
          waiting: true,
          waitMessage: message || ''
        });
      } else {
        this.setState({waiting: false});
      }
    },
    toggleConfigModal: function() {
      this.setState({
        displayConfig: !this.state.displayConfig
      });
    },
    toggleCropDialog: function(targetPage) {
      this.setState({
        cropTarget: targetPage
      });
    },
    setCropParams: function(params) {
      var origParams = this.state.cropParams;
      origParams[this.state.cropTarget] = params;
      this.setState({
        cropParams: origParams,
        cropTarget: undefined
      });
    },
    saveConfig: function() {
      this.props.workflow.on('validated:invalid', function(workflow, errors) {
        this.setState({validationErrors: errors});
      }, this);
      var xhr = this.props.workflow.save();
      if (xhr) {
        xhr.done(function() {
          this.toggleConfigModal();
          this.toggleWaiting("Configuring cameras.")
          this.props.workflow.prepareCapture(this.toggleWaiting, true);
          this.props.workflow.off('validated:invalid', null, this);
        }.bind(this))
      };
    },
    /**
     * Open image in lightbox overlay
     *
     * @param {url} - Image to display in lightbox
     */
    openLightbox: function(img, targetPage) {
      this.setState({
        lightboxImage: img,
        lightboxImageTarget: targetPage
      });
    },
    /**
     * Close the lightbox overlay.
     */
    closeLightbox: function() {
      this.setState({
        lightboxImage: undefined,
        lightboxImageTarget: undefined,
        refreshReview: false,
      });
    },
    toggleAdvanced: function(){
      this.setState({ advancedOpts: !this.state.advancedOpts });
      this.forceUpdate();
    },
    getCropPreviewStyle: function(targetPage) {
      var cropParams = this.state.cropParams[targetPage],
          thumbNode = this.refs['thumb-'+targetPage].getDOMNode(),
          factor = thumbNode.offsetWidth / cropParams.nativeWidth;
      return {
        left: Math.ceil(cropParams.left*factor),
        top: Math.ceil(cropParams.top*factor),
        width: Math.ceil(cropParams.width*factor),
        height: Math.ceil(cropParams.height*factor)
      };
    },
    render: function() {
      var workflow = this.props.workflow || {},
          randomSuffix = this.state.refreshReview ? '?'+(Math.random()*10e3 | 0) : '',
          speed, oddImage, evenImage, captureKeys, previewClasses;

      previewClasses = {
        'capture-preview': true,
        'small-block-grid-2': util.getOrientation() === 'landscape',
        'small-block-grid-1': util.getOrientation() === 'portrait',
        'medium-block-grid-2': util.getOrientation() === 'portrait'
      }
      captureKeys = [] ;
      _.each(window.config.core.capture_keys, function(key) {
        if (key === ' ') captureKeys.push('<spacebar>');
        else captureKeys.push(key);
      });
      if (workflow && this.state.captureStart) {
        var elapsed = (new Date().getTime()/1000) - this.state.captureStart,
            shot = workflow.get('images').length - this.state.initialPageCount;
        speed = (3600/elapsed)*shot | 0;
      } else {
        this.setState({captureStart: new Date().getTime()/1000});
        speed = 0.0;
      }
      if (workflow.get('images').length) {
        evenImage = workflow.get('images').slice(-2)[0];
        oddImage = workflow.get('images').slice(-2)[1];
      }
      return (

          {/* Display loading overlay? */}
          {this.state.waiting && <LoadingOverlay message={this.state.waitMessage} />}
          {/* Display lightbox overlay? */}
          {this.state.lightboxImage &&
            <lightbox onClose={this.closeLightbox} src={this.state.lightboxImage}
                      targetPage={this.state.lightboxImageTarget} />}
          {this.state.displayConfig &&
            <form onSubmit={this.saveConfig}>
              <confirmModal onCancel={this.toggleConfigModal}>

Configure Devices

                <input id="check-advanced" type="checkbox" value={this.state.advancedOpts}
                       onChange={this.toggleAdvanced} />
                Show advanced options
                <PluginWidget plugin="device" template={window.pluginTemplates.device}
                              showAdvanced={this.state.advancedOpts}
                              bindFunc={function(key) {
                                return this.bindTo(this.props.workflow,
                                                    'config.device.' + key);
                              }.bind(this)} errors={[]} />
              </confirmModal>
            </form>
          }
          {this.state.cropTarget &&
            <modal onClose={function(){this.setState({cropTarget: undefined});}.bind(this)}
                   small={false}>
              <CropWidget imageSrc={this.state.cropTarget == 'even' ? evenImage : oddImage}
                          onSave={this.setCropParams}
                          cropParams={this.state.cropParams[this.state.cropTarget]}
                          showInputs={true}/>
            </modal>
          }
          <row>
            <column>
              {/* NOTE: We append a random suffix to the thumbnail URL to force
                *       the browser to load from the server and not from the cache.
                *       This is needed since the images might change on the server,
                *       e.g. after a retake. */}

                   Crop
                  {evenImage ?

                    :
                    }
                  {this.state.cropParams.even &&

                  }

                   Crop
                  {oddImage ?

                  :
                  }
                  {this.state.cropParams.odd &&

                  }

            </column>
          </row>
          <row className="capture-info">
            <column size="6">
              {workflow.get('images').length} pages
            </column>
            {speed > 0 &&
            <column size="6">
              {speed} pages/hour
            </column>}
          </row>
          <row>

                  <fnButton title="Discard last capture and take a new one"
                            callback={this.handleRetake} secondary='true'>

                  </fnButton>

                  <fnButton title="Trigger capture"
                            callback={this.handleCapture}>

                  </fnButton>

                  <fnButton title="Configure devices"
                            callback={this.toggleConfigModal} secondary='true'>

                  </fnButton>

                  <fnButton title="Finish capture and return to workflow list"
                            callback={this.handleFinish} complete={true}>

                  </fnButton>

          </row>
          {!util.isTouchDevice() &&
          <row className="hide-for-touch">
            <column size="4" offset="4" className="shortcuts">
              Keyboard shortcuts:

Capture:
                  {_.map(captureKeys, function(key) {
                    return ({' '}{key.toUpperCase()});
                  })}

Retake: R

Finish: F

            </column>
          </row>}

      );
    }
  });
})();
<commit_msg>web: Fix flipped crop targets<commit_after>/** @jsx React.DOM */
/* global module, require, console */

/*
 * Copyright (C) 2014 Johannes Baiter <johannes.baiter@gmail.com>
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.

 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

(function() {
  'use strict';

  var React = require('react/addons'),
      _ = require('underscore'),
      Mousetrap = require('mousetrap'),
      foundation = require('./foundation.js'),
      ModelMixin = require('../../vendor/backbonemixin.js'),
      LoadingOverlay = require('./overlays.js').Activity,
      lightbox = require('./overlays.js').LightBox,
      PluginWidget = require('./config.js').PluginWidget,
      CropWidget = require('./cropdialog.js'),
      util = require('../util.js'),
      row = foundation.row,
      column = foundation.column,
      fnButton = foundation.button,
      confirmModal = foundation.confirmModal,
      modal = foundation.modal,
      placeholderImg;

  placeholderImg = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4AQMAAABPbGssAAAAA1BMVEWZmZl86KQWAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gQIFjciiRhnwgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAZSURBVEjH7cEBDQAAAMKg909tDwcUAAAPBgnYAAHW6F1SAAAAAElFTkSuQmCC";

  /**
   * Screen component to control the capture process.
   *
   * @property {Workflow} workflow - Workflow to control capture on
   */
  module.exports = React.createClass({
    displayName: "CaptureScreen",

    /** Enables two-way databinding with Backbone model */
    mixins: [ModelMixin],

    /** Activates databinding for `workflow` model property. */
    getBackboneModels: function() {
      return [this.props.workflow];
    },
    getInitialState: function() {
      return {
        /** Display activity overlay? */
        waiting: false,
        /** Initial number of pages shot */
        initialPageCount: this.props.workflow.get('images').length,
        /** Message for activity overlay */
        waitMessage: undefined,
        /** Time of first capture */
        captureStart: undefined,
        /** Validation errors for device configuration */
        validationErrors: {},
        /** Crop parameters */
        cropParams: {}
      };
    },
    /**
     * Triggers preparation of capture on workflow and displays the activity
     * overlay until the process is finished.
     */
    componentWillMount: function() {
      this.toggleWaiting("Please wait while the devices  are being prepared " +
                          "for capture");
      this.props.workflow.on('capture-triggered', function(){
        this.toggleWaiting("Please wait for the capture to finish...");
      }, this);
      this.props.workflow.on('capture-succeeded', this.toggleWaiting, this);
      _.each(window.config.core.capture_keys, function(key) {
        if (key === ' ') key = 'space';
        Mousetrap.bind(key, this.handleCapture);
      }, this);
      Mousetrap.bind('r', this.handleRetake);
      Mousetrap.bind('f', this.handleFinish);
      this.props.workflow.prepareCapture(this.toggleWaiting);
    },
    /**
     * Triggers finish of capture on workflow.
     */
    componentWillUnmount: function() {
      console.log("Wrapping up capture process");
      _.each(window.config.core.capture_key, function(key) {
        if (key === ' ') key = 'space';
        Mousetrap.unbind(key);
      });
      Mousetrap.unbind('r');
      Mousetrap.unbind('f');
      if (!_.isEmpty(this.state.cropParams)) {
        this.cropLast();
      }
      this.props.workflow.finishCapture();
    },
    /**
     * Trigger a single capture, display activity overlay until it is finished
     */
    handleCapture: function() {
      if (this.state.waiting) {
        // There is already a capture (or preparation) in progress.
        return;
      }
      if (!_.isEmpty(this.state.cropParams)) {
        this.cropLast();
      }
      console.log("Triggering capture");
      this.props.workflow.triggerCapture(false, function() {
        if (this.state.refreshReview) {
          this.setState({refreshReview: false});
        }
      }.bind(this));
    },
    cropLast: function() {
      var workflow = this.props.workflow,
          evenImage = workflow.get('images').slice(-2)[0],
          oddImage = workflow.get('images').slice(-2)[1];
      console.log("Cropping last capture")
      this.props.workflow.cropImage(evenImage, this.state.cropParams.even);
      this.props.workflow.cropImage(oddImage, this.state.cropParams.odd);
    },
    /**
     * Trigger a retake (= delete last <num_devices> captures and take new
     * ones, display activity overlay until it is finished.
     */
    handleRetake: function() {
      if (this.state.waiting) {
        // There is already a capture (or preparation) in progress.
        return;
      }
      console.log("Re-taking last shot");
      this.props.workflow.triggerCapture(true, function() {
        if (!this.state.refreshReview) {
          this.setState({refreshReview: true});
        }
      }.bind(this));
    },
    /**
     * Finish capture and navigate back to workflow list screen
     */
    handleFinish: function() {
      window.router.navigate('/', {trigger: true});
    },
    /**
     * Toggle display of activity overlay.
     *
     * @param {string} message - Message to display on overlay
     */
    toggleWaiting: function(message) {
      if (!this.state.waiting) {
        this.setState({
          waiting: true,
          waitMessage: message || ''
        });
      } else {
        this.setState({waiting: false});
      }
    },
    toggleConfigModal: function() {
      this.setState({
        displayConfig: !this.state.displayConfig
      });
    },
    toggleCropDialog: function(targetPage) {
      this.setState({
        cropTarget: targetPage
      });
    },
    setCropParams: function(params) {
      var origParams = this.state.cropParams;
      origParams[this.state.cropTarget] = params;
      this.setState({
        cropParams: origParams,
        cropTarget: undefined
      });
    },
    saveConfig: function() {
      this.props.workflow.on('validated:invalid', function(workflow, errors) {
        this.setState({validationErrors: errors});
      }, this);
      var xhr = this.props.workflow.save();
      if (xhr) {
        xhr.done(function() {
          this.toggleConfigModal();
          this.toggleWaiting("Configuring cameras.")
          this.props.workflow.prepareCapture(this.toggleWaiting, true);
          this.props.workflow.off('validated:invalid', null, this);
        }.bind(this))
      };
    },
    /**
     * Open image in lightbox overlay
     *
     * @param {url} - Image to display in lightbox
     */
    openLightbox: function(img, targetPage) {
      this.setState({
        lightboxImage: img,
        lightboxImageTarget: targetPage
      });
    },
    /**
     * Close the lightbox overlay.
     */
    closeLightbox: function() {
      this.setState({
        lightboxImage: undefined,
        lightboxImageTarget: undefined,
        refreshReview: false,
      });
    },
    toggleAdvanced: function(){
      this.setState({ advancedOpts: !this.state.advancedOpts });
      this.forceUpdate();
    },
    getCropPreviewStyle: function(targetPage) {
      var cropParams = this.state.cropParams[targetPage],
          thumbNode = this.refs['thumb-'+targetPage].getDOMNode(),
          factor = thumbNode.offsetWidth / cropParams.nativeWidth;
      return {
        left: Math.ceil(cropParams.left*factor),
        top: Math.ceil(cropParams.top*factor),
        width: Math.ceil(cropParams.width*factor),
        height: Math.ceil(cropParams.height*factor)
      };
    },
    render: function() {
      var workflow = this.props.workflow || {},
          randomSuffix = this.state.refreshReview ? '?'+(Math.random()*10e3 | 0) : '',
          speed, oddImage, evenImage, captureKeys, previewClasses;

      previewClasses = {
        'capture-preview': true,
        'small-block-grid-2': util.getOrientation() === 'landscape',
        'small-block-grid-1': util.getOrientation() === 'portrait',
        'medium-block-grid-2': util.getOrientation() === 'portrait'
      }
      captureKeys = [] ;
      _.each(window.config.core.capture_keys, function(key) {
        if (key === ' ') captureKeys.push('<spacebar>');
        else captureKeys.push(key);
      });
      if (workflow && this.state.captureStart) {
        var elapsed = (new Date().getTime()/1000) - this.state.captureStart,
            shot = workflow.get('images').length - this.state.initialPageCount;
        speed = (3600/elapsed)*shot | 0;
      } else {
        this.setState({captureStart: new Date().getTime()/1000});
        speed = 0.0;
      }
      if (workflow.get('images').length) {
        evenImage = workflow.get('images').slice(-2)[0];
        oddImage = workflow.get('images').slice(-2)[1];
      }
      return (

          {/* Display loading overlay? */}
          {this.state.waiting && <LoadingOverlay message={this.state.waitMessage} />}
          {/* Display lightbox overlay? */}
          {this.state.lightboxImage &&
            <lightbox onClose={this.closeLightbox} src={this.state.lightboxImage}
                      targetPage={this.state.lightboxImageTarget} />}
          {this.state.displayConfig &&
            <form onSubmit={this.saveConfig}>
              <confirmModal onCancel={this.toggleConfigModal}>

Configure Devices

                <input id="check-advanced" type="checkbox" value={this.state.advancedOpts}
                       onChange={this.toggleAdvanced} />
                Show advanced options
                <PluginWidget plugin="device" template={window.pluginTemplates.device}
                              showAdvanced={this.state.advancedOpts}
                              bindFunc={function(key) {
                                return this.bindTo(this.props.workflow,
                                                    'config.device.' + key);
                              }.bind(this)} errors={[]} />
              </confirmModal>
            </form>
          }
          {this.state.cropTarget &&
            <modal onClose={function(){this.setState({cropTarget: undefined});}.bind(this)}
                   small={false}>
              <CropWidget imageSrc={this.state.cropTarget == 'even' ? evenImage : oddImage}
                          onSave={this.setCropParams}
                          cropParams={this.state.cropParams[this.state.cropTarget]}
                          showInputs={true}/>
            </modal>
          }
          <row>
            <column>
              {/* NOTE: We append a random suffix to the thumbnail URL to force
                *       the browser to load from the server and not from the cache.
                *       This is needed since the images might change on the server,
                *       e.g. after a retake. */}

                   Crop
                  {evenImage ?

                    :
                    }
                  {this.state.cropParams.even &&

                  }

                   Crop
                  {oddImage ?

                  :
                  }
                  {this.state.cropParams.odd &&

                  }

            </column>
          </row>
          <row className="capture-info">
            <column size="6">
              {workflow.get('images').length} pages
            </column>
            {speed > 0 &&
            <column size="6">
              {speed} pages/hour
            </column>}
          </row>
          <row>

                  <fnButton title="Discard last capture and take a new one"
                            callback={this.handleRetake} secondary='true'>

                  </fnButton>

                  <fnButton title="Trigger capture"
                            callback={this.handleCapture}>

                  </fnButton>

                  <fnButton title="Configure devices"
                            callback={this.toggleConfigModal} secondary='true'>

                  </fnButton>

                  <fnButton title="Finish capture and return to workflow list"
                            callback={this.handleFinish} complete={true}>

                  </fnButton>

          </row>
          {!util.isTouchDevice() &&
          <row className="hide-for-touch">
            <column size="4" offset="4" className="shortcuts">
              Keyboard shortcuts:

Capture:
                  {_.map(captureKeys, function(key) {
                    return ({' '}{key.toUpperCase()});
                  })}

Retake: R

Finish: F

            </column>
          </row>}

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


<commit_before>const path = require('path');
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

module.exports = webpackMerge(commonConfig, {
    devtool: 'eval-source-map',
    devServer: {
        publicPath: '/',
        contentBase: path.resolve('dist'),
        hot: true,
        https: true,
        compress: true,
        overlay: {
            warnings: false,
            errors: true
        },
        port: 3000,
        historyApiFallback: true
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
});
<commit_msg>Use generated certs rather than internal webpack-dev-server ones<commit_after>const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

module.exports = webpackMerge(commonConfig, {
    devtool: 'eval-source-map',
    devServer: {
        publicPath: '/',
        contentBase: path.resolve('dist'),
        hot: true,
        https: {
            key: fs.readFileSync('./certs/server.key'),
            cert: fs.readFileSync('./certs/server.crt'),
            cacert: fs.readFileSync('./certs/ca.crt')
        },
        compress: true,
        overlay: {
            warnings: false,
            errors: true
        },
        port: 3000,
        historyApiFallback: true
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
});
<|endoftext|>


<commit_before>/**
 * @file
 * The top bar, which is displayed globally, is defined here.
 */

// Libraries
import React, {Component} from 'react';
import PropTypes from 'prop-types';

import {TopbarCartItem} from '../Cart/TopbarCartItem.component';

export class TopBarComponent extends Component {
  render() {
    const cart = this.props.pro ? <TopbarCartItem cart={this.props.cart}/> : null;

    let topBarCaption = 'Find en bog med DK5';
    if (this.props.pro) {
      topBarCaption = 'DK5 PRO';
    }

    let emptyCart;
    if (Object.keys(this.props.cart.contents).length) {
      emptyCart = ;  // eslint-disable-line no-script-url
    }

    return (

        {emptyCart}

            {topBarCaption}

        {cart}

    );
  }
}

TopBarComponent.displayName = 'TopBar';
TopBarComponent.propTypes = {
  cart: PropTypes.object.isRequired,
  pro: PropTypes.bool.isRequired
};

<commit_msg>#167: use bind<commit_after>/**
 * @file
 * The top bar, which is displayed globally, is defined here.
 */

// Libraries
import React, {Component} from 'react';
import PropTypes from 'prop-types';

import {TopbarCartItem} from '../Cart/TopbarCartItem.component';

export class TopBarComponent extends Component {
  clearCart() {
    location.reload();
  }

  render() {
    const cart = this.props.pro ? <TopbarCartItem cart={this.props.cart}/> : null;

    let topBarCaption = 'Find en bog med DK5';
    if (this.props.pro) {
      topBarCaption = 'DK5 PRO';
    }

    return (

       {Object.keys(this.props.cart.contents).length > 0 && }

            {topBarCaption}

        {cart}

    );
  }
}

TopBarComponent.displayName = 'TopBar';
TopBarComponent.propTypes = {
  cart: PropTypes.object.isRequired,
  pro: PropTypes.bool.isRequired
};

<|endoftext|>


<commit_before>import Field from '../field/Field';

export default class Multivalue extends Field {
  get dataValue() {
    const parent = super.dataValue;

    if (!parent && this.component.multiple) {
      return [];
    }
    return parent;
  }

  set dataValue(value) {
    super.dataValue = value;
  }

  get defaultValue() {
    if (this.component.multiple) {
      return [super.defaultValue];
    }
    return super.defaultValue;
  }

  get addAnother() {
    return this.t(this.component.addAnother || ' Add Another');
  }

  useWrapper() {
    return this.component.hasOwnProperty('multiple') && this.component.multiple;
  }

  render() {
    // If single value field.
    if (!this.useWrapper()) {
      return super.render(`<div ref="element">${this.renderElement(this.dataValue)}</div>`);
    }

    // If multiple value field.
    return super.render(this.renderTemplate('multiValueTable', {
      rows: this.dataValue.map(this.renderRow.bind(this)).join(''),
      disabled: this.disabled,
      addAnother: this.addAnother,
    }));
  }

  renderRow(value, index) {
    return this.renderTemplate('multiValueRow', {
      index,
      disabled: this.disabled,
      element: `${this.renderElement(value, index)}`,
    });
  }

  attach(dom) {
    super.attach(dom);
    this.loadRefs(dom, { addButton: 'multiple', input: 'multiple', removeRow: 'multiple' });

    this.refs.input.forEach(this.attachElement.bind(this));
    if (!this.component.multiple) {
      return;
    }

    this.refs.removeRow.forEach((removeButton, index) => {
      this.addEventListener(removeButton, 'click', (event) => {
        event.preventDefault();
        this.removeValue(index);
      });
    });

    // If single value field.
    this.refs.addButton.forEach((addButton) => {
      this.addEventListener(addButton, 'click', (event) => {
        event.preventDefault();
        this.addValue();
      });
    });
  }

  /**
   * Adds a new empty value to the data array.
   */
  addNewValue(value) {
    if (value === undefined) {
      value = this.component.defaultValue ?
      this.component.defaultValue : this.emptyValue;
    }
    let dataValue = this.dataValue || [];
    if (!Array.isArray(dataValue)) {
      dataValue = [dataValue];
    }

    if (Array.isArray(value)) {
      dataValue = dataValue.concat(value);
    }
    else {
      dataValue.push(value);
    }
    this.dataValue = dataValue;
  }

  /**
   * Adds a new empty value to the data array, and add a new row to contain it.
   */
  addValue() {
    this.addNewValue();
    this.redraw();
    this.checkConditions(this.root ? this.root.data : this.data);
    this.restoreValue();
    if (this.root) {
      this.root.onChange();
    }
  }
}
<commit_msg>Fix textarea multivalue.<commit_after>import Field from '../field/Field';

export default class Multivalue extends Field {
  get dataValue() {
    const parent = super.dataValue;

    if (!parent && this.component.multiple) {
      return [];
    }
    return parent;
  }

  set dataValue(value) {
    super.dataValue = value;
  }

  get defaultValue() {
    if (this.component.multiple) {
      return [super.defaultValue];
    }
    return super.defaultValue;
  }

  get addAnother() {
    return this.t(this.component.addAnother || ' Add Another');
  }

  useWrapper() {
    return this.component.hasOwnProperty('multiple') && this.component.multiple;
  }

  render() {
    // If single value field.
    if (!this.useWrapper()) {
      return super.render(`<div ref="element">${this.renderElement(this.dataValue)}</div>`);
    }

    // If multiple value field.
    return super.render(this.renderTemplate('multiValueTable', {
      rows: this.dataValue.map(this.renderRow.bind(this)).join(''),
      disabled: this.disabled,
      addAnother: this.addAnother,
    }));
  }

  renderRow(value, index) {
    return this.renderTemplate('multiValueRow', {
      index,
      disabled: this.disabled,
      element: `${this.renderElement(value, index)}`,
    });
  }

  attach(dom) {
    super.attach(dom);
    this.loadRefs(dom, { addButton: 'multiple', input: 'multiple', removeRow: 'multiple' });

    this.refs.input.forEach(this.attachElement.bind(this));
    if (!this.component.multiple) {
      return;
    }

    this.refs.removeRow.forEach((removeButton, index) => {
      this.addEventListener(removeButton, 'click', (event) => {
        event.preventDefault();
        this.removeValue(index);
      });
    });

    // If single value field.
    this.refs.addButton.forEach((addButton) => {
      this.addEventListener(addButton, 'click', (event) => {
        event.preventDefault();
        this.addValue();
      });
    });
  }

  /**
   * Adds a new empty value to the data array.
   */
  addNewValue(value) {
    if (value === undefined) {
      value = this.component.defaultValue ?
      this.component.defaultValue : this.emptyValue;
      // If default is an empty aray, default back to empty value.
      if (Array.isArray(value) && value.length === 0) {
        value = this.emptyValue;
      }
    }
    let dataValue = this.dataValue || [];
    if (!Array.isArray(dataValue)) {
      dataValue = [dataValue];
    }

    if (Array.isArray(value)) {
      dataValue = dataValue.concat(value);
    }
    else {
      dataValue.push(value);
    }
    this.dataValue = dataValue;
  }

  /**
   * Adds a new empty value to the data array, and add a new row to contain it.
   */
  addValue() {
    this.addNewValue();
    this.redraw();
    this.checkConditions(this.root ? this.root.data : this.data);
    this.restoreValue();
    if (this.root) {
      this.root.onChange();
    }
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import Dropzone from 'react-dropzone';
import {BernieText, BernieColors} from './styles/bernie-css'
import {RaisedButton} from 'material-ui'
import Papa from 'papaparse'
import qs from 'querystring'
import superagent from 'superagent'
import downloadCSV from '../helpers/downloadCSV'

export default class AdminEventUploadRsvps extends React.Component {
  styles = {
    dropzoneStyle: {
      font: BernieText.default,
      fontSize: '1.5em',
      color: BernieColors.green,
      margin: '0 auto',
      width: 500,
      height: 500,
      borderWidth: 2,
      borderColor: BernieColors.green,
      borderStyle: 'dashed',
      borderRadius: 5,
      position: 'relative',
      textAlign: 'center',
      overflow: 'scroll'
    },
    dropzoneActiveStyle: {
      borderStyle: 'solid',
      backgroundColor: '#eee'
    },
    dropzoneRejectStyle: {
      borderStyle: 'solid',
      backgroundColor: '#ffdddd'
    },
    fileStatus: {
      ...BernieText.secondaryTitle,
      fontSize: '0.75em',
      textAlign: 'left',
      paddingLeft: 20
    }
  }

  state = {
    filesProcessed: {},
  }

  allowedKeys = new Set([
    'event_id',
    'event_id_obfuscated',
    'will_attend',
    'guid',
    'email',
    'zip',
    'country',
    'shift_ids',
    'phone',
    'employer',
    'occupation',
    'addr1',
    'addr2',
    'addr3',
    'city',
    'comment',
    // 'firstname', // not sure why first and last name don't work, but they're also not immediately necessary
    'guests',
    'is_potential_volunteer',
    'is_reminder_sent',
    // 'lastname',
    'pledge_amt',
    'pledge_method',
    'state_cd',
    'zip_4'
  ])

  createRSVPs(row, fileName, [...eventIdKeys]) {
    const obfuscatedIds = eventIdKeys
      .filter((key) => row[key])
      .map((key) => row[key])

    row.event_id_obfuscated = [row.event_id_obfuscated, ...obfuscatedIds].join(',')

    Object.keys(row).forEach((key) => {
      if (!this.allowedKeys.has(key))
        delete row[key]
    })
    let url = `/events/add-rsvp?${qs.stringify(row)}`

    superagent.post(url, (err, res) => {
      let filesProcessed = this.state.filesProcessed
      let processObj = filesProcessed[fileName]
      processObj.processedRows += 1

      console.log(res.body)

      if (err) {
        processObj.errors.push(res.body)
      }
      filesProcessed[fileName] = processObj
      this.setState(filesProcessed)
    })
  }

  onDrop = (files) => {
    let eventIdKeys
    const transformFields = (chunk) => {
      eventIdKeys = new Set(chunk.match(/(Event\s\d+\sID)/g))

      return chunk
        .replace(/Email Address/i, 'email')
        .replace(/First Name/i, 'firstname')
        .replace(/Last Name/i, 'lastname')
        .replace(/Phone Number/i, 'phone')
        .replace(/Zip/i, 'zip')
        .replace(/DWID/i, 'guid')
    }

    files.forEach((file) => {
      let reader = new FileReader();
      reader.onload = (e) => {
        let text = reader.result;
        let data = Papa.parse(text, {header: true, beforeFirstChunk: transformFields}).data
        let processObj = {
          totalRows: data.length,
          processedRows: 0,
          errors: []
        }
        let currentFiles = this.state.filesProcessed
        currentFiles[file.name] = processObj
        this.setState({filesProcessed: currentFiles})

        for (let i=0; i<data.length; i++){
          this.createRSVPs(data[i], file.name, eventIdKeys)
        }
      }
      reader.readAsText(file, 'utf8');
    })
  }

  renderFileProgress() {
    let count = 0
    let renderErrors = (fileObj) => {
      if (fileObj.errors.length === 0)
        return

      return (

          {fileObj.errors.map((error) => {
            return (

                {`${error.email}, Zip: ${error.zip}, Phone: ${error.phone}`}

                {error.error}

            )
          })}

      )
    }
    let renderDownloadErrors = (fileObj, fileName) => {
      if (fileObj.errors.length === 0)
        return

      return (
        <RaisedButton
          label="Download & Fix Errors"
          primary={true}
          onTouchTap={() => {
            const csv = Papa.unparse(fileObj.errors)

            // modify file name to include 'errors'
            fileName = fileName.split('.')
            const fileExtension = fileName[fileName.length - 1]
            fileName[fileName.length - 1] = 'errors'
            fileName.push(fileExtension)

            // download the file
            downloadCSV(csv, fileName.join('.'))
          }}
        />
      )
    }
    let fileNodes = Object.keys(this.state.filesProcessed).map((fileName) => {
      count = count + 1
      let fileObj = this.state.filesProcessed[fileName]
      let color = (fileObj.errors.length === 0 ? (fileObj.totalRows === fileObj.processedRows ? BernieColors.green : BernieColors.gray) : BernieColors.red)

      return (

            {fileName}: {fileObj.processedRows}/{fileObj.totalRows}
            {renderErrors(fileObj)}

          {renderDownloadErrors(fileObj, fileName)}

      )
    })
    return (

        {fileNodes}

    )
  }

  render() {
    return (

        <Dropzone
          onDrop={this.onDrop}
          style={this.styles.dropzoneStyle}
          activeStyle={this.styles.dropzoneActiveStyle}
          rejectStyle={this.styles.dropzoneRejectStyle}
          disableClick={true}
        >

              Drop your CSVs of RSVPs here!

              CSV with column headers - any of the values under "Query Parameters" here.

          {this.renderFileProgress()}
        </Dropzone>

    );
  }
}<commit_msg>Improve error styling<commit_after>import React from 'react'
import Dropzone from 'react-dropzone';
import {BernieText, BernieColors} from './styles/bernie-css'
import {RaisedButton} from 'material-ui'
import Papa from 'papaparse'
import qs from 'querystring'
import superagent from 'superagent'
import downloadCSV from '../helpers/downloadCSV'

export default class AdminEventUploadRsvps extends React.Component {
  styles = {
    dropzoneStyle: {
      font: BernieText.default,
      fontSize: '1.5em',
      color: BernieColors.green,
      margin: '0 auto',
      width: 500,
      height: 500,
      borderWidth: 2,
      borderColor: BernieColors.green,
      borderStyle: 'dashed',
      borderRadius: 5,
      position: 'relative',
      textAlign: 'center',
      overflow: 'scroll'
    },
    dropzoneActiveStyle: {
      borderStyle: 'solid',
      backgroundColor: '#eee'
    },
    dropzoneRejectStyle: {
      borderStyle: 'solid',
      backgroundColor: '#ffdddd'
    },
    fileStatus: {
      ...BernieText.secondaryTitle,
      fontSize: '0.75em',
      textAlign: 'left',
      paddingLeft: 20
    }
  }

  state = {
    filesProcessed: {},
  }

  allowedKeys = new Set([
    'event_id',
    'event_id_obfuscated',
    'will_attend',
    'guid',
    'email',
    'zip',
    'country',
    'shift_ids',
    'phone',
    'employer',
    'occupation',
    'addr1',
    'addr2',
    'addr3',
    'city',
    'comment',
    // 'firstname', // not sure why first and last name don't work, but they're also not immediately necessary
    'guests',
    'is_potential_volunteer',
    'is_reminder_sent',
    // 'lastname',
    'pledge_amt',
    'pledge_method',
    'state_cd',
    'zip_4'
  ])

  createRSVPs(row, fileName, [...eventIdKeys]) {
    const obfuscatedIds = eventIdKeys
      .filter((key) => row[key])
      .map((key) => row[key])

    row.event_id_obfuscated = [row.event_id_obfuscated, ...obfuscatedIds].join(',')

    Object.keys(row).forEach((key) => {
      if (!this.allowedKeys.has(key))
        delete row[key]
    })
    let url = `/events/add-rsvp?${qs.stringify(row)}`

    superagent.post(url, (err, res) => {
      let filesProcessed = this.state.filesProcessed
      let processObj = filesProcessed[fileName]
      processObj.processedRows += 1

      console.log(res.body)

      if (err) {
        processObj.errors.push(res.body)
      }
      filesProcessed[fileName] = processObj
      this.setState(filesProcessed)
    })
  }

  onDrop = (files) => {
    let eventIdKeys
    const transformFields = (chunk) => {
      eventIdKeys = new Set(chunk.match(/(Event\s\d+\sID)/g))

      return chunk
        .replace(/Email Address/i, 'email')
        .replace(/First Name/i, 'firstname')
        .replace(/Last Name/i, 'lastname')
        .replace(/Phone Number/i, 'phone')
        .replace(/Zip/i, 'zip')
        .replace(/DWID/i, 'guid')
    }

    files.forEach((file) => {
      let reader = new FileReader();
      reader.onload = (e) => {
        let text = reader.result;
        let data = Papa.parse(text, {header: true, beforeFirstChunk: transformFields}).data
        let processObj = {
          totalRows: data.length,
          processedRows: 0,
          errors: []
        }
        let currentFiles = this.state.filesProcessed
        currentFiles[file.name] = processObj
        this.setState({filesProcessed: currentFiles})

        for (let i=0; i<data.length; i++){
          this.createRSVPs(data[i], file.name, eventIdKeys)
        }
      }
      reader.readAsText(file, 'utf8');
    })
  }

  renderFileProgress() {
    let count = 0
    let renderErrors = (fileObj) => {
      if (fileObj.errors.length === 0)
        return

      return (

          {fileObj.errors.map((error) => {
            return (

                {`${error.email}, Zip: ${error.zip}, Phone: ${error.phone}`}

                {error.error}

            )
          })}

      )
    }
    let renderDownloadErrors = (fileObj, fileName) => {
      if (fileObj.errors.length === 0)
        return

      return (
        <RaisedButton
          label="Download & Fix Errors"
          primary={true}
          style={{marginTop: '1em', marginBottom: '1em'}}
          onTouchTap={() => {
            const csv = Papa.unparse(fileObj.errors)

            // modify file name to include 'errors'
            fileName = fileName.split('.')
            const fileExtension = fileName[fileName.length - 1]
            fileName[fileName.length - 1] = 'errors'
            fileName.push(fileExtension)

            // download the file
            downloadCSV(csv, fileName.join('.'))
          }}
        />
      )
    }
    let fileNodes = Object.keys(this.state.filesProcessed).map((fileName) => {
      count = count + 1
      let fileObj = this.state.filesProcessed[fileName]
      let color = (fileObj.errors.length === 0 ? (fileObj.totalRows === fileObj.processedRows ? BernieColors.green : BernieColors.gray) : BernieColors.red)

      return (

            {fileName}: {fileObj.processedRows}/{fileObj.totalRows}
            {renderErrors(fileObj)}

          {renderDownloadErrors(fileObj, fileName)}

      )
    })
    return (

        {fileNodes}

    )
  }

  render() {
    return (

        <Dropzone
          onDrop={this.onDrop}
          style={this.styles.dropzoneStyle}
          activeStyle={this.styles.dropzoneActiveStyle}
          rejectStyle={this.styles.dropzoneRejectStyle}
          disableClick={true}
        >

              Drop your CSVs of RSVPs here!

              CSV with column headers - any of the values under "Query Parameters" here.

          {this.renderFileProgress()}
        </Dropzone>

    );
  }
}<|endoftext|>


<commit_before>const expect = require('chai').expect;
const QueryString = require('../query_string');

describe('QueryString', () => {
    describe('.queryStringToObject', () => {
        it('returns empty object for empty string', () => {
            expect(QueryString.queryStringToObject('')).to.deep.eq({});
        });
        it('parses query with a single parameter', () => {
            expect(QueryString.queryStringToObject('?anchor=otc')).to.deep.eq({
                anchor: 'otc',
            });
        });
        it('parses query with multiple parameters', () => {
            expect(QueryString.queryStringToObject('?anchor=otc&param2=42')).to.deep.eq({
                anchor: 'otc',
                param2: '42',
            });
            expect(QueryString.queryStringToObject('?param2=42&anchor=otc')).to.deep.eq({
                anchor: 'otc',
                param2: '42',
            });
        })
    });

    describe('.removeParamFromQueryString', () => {
        it('works for empty query', () => {
            expect(QueryString.removeParamFromQueryString('', 'anchor')).to.eq('');
        });
        it('doesn\'t modify query without specified key', () => {
            expect(QueryString.removeParamFromQueryString('?a=123', 'anchor')).to.eq('?a=123');
            expect(QueryString.removeParamFromQueryString('?a=123&b=test', 'anchor')).to.eq('?a=123&b=test');
        });
        it('removes specified key value pair', () => {
            expect(QueryString.removeParamFromQueryString('?anchor=NZT', 'anchor').to.eq(''));
            expect(QueryString.removeParamFromQueryString('?a=Hello%20World&b=10', 'a').to.eq('?b=10'));
        });
    });
});<commit_msg>fix faulty tests<commit_after>const expect = require('chai').expect;
const QueryString = require('../query_string');

describe('QueryString', () => {
    describe('.queryStringToObject', () => {
        it('returns empty object for empty string', () => {
            expect(QueryString.queryStringToObject('')).to.deep.eq({});
        });
        it('parses query with a single parameter', () => {
            expect(QueryString.queryStringToObject('?anchor=otc')).to.deep.eq({
                anchor: 'otc',
            });
        });
        it('parses query with multiple parameters', () => {
            expect(QueryString.queryStringToObject('?anchor=otc&param2=42')).to.deep.eq({
                anchor: 'otc',
                param2: '42',
            });
            expect(QueryString.queryStringToObject('?param2=42&anchor=otc')).to.deep.eq({
                anchor: 'otc',
                param2: '42',
            });
        })
    });

    describe('.removeParamFromQueryString', () => {
        it('works for empty query', () => {
            expect(QueryString.removeParamFromQueryString('', 'anchor')).to.eq('');
        });
        it('doesn\'t modify query without specified key', () => {
            expect(QueryString.removeParamFromQueryString('?a=123', 'anchor')).to.eq('?a=123');
            expect(QueryString.removeParamFromQueryString('?a=123&b=test', 'anchor')).to.eq('?a=123&b=test');
        });
        it('removes specified key value pair', () => {
            expect(QueryString.removeParamFromQueryString('?anchor=NZT', 'anchor')).to.eq('');
            expect(QueryString.removeParamFromQueryString('?a=Hello%20World&b=10', 'a')).to.eq('?b=10');
        });
    });
});<|endoftext|>


<commit_before>/**
 * Conditional sub fields
 *
 * Supports the ability to conditionally show and hide content based on the
 * value of a field.
 *
 * The element to be shown requires 2 attributes:
 * [data-controlled-by] - this is the name of the field which controls it
 * [data-control-value] - the value by which this element should be displayed
 *
 * It also requires a basic bit of style to work:
 *
 * [data-controlled-by] {
 *   display: none;
 * }
 * [data-controlled-by].is-active {
 *   display: block;
 * }
 *
 * Note: it is recommend to only apply this if javascript is enabled
 *
 * @example
 *
 *
 *   <input name="control-field" value="yes" type="radio">Yes
 *
 *
 *   <input name="control-field" value="no" type="radio">No
 *
 *
 *

 *   Visible when `control-field` equals `yes`
 *

 *
 */

const uniq = require('lodash/uniq')
const includes = require('lodash/includes')

const { addClass, removeClass } = require('../../lib/element-stuff.js')

const ConditionalSubfields = {
  selector: 'js-ConditionalSubfield',
  activeClass: 'is-active',
  onChangeHandler: null,
  controllers: [],
  wrapper: null,

  init (wrapper = document) {
    this.wrapper = wrapper

    this.cacheEls()

    if (this.controllers.length) {
      this.bindEvents()
      this.render()
    }
  },

  cacheEls () {
    const conditionalSubFields = this.wrapper.getElementsByClassName(this.selector)

    this.onChangeHandler = this.onChange.bind(this)
    this.controllers = uniq([...conditionalSubFields].map((controller, i) => {
      return controller.getAttribute('data-controlled-by')
    }))
  },

  bindEvents () {
    this.wrapper.addEventListener('change', this.onChangeHandler)
  },

  render () {
    this.controllers.forEach((controller) => {
      const field = this.wrapper.querySelector(`[name="${controller}"]`)

      this._handleField(field)
    })
  },

  destroy () {
    this.wrapper.removeEventListener('change', this.onChangeHandler)
  },

  onChange (evt) {
    const field = evt.target

    if (includes(this.controllers, field.name)) {
      this._handleField(field)
    }
  },

  _handleField (controlInput) {
    if (!controlInput) {
      return
    }
    const subFields = this.wrapper.querySelectorAll(`[data-controlled-by="${controlInput.name}"]`)
    const tagName = controlInput.tagName
    let controlInputValue

    if (tagName === 'SELECT') {
      controlInputValue = controlInput.value
    } else if (tagName === 'INPUT') {
      const type = controlInput.type

      if (type === 'radio' || type === 'checkbox') {
        const checked = this.wrapper.querySelector(`[name="${controlInput.name}"]:checked`)

        controlInputValue = checked ? checked.value : null
      } else {
        controlInputValue = controlInput.value
      }
    }

    Array.from(subFields).forEach((subField) => {
      const value = subField.getAttribute('data-control-value') + ''
      let isVisible

      if (controlInputValue === value) {
        isVisible = true
      } else {
        isVisible = false
      }

      this._toggleSubField(subField, isVisible)
    })
  },

  _toggleSubField (subField, isVisible) {
    if (isVisible) {
      addClass(subField, this.activeClass)
    } else {
      removeClass(subField, this.activeClass)
    }

    subField.setAttribute('aria-expanded', isVisible)
    subField.setAttribute('aria-hidden', !isVisible)

    if (!isVisible && !subField.getAttribute('data-persist-values')) {
      const children = subField.querySelectorAll('input, select, checkbox, textarea')

      Array.from(children).forEach((field) => {
        field.value = ''
        field.checked = false

        const event = this.wrapper.createEvent('HTMLEvents')
        event.initEvent('change', true, false)
        field.dispatchEvent(event)
      })
    }
  },
}

module.exports = ConditionalSubfields
<commit_msg>Simplify setting of a boolean in a variable<commit_after>/**
 * Conditional sub fields
 *
 * Supports the ability to conditionally show and hide content based on the
 * value of a field.
 *
 * The element to be shown requires 2 attributes:
 * [data-controlled-by] - this is the name of the field which controls it
 * [data-control-value] - the value by which this element should be displayed
 *
 * It also requires a basic bit of style to work:
 *
 * [data-controlled-by] {
 *   display: none;
 * }
 * [data-controlled-by].is-active {
 *   display: block;
 * }
 *
 * Note: it is recommend to only apply this if javascript is enabled
 *
 * @example
 *
 *
 *   <input name="control-field" value="yes" type="radio">Yes
 *
 *
 *   <input name="control-field" value="no" type="radio">No
 *
 *
 *

 *   Visible when `control-field` equals `yes`
 *

 *
 */

const uniq = require('lodash/uniq')
const includes = require('lodash/includes')

const { addClass, removeClass } = require('../../lib/element-stuff.js')

const ConditionalSubfields = {
  selector: 'js-ConditionalSubfield',
  activeClass: 'is-active',
  onChangeHandler: null,
  controllers: [],
  wrapper: null,

  init (wrapper = document) {
    this.wrapper = wrapper

    this.cacheEls()

    if (this.controllers.length) {
      this.bindEvents()
      this.render()
    }
  },

  cacheEls () {
    const conditionalSubFields = this.wrapper.getElementsByClassName(this.selector)

    this.onChangeHandler = this.onChange.bind(this)
    this.controllers = uniq([...conditionalSubFields].map((controller, i) => {
      return controller.getAttribute('data-controlled-by')
    }))
  },

  bindEvents () {
    this.wrapper.addEventListener('change', this.onChangeHandler)
  },

  render () {
    this.controllers.forEach((controller) => {
      const field = this.wrapper.querySelector(`[name="${controller}"]`)

      this._handleField(field)
    })
  },

  destroy () {
    this.wrapper.removeEventListener('change', this.onChangeHandler)
  },

  onChange (evt) {
    const field = evt.target

    if (includes(this.controllers, field.name)) {
      this._handleField(field)
    }
  },

  _handleField (controlInput) {
    if (!controlInput) {
      return
    }
    const subFields = this.wrapper.querySelectorAll(`[data-controlled-by="${controlInput.name}"]`)
    const tagName = controlInput.tagName
    let controlInputValue

    if (tagName === 'SELECT') {
      controlInputValue = controlInput.value
    } else if (tagName === 'INPUT') {
      const type = controlInput.type

      if (type === 'radio' || type === 'checkbox') {
        const checked = this.wrapper.querySelector(`[name="${controlInput.name}"]:checked`)

        controlInputValue = checked ? checked.value : null
      } else {
        controlInputValue = controlInput.value
      }
    }

    Array.from(subFields).forEach((subField) => {
      const value = subField.getAttribute('data-control-value') + ''
      let isVisible

      isVisible = controlInputValue === value

      this._toggleSubField(subField, isVisible)
    })
  },

  _toggleSubField (subField, isVisible) {
    if (isVisible) {
      addClass(subField, this.activeClass)
    } else {
      removeClass(subField, this.activeClass)
    }

    subField.setAttribute('aria-expanded', isVisible)
    subField.setAttribute('aria-hidden', !isVisible)

    if (!isVisible && !subField.getAttribute('data-persist-values')) {
      const children = subField.querySelectorAll('input, select, checkbox, textarea')

      Array.from(children).forEach((field) => {
        field.value = ''
        field.checked = false

        const event = this.wrapper.createEvent('HTMLEvents')
        event.initEvent('change', true, false)
        field.dispatchEvent(event)
      })
    }
  },
}

module.exports = ConditionalSubfields
<|endoftext|>


<commit_before>angular.module("hylo.createCommunity", [])
  .factory("CreateCommunityService", ['$timeout', '$analytics', function($timeout, $analytics) {
      var pickBanner = function pickBanner(imageModel, width, height) {
        // This will pick a file and store the original file in the 'orig/' subfolder, then convert it to a 200x200 image and store
        // that into the 'thumb/' folder with the same key.
        state.loadingBanner = true;

        var convertSettings = {
          width: 1600,
          format: 'jpg',
          fit: 'max',
          rotate: "exif"
        };

        filepicker.pickAndStore({
          mimetype: "image/*",
          multiple: false,
          services: ['COMPUTER', 'FACEBOOK', 'WEBCAM', 'GOOGLE_DRIVE', 'DROPBOX', 'INSTAGRAM', 'FLICKR', 'URL'],
          folders: false
        }, {
          location: "S3",
          path: 'orig/',
          access: 'public'
        }, function (InkBlobs) {
          $.each(InkBlobs, function(index, inkBlob) { // Convert the newly uploaded image to a thumbnail
            filepicker.convert(inkBlob, convertSettings, {
                  location: 'S3',
                  path: 'communityBanner/' + inkBlob.key.substring(5), // Store the converted image into the 'thumb/' subfolder with the same key as the orig file (by substringing off the 'orig/')
                  access: 'public'
                },
                function (new_InkBlob) { // Success Handler
                  // set the user avatar image by replacing the url with the cloudfront hosted image.
                  $timeout(function() {
                    state.loadingBanner = false;
                  }, 1000);

                  $timeout(function() {
                    var cloudFrontURL = new_InkBlob.url.replace('www.filepicker.io', 'd2kdz49m4u0buf.cloudfront.net');

                    $analytics.eventTrack("Uploaded image", {url: cloudFrontURL});

                    state.createForm.banner = cloudFrontURL;
                    state.bannerChosen = true;
                  });

                }, function(FPERROR) { // Error Handler
                  growl.addErrorMessage('Error uploading image.  Please try again.');
                  $timeout(function() {
                    state.loadingBanner = false;
                  });
                }, function(percent) { // On Progress Handler
                  $timeout(function() {
                    state.loadingBanner = true;
                  });
                }
            );
          });
        }, function (FPError) {
          $timeout(function() {
            state.loadingBanner = false;
            $log.error(FPError);
          })
          if (FPError.code != 101) {
            growl.addErrorMessage('Error uploading image.  Please try again.');
          }
        });
      };

      var pickIcon = function pickIcon() {
        // This will pick a file and store the original file in the 'orig/' subfolder, then convert it to a 200x200 image and store
        // that into the 'thumb/' folder with the same key.
        state.loadingIcon = true;

        var convertSettings = {
          width: 160,
          fit: 'clip',
          rotate: "exif"
        };

        filepicker.pickAndStore({
          mimetype: "image/*",
          multiple: false,
          services: ['COMPUTER', 'FACEBOOK', 'WEBCAM', 'GOOGLE_DRIVE', 'DROPBOX', 'INSTAGRAM', 'FLICKR', 'URL'],
          folders: false
        }, {
          location: "S3",
          path: 'orig/',
          access: 'public'
        }, function (InkBlobs) {
          $.each(InkBlobs, function(index, inkBlob) { // Convert the newly uploaded image to a thumbnail
            filepicker.convert(inkBlob, convertSettings, {
                  location: 'S3',
                  path: 'communityIcon/' + inkBlob.key.substring(5), // Store the converted image into the 'thumb/' subfolder with the same key as the orig file (by substringing off the 'orig/')
                  access: 'public'
                },
                function (new_InkBlob) { // Success Handler
                  // set the user avatar image by replacing the url with the cloudfront hosted image.
                  $timeout(function() {
                    state.loadingIcon = false;
                  }, 1000);

                  $timeout(function() {
                    var cloudFrontURL = new_InkBlob.url.replace('www.filepicker.io', 'd2kdz49m4u0buf.cloudfront.net');

                    $analytics.eventTrack("Uploaded image", {url: cloudFrontURL});

                    state.createForm.icon = cloudFrontURL;
                    state.iconChosen = true;
                  });

                }, function(FPERROR) { // Error Handler
                  growl.addErrorMessage('Error uploading image.  Please try again.');
                  $timeout(function() {
                    state.loadingIcon = false;
                  });
                }, function(percent) { // On Progress Handler
                  $timeout(function() {
                    state.loadingIcon = true;
                  });
                }
            );
          });
        }, function (FPError) {
          $timeout(function() {
            state.loadingIcon = false;
          })
          $log.error(FPError);
          if (FPError.code != 101) {
            growl.addErrorMessage('Error uploading image.  Please try again.');
          }
        });
      };

      var previous = function previous() {
        state.step--;
      }

      var next = function next(form) {
        // Trigger validation flag.
        form.submitted = true;

        // If form is invalid, return and let AngularJS show validation errors.
        if (form.$invalid) {
          return;
        }

        state.step++;
      }

      var state = {
        categoryListOptions: {
          'coworking': 'Co-working',
          'makerspace': 'Makerspace',
          'neighborhood': 'Neighborhood',
          'event': 'Special Event',
          'other': 'Other'
        },

        joiningOptions: {
          'private': 'Private - anyone may request to join',
          'public': 'Public - anyone can join'
        },

        joiningOptionsAdd: {
          private: {
            "moderatorOnly": "Only Moderators may add members",
            "anyone": "Any member may add members"
          },
          public: {
            "anyone": "Any"
          }
        },

        sharingOptions: {
          'yes': 'Yes, on Facebook, Twitter and LinkedIn',
          'no': 'No, I dont want seeds to be shared outside this community'
        },

        membershipOptions: {
          'yes': 'Yes',
          'no': 'No'
        },

        step: 1,

        next: next,

        previous: previous,

        pickIcon: pickIcon,

        pickBanner: pickBanner

      };

      var getNewState = function getNewState(isEditing) {
        state.step = 1;
        state.bannerChosen = isEditing;
        state.iconChosen = isEditing;

        return state;
      }

      return {
        getNewState: getNewState
      }

    }])
    .controller("CreateCommunityCtrl", [ "$scope", '$timeout', '$analytics', '$state', '$log', 'CreateCommunityResource', 'CreateCommunityService', "$rootScope", 'CurrentUser', '$analytics',
      function ($scope, $timeout, $analytics, $state, $log, CreateCommunityResource, CreateCommunityService, $rootScope, CurrentUser, $analytics) {

        $scope.state = CreateCommunityService.getNewState(false);

        $scope.state.createForm = {

          name: "",
          slug: "",
          coreIntention: "",
          category: "",
          joining1: "public",
          joining2: "anyone",
          sharing: "no",
          membershipType: "no",
          membershipAmount: 0,
          banner: "https://d3ngex8q79bk55.cloudfront.net/communities/default/communitiesDefaultBanner.jpg",
          icon: "https://d3ngex8q79bk55.cloudfront.net/communities/default/communitiesDefaultIcon.png"
        };

        $scope.cancel = function cancelCreate() {
          if ($scope.navigated) { // If the rootscope 'navigated' boolean is true, then just go back TODO change to Service method
            history.back();
          } else { // Otherwise, go home
            $state.go('home')
          }
        }

        $scope.submit = function() {

          var newCommunity = new CreateCommunityResource($scope.state.createForm);

          newCommunity.$save(function (value, respHeaders) {
            $analytics.eventTrack('Created new community');
            // Invoke scope function

            $rootScope.currentUser = CurrentUser.get();
            $analytics.eventTrack('createCommunity');
            $state.go("community", {community: value.slug});

          }, function (responseValue) {
            $log.error("error", responseValue);
          });

        }

      }
    ])

    .controller("EditCommunityCtrl", [ "$scope", '$timeout', '$analytics', '$state', '$log', 'CreateCommunityResource', 'CreateCommunityService', '$stateParams', '$rootScope', 'CurrentUser', '$analytics',
      function ($scope, $timeout, $analytics, $state, $log, CreateCommunityResource, CreateCommunityService, $stateParams, $rootScope, CurrentUser, $analytics) {

        $scope.state = CreateCommunityService.getNewState(true);

        var originalSlug;
        $scope.state.createForm = CreateCommunityResource.get({id: $stateParams.id}, function(createForm) {
          // We need to save the original slug in case the user modified the slug, but cancels the changes.
          originalSlug = createForm.slug;
        });

        $scope.cancel = function cancelEdit() {
            $state.go('community', {community: originalSlug});
        }

        $scope.submit = function() {

          var newCommunity = new CreateCommunityResource($scope.state.createForm);

          CreateCommunityResource.update({}, newCommunity, function (value, respHeaders) {
            $analytics.eventTrack('Created new community');
            // Invoke scope function

            $rootScope.currentUser = CurrentUser.get();
            $analytics.eventTrack('editCommunity');
            $state.go("community", {community: value.slug}, {reload: true});
          }, function (responseValue) {
            $log.error("error", responseValue);
          });
        }
      }
    ])

    .factory("CreateCommunityResource", ["$resource",
  function($resource) {
    return $resource("/rest/create/community/:id", null, {
      'update': { method:'PUT' }
    });
  }]);
<commit_msg>fix: add $log to CreateCommunityService<commit_after>angular.module("hylo.createCommunity", [])
  .factory("CreateCommunityService", ['$timeout', '$analytics', '$log', function($timeout, $analytics, $log) {
      var pickBanner = function pickBanner(imageModel, width, height) {
        // This will pick a file and store the original file in the 'orig/' subfolder, then convert it to a 200x200 image and store
        // that into the 'thumb/' folder with the same key.
        state.loadingBanner = true;

        var convertSettings = {
          width: 1600,
          format: 'jpg',
          fit: 'max',
          rotate: "exif"
        };

        filepicker.pickAndStore({
          mimetype: "image/*",
          multiple: false,
          services: ['COMPUTER', 'FACEBOOK', 'WEBCAM', 'GOOGLE_DRIVE', 'DROPBOX', 'INSTAGRAM', 'FLICKR', 'URL'],
          folders: false
        }, {
          location: "S3",
          path: 'orig/',
          access: 'public'
        }, function (InkBlobs) {
          $.each(InkBlobs, function(index, inkBlob) { // Convert the newly uploaded image to a thumbnail
            filepicker.convert(inkBlob, convertSettings, {
                  location: 'S3',
                  path: 'communityBanner/' + inkBlob.key.substring(5), // Store the converted image into the 'thumb/' subfolder with the same key as the orig file (by substringing off the 'orig/')
                  access: 'public'
                },
                function (new_InkBlob) { // Success Handler
                  // set the user avatar image by replacing the url with the cloudfront hosted image.
                  $timeout(function() {
                    state.loadingBanner = false;
                  }, 1000);

                  $timeout(function() {
                    var cloudFrontURL = new_InkBlob.url.replace('www.filepicker.io', 'd2kdz49m4u0buf.cloudfront.net');

                    $analytics.eventTrack("Uploaded image", {url: cloudFrontURL});

                    state.createForm.banner = cloudFrontURL;
                    state.bannerChosen = true;
                  });

                }, function(FPERROR) { // Error Handler
                  growl.addErrorMessage('Error uploading image.  Please try again.');
                  $timeout(function() {
                    state.loadingBanner = false;
                  });
                }, function(percent) { // On Progress Handler
                  $timeout(function() {
                    state.loadingBanner = true;
                  });
                }
            );
          });
        }, function (FPError) {
          $timeout(function() {
            state.loadingBanner = false;
            $log.error(FPError);
          })
          if (FPError.code != 101) {
            growl.addErrorMessage('Error uploading image.  Please try again.');
          }
        });
      };

      var pickIcon = function pickIcon() {
        // This will pick a file and store the original file in the 'orig/' subfolder, then convert it to a 200x200 image and store
        // that into the 'thumb/' folder with the same key.
        state.loadingIcon = true;

        var convertSettings = {
          width: 160,
          fit: 'clip',
          rotate: "exif"
        };

        filepicker.pickAndStore({
          mimetype: "image/*",
          multiple: false,
          services: ['COMPUTER', 'FACEBOOK', 'WEBCAM', 'GOOGLE_DRIVE', 'DROPBOX', 'INSTAGRAM', 'FLICKR', 'URL'],
          folders: false
        }, {
          location: "S3",
          path: 'orig/',
          access: 'public'
        }, function (InkBlobs) {
          $.each(InkBlobs, function(index, inkBlob) { // Convert the newly uploaded image to a thumbnail
            filepicker.convert(inkBlob, convertSettings, {
                  location: 'S3',
                  path: 'communityIcon/' + inkBlob.key.substring(5), // Store the converted image into the 'thumb/' subfolder with the same key as the orig file (by substringing off the 'orig/')
                  access: 'public'
                },
                function (new_InkBlob) { // Success Handler
                  // set the user avatar image by replacing the url with the cloudfront hosted image.
                  $timeout(function() {
                    state.loadingIcon = false;
                  }, 1000);

                  $timeout(function() {
                    var cloudFrontURL = new_InkBlob.url.replace('www.filepicker.io', 'd2kdz49m4u0buf.cloudfront.net');

                    $analytics.eventTrack("Uploaded image", {url: cloudFrontURL});

                    state.createForm.icon = cloudFrontURL;
                    state.iconChosen = true;
                  });

                }, function(FPERROR) { // Error Handler
                  growl.addErrorMessage('Error uploading image.  Please try again.');
                  $timeout(function() {
                    state.loadingIcon = false;
                  });
                }, function(percent) { // On Progress Handler
                  $timeout(function() {
                    state.loadingIcon = true;
                  });
                }
            );
          });
        }, function (FPError) {
          $timeout(function() {
            state.loadingIcon = false;
          })
          $log.error(FPError);
          if (FPError.code != 101) {
            growl.addErrorMessage('Error uploading image.  Please try again.');
          }
        });
      };

      var previous = function previous() {
        state.step--;
      }

      var next = function next(form) {
        // Trigger validation flag.
        form.submitted = true;

        // If form is invalid, return and let AngularJS show validation errors.
        if (form.$invalid) {
          return;
        }

        state.step++;
      }

      var state = {
        categoryListOptions: {
          'coworking': 'Co-working',
          'makerspace': 'Makerspace',
          'neighborhood': 'Neighborhood',
          'event': 'Special Event',
          'other': 'Other'
        },

        joiningOptions: {
          'private': 'Private - anyone may request to join',
          'public': 'Public - anyone can join'
        },

        joiningOptionsAdd: {
          private: {
            "moderatorOnly": "Only Moderators may add members",
            "anyone": "Any member may add members"
          },
          public: {
            "anyone": "Any"
          }
        },

        sharingOptions: {
          'yes': 'Yes, on Facebook, Twitter and LinkedIn',
          'no': 'No, I dont want seeds to be shared outside this community'
        },

        membershipOptions: {
          'yes': 'Yes',
          'no': 'No'
        },

        step: 1,

        next: next,

        previous: previous,

        pickIcon: pickIcon,

        pickBanner: pickBanner

      };

      var getNewState = function getNewState(isEditing) {
        state.step = 1;
        state.bannerChosen = isEditing;
        state.iconChosen = isEditing;

        return state;
      }

      return {
        getNewState: getNewState
      }

    }])
    .controller("CreateCommunityCtrl", [ "$scope", '$timeout', '$analytics', '$state', '$log', 'CreateCommunityResource', 'CreateCommunityService', "$rootScope", 'CurrentUser', '$analytics',
      function ($scope, $timeout, $analytics, $state, $log, CreateCommunityResource, CreateCommunityService, $rootScope, CurrentUser, $analytics) {

        $scope.state = CreateCommunityService.getNewState(false);

        $scope.state.createForm = {

          name: "",
          slug: "",
          coreIntention: "",
          category: "",
          joining1: "public",
          joining2: "anyone",
          sharing: "no",
          membershipType: "no",
          membershipAmount: 0,
          banner: "https://d3ngex8q79bk55.cloudfront.net/communities/default/communitiesDefaultBanner.jpg",
          icon: "https://d3ngex8q79bk55.cloudfront.net/communities/default/communitiesDefaultIcon.png"
        };

        $scope.cancel = function cancelCreate() {
          if ($scope.navigated) { // If the rootscope 'navigated' boolean is true, then just go back TODO change to Service method
            history.back();
          } else { // Otherwise, go home
            $state.go('home')
          }
        }

        $scope.submit = function() {

          var newCommunity = new CreateCommunityResource($scope.state.createForm);

          newCommunity.$save(function (value, respHeaders) {
            $analytics.eventTrack('Created new community');
            // Invoke scope function

            $rootScope.currentUser = CurrentUser.get();
            $analytics.eventTrack('createCommunity');
            $state.go("community", {community: value.slug});

          }, function (responseValue) {
            $log.error("error", responseValue);
          });

        }

      }
    ])

    .controller("EditCommunityCtrl", [ "$scope", '$timeout', '$analytics', '$state', '$log', 'CreateCommunityResource', 'CreateCommunityService', '$stateParams', '$rootScope', 'CurrentUser', '$analytics',
      function ($scope, $timeout, $analytics, $state, $log, CreateCommunityResource, CreateCommunityService, $stateParams, $rootScope, CurrentUser, $analytics) {

        $scope.state = CreateCommunityService.getNewState(true);

        var originalSlug;
        $scope.state.createForm = CreateCommunityResource.get({id: $stateParams.id}, function(createForm) {
          // We need to save the original slug in case the user modified the slug, but cancels the changes.
          originalSlug = createForm.slug;
        });

        $scope.cancel = function cancelEdit() {
            $state.go('community', {community: originalSlug});
        }

        $scope.submit = function() {

          var newCommunity = new CreateCommunityResource($scope.state.createForm);

          CreateCommunityResource.update({}, newCommunity, function (value, respHeaders) {
            $analytics.eventTrack('Created new community');
            // Invoke scope function

            $rootScope.currentUser = CurrentUser.get();
            $analytics.eventTrack('editCommunity');
            $state.go("community", {community: value.slug}, {reload: true});
          }, function (responseValue) {
            $log.error("error", responseValue);
          });
        }
      }
    ])

    .factory("CreateCommunityResource", ["$resource",
  function($resource) {
    return $resource("/rest/create/community/:id", null, {
      'update': { method:'PUT' }
    });
  }]);
<|endoftext|>


<commit_before>/**
 * VIEW: Toolbar
 *
 */

var
    template = require("./templates/layout.tpl")
  , Header = require("./Header")
  , types = {
        people: require("../MenuModal/People")
      , organizations: require("../MenuModal/Organizations")
      , places: require("../MenuModal/Places")
      , dates: require("../MenuModal/Dates")
    };

module.exports = Backbone.Marionette.Layout.extend({

  //--------------------------------------
  //+ PUBLIC PROPERTIES / CONSTANTS
  //--------------------------------------

  template: template,

  ui: {
    modal: ".menu-modal",
    links: ".group-b a",
    content: ".menu-modal .content"
  },

  regions: {
    "header": ".menu-modal .header",
    "content": ".menu-modal .content"
  },

  modelEvents: {
    "change:counter": "render"
  },

  events: {
    "click .group-b a": "menuClicked"
  },

  //--------------------------------------
  //+ INHERITED / OVERRIDES
  //--------------------------------------

  active: "people",

  initialize: function(options){
    this.documentView = options.documentView;
  },

  onRender: function(){
    this.loadContent(this.active);
  },

  serializeData: function(){
    return _.extend({
      showDocumentStats: this.documentView ? false : true
    }, ((this.model && this.model.toJSON()) || {}));
  },

  //--------------------------------------
  //+ PUBLIC METHODS / GETTERS / SETTERS
  //--------------------------------------

  //--------------------------------------
  //+ EVENT HANDLERS
  //--------------------------------------

  menuClicked: function(e){
    var link = $(e.target).parents("a"),
      item = link.attr("data-menu");

    if (item && types.hasOwnProperty(item)){
      this.loadContent(item);
    }
  },

  loadContent: function(item){
    var self = this;
    window.aeolus.app.modalMentions.close();

    this.ui.links
      .removeClass("active")
      .filter("[data-menu=" + item + "]").addClass("active");

    this.active = item;

    this.ui.modal.show()
      .removeClass("drawer-people")
      .removeClass("drawer-dates")
      .removeClass("drawer-places")
      .removeClass("drawer-organizations")
      .addClass("drawer-" + item);

    var header = new Header({
      type: item
    });

    this.itemsCollection = this.model.getListByTypes(item);

    var content = new types[item]({
      collection: this.itemsCollection
    });

    this.header.show(header);
    this.content.show(content);

    this.ui.content.on("scroll", function(){
      window.aeolus.app.modalMentions.close();
    });

    header.on("filter", function(keyword){
       $("li", content.$el)
        .show()
        .not(":icontains(" + keyword + ")")
        .hide();
    });

    header.on("changeSort", function(opt){
      self.itemsCollection.changeSort(opt.by, opt.order);
    });
  }

  //--------------------------------------
  //+ PRIVATE AND PROTECTED METHODS
  //--------------------------------------

});<commit_msg>[aeolus] Menu sorted by mentions by default<commit_after>/**
 * VIEW: Toolbar
 *
 */

var
    template = require("./templates/layout.tpl")
  , Header = require("./Header")
  , types = {
        people: require("../MenuModal/People")
      , organizations: require("../MenuModal/Organizations")
      , places: require("../MenuModal/Places")
      , dates: require("../MenuModal/Dates")
    };

module.exports = Backbone.Marionette.Layout.extend({

  //--------------------------------------
  //+ PUBLIC PROPERTIES / CONSTANTS
  //--------------------------------------

  template: template,

  ui: {
    modal: ".menu-modal",
    links: ".group-b a",
    content: ".menu-modal .content"
  },

  regions: {
    "header": ".menu-modal .header",
    "content": ".menu-modal .content"
  },

  modelEvents: {
    "change:counter": "render"
  },

  events: {
    "click .group-b a": "menuClicked"
  },

  //--------------------------------------
  //+ INHERITED / OVERRIDES
  //--------------------------------------

  active: "people",

  initialize: function(options){
    this.documentView = options.documentView;
  },

  onRender: function(){
    this.loadContent(this.active);
  },

  serializeData: function(){
    return _.extend({
      showDocumentStats: this.documentView ? false : true
    }, ((this.model && this.model.toJSON()) || {}));
  },

  //--------------------------------------
  //+ PUBLIC METHODS / GETTERS / SETTERS
  //--------------------------------------

  //--------------------------------------
  //+ EVENT HANDLERS
  //--------------------------------------

  menuClicked: function(e){
    var link = $(e.target).parents("a"),
      item = link.attr("data-menu");

    if (item && types.hasOwnProperty(item)){
      this.loadContent(item);
    }
  },

  loadContent: function(item){
    var self = this;
    window.aeolus.app.modalMentions.close();

    this.ui.links
      .removeClass("active")
      .filter("[data-menu=" + item + "]").addClass("active");

    this.active = item;

    this.ui.modal.show()
      .removeClass("drawer-people")
      .removeClass("drawer-dates")
      .removeClass("drawer-places")
      .removeClass("drawer-organizations")
      .addClass("drawer-" + item);

    var header = new Header({
      type: item
    });

    this.itemsCollection = this.model.getListByTypes(item);
    this.itemsCollection.changeSort('mentions', 'desc');

    var content = new types[item]({
      collection: this.itemsCollection
    });

    this.header.show(header);
    this.content.show(content);

    this.ui.content.on("scroll", function(){
      window.aeolus.app.modalMentions.close();
    });

    header.on("filter", function(keyword){
       $("li", content.$el)
        .show()
        .not(":icontains(" + keyword + ")")
        .hide();
    });

    header.on("changeSort", function(opt){
      self.itemsCollection.changeSort(opt.by, opt.order);
    });
  }

  //--------------------------------------
  //+ PRIVATE AND PROTECTED METHODS
  //--------------------------------------

});<|endoftext|>


<commit_before>define(
  'ephox.robin.api.general.Structure',

  [
    'ephox.compass.Arr'
  ],

  function (Arr) {
    var blockList = [
      'body',
      'p',
      'div',
      'article',
      'aside',
      'figcaption',
      'figure',
      'footer',
      'header',
      'nav',
      'section',
      'ol',
      'ul',
      // --- NOTE, TagBoundaries has li here. That means universe.isBoundary => true for li tags.
      'table',
      'thead',
      'tbody',
      'caption',
      'tr',
      'td',
      'th',
      'h1',
      'h2',
      'h3',
      'h4',
      'h5',
      'h6',
      'blockquote',
      'pre',
      'address'
    ];

    var isList = function (universe, item) {
      var tagName = universe.property().name(item);
      return Arr.contains([ 'ol', 'ul' ], tagName);
    };

    var isBlock = function (universe, item) {
      var tagName = universe.property().name(item);
      return Arr.contains(blockList, tagName);
    };

    var isFormatting = function (universe, item) {
      var tagName = universe.property().name(item);
      return Arr.contains([ 'address', 'pre', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ], tagName);
    };

    var isContainer = function (universe, item) {
      return Arr.contains([ 'div', 'li', 'td', 'th', 'blockquote', 'body' ], universe.property().name(item));
    };

    var isEmptyTag = function (universe, item) {
      return Arr.contains(['br', 'img', 'hr'], universe.property().name(item));
    };

    return {
      isBlock: isBlock,
      isList: isList,
      isFormatting: isFormatting,
      isContainer: isContainer,
      isEmptyTag: isEmptyTag
    };
  }
);
<commit_msg>TBIO-2108: caption as a container<commit_after>define(
  'ephox.robin.api.general.Structure',

  [
    'ephox.compass.Arr'
  ],

  function (Arr) {
    var blockList = [
      'body',
      'p',
      'div',
      'article',
      'aside',
      'figcaption',
      'figure',
      'footer',
      'header',
      'nav',
      'section',
      'ol',
      'ul',
      // --- NOTE, TagBoundaries has li here. That means universe.isBoundary => true for li tags.
      'table',
      'thead',
      'tbody',
      'caption',
      'tr',
      'td',
      'th',
      'h1',
      'h2',
      'h3',
      'h4',
      'h5',
      'h6',
      'blockquote',
      'pre',
      'address'
    ];

    var isList = function (universe, item) {
      var tagName = universe.property().name(item);
      return Arr.contains([ 'ol', 'ul' ], tagName);
    };

    var isBlock = function (universe, item) {
      var tagName = universe.property().name(item);
      return Arr.contains(blockList, tagName);
    };

    var isFormatting = function (universe, item) {
      var tagName = universe.property().name(item);
      return Arr.contains([ 'address', 'pre', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ], tagName);
    };

    var isContainer = function (universe, item) {
      return Arr.contains([ 'div', 'li', 'td', 'th', 'blockquote', 'body', 'caption' ], universe.property().name(item));
    };

    var isEmptyTag = function (universe, item) {
      return Arr.contains(['br', 'img', 'hr'], universe.property().name(item));
    };

    return {
      isBlock: isBlock,
      isList: isList,
      isFormatting: isFormatting,
      isContainer: isContainer,
      isEmptyTag: isEmptyTag
    };
  }
);
<|endoftext|>