<commit_before>// Simulate config options from your production environment by
// customising the .env file in your project's root folder.
require('dotenv').config();

// Require keystone
var keystone = require('keystone');
var handlebars = require('express-handlebars');

// Initialise Keystone with your project's configuration.
// See http://keystonejs.com/guide/config for available options
// and documentation.

keystone.init({
	'name': 'Horses for Courses',
	'brand': 'Horses for Courses',

	'sass': 'public',
	'static': 'public',
	'favicon': 'public/favicon.ico',
	'views': 'templates/views',
	'view engine': 'hbs',

	'custom engine': handlebars.create({
		layoutsDir: 'templates/views/layouts',
		partialsDir: 'templates/views/partials',
		defaultLayout: 'default',
		helpers: new require('./templates/views/helpers')(),
		extname: '.hbs',
	}).engine,

	'auto update': true,
	'session': true,
	'auth': true,
	'user model': 'User',
});

// Load your project's Models
keystone.import('models');

// Setup common locals for your templates. The following are required for the
// bundled templates and layouts. Any runtime locals (that should be set uniquely
// for each request) should be added to ./routes/middleware.js
keystone.set('locals', {
	_: require('lodash'),
	env: keystone.get('env'),
	utils: keystone.utils,
	editable: keystone.content.editable,
});

// Load your project's Routes
keystone.set('routes', require('./routes'));

// Switch Keystone Email defaults to handlebars
keystone.Email.defaults.templateExt = 'hbs';
keystone.Email.defaults.templateEngine = require('handlebars');

// Configure the navigation bar in Keystone's Admin UI
keystone.set('nav', {
	users: 'users',
});

// Configure the environment
keystone.set('mongo', process.env.MONGODB_URI || 'mongodb://localhost/horses-for-courses'); // TODO: check this fallback

if (keystone.get('env') == 'production'){
	console.info('env = ' + keystone.get('env'));
    // keystone.set('cloudinary config', process.env.CLOUDINARY_URL); // Not yet needed
    keystone.set('cookie secret', process.env.COOKIE_SECRET);
}

// Start Keystone to connect to your database and initialise the web server

keystone.start();
<commit_msg>Removes console log<commit_after>// Simulate config options from your production environment by
// customising the .env file in your project's root folder.
require('dotenv').config();

// Require keystone
var keystone = require('keystone');
var handlebars = require('express-handlebars');

// Initialise Keystone with your project's configuration.
// See http://keystonejs.com/guide/config for available options
// and documentation.

keystone.init({
	'name': 'Horses for Courses',
	'brand': 'Horses for Courses',

	'sass': 'public',
	'static': 'public',
	'favicon': 'public/favicon.ico',
	'views': 'templates/views',
	'view engine': 'hbs',

	'custom engine': handlebars.create({
		layoutsDir: 'templates/views/layouts',
		partialsDir: 'templates/views/partials',
		defaultLayout: 'default',
		helpers: new require('./templates/views/helpers')(),
		extname: '.hbs',
	}).engine,

	'auto update': true,
	'session': true,
	'auth': true,
	'user model': 'User',
});

// Load your project's Models
keystone.import('models');

// Setup common locals for your templates. The following are required for the
// bundled templates and layouts. Any runtime locals (that should be set uniquely
// for each request) should be added to ./routes/middleware.js
keystone.set('locals', {
	_: require('lodash'),
	env: keystone.get('env'),
	utils: keystone.utils,
	editable: keystone.content.editable,
});

// Load your project's Routes
keystone.set('routes', require('./routes'));

// Switch Keystone Email defaults to handlebars
keystone.Email.defaults.templateExt = 'hbs';
keystone.Email.defaults.templateEngine = require('handlebars');

// Configure the navigation bar in Keystone's Admin UI
keystone.set('nav', {
	users: 'users',
});

// Configure the environment
keystone.set('mongo', process.env.MONGODB_URI || 'mongodb://localhost/horses-for-courses'); // TODO: check this fallback

if (keystone.get('env') == 'production'){
    // keystone.set('cloudinary config', process.env.CLOUDINARY_URL); // Not yet needed
    keystone.set('cookie secret', process.env.COOKIE_SECRET);
}

// Start Keystone to connect to your database and initialise the web server

keystone.start();
<|endoftext|>


<commit_before>const Promise = require('bluebird');
const MessageSkeleton = require('./messageSkeleton')

function mkOrderId(id, chId) {
    return (id + ':' + chId);
}

function invalidCmd(msg) {
    msg.text = ':warning: Invalid command. :warning:\n \
    Take a look at our help. :arrow_right:  `/food4me help`';
    return msg;
}

function help(msg) {
    msg.text = 'Start new order: `/food4me (start|create) {orderid}`\n \
Get info about existing order: `/food4me get {orderid}`\n \
Close ongoing order: `/food4me (stop|kill|close) {orderid}`\n \
Join order: `/food4me (join|set) {orderid} {your text}`\n \
Update your order item: `/food4me (update|set) {orderid} {your text}`\n \
Remove your order item: `/food4me (rm|cancel) {orderid}`\n \
Print this help: `/food4me (h|help|halp|man) {orderid}`';
    return msg;
}

function internalError(payload, msg) {
    msg.icon_emoji = ':frowning:';
    msg.text = 'Pssssst... ' + payload.user_name +
        '... There was a little accident... Dunno what happened exactly, \
        but I can\'t do what you have requested right now... :confused: I\'m pretty \
        sure those evil developers are in charge for this... :unamused:';
    return msg;
}

function getOrder(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            resolve(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id);
        dbo.getOrder(orderId)
        .then(obj => {
            if (obj) {
                msg.text = Object.keys(obj).reduce((prev, curr) => {
                    return prev + '\n*' + curr + '*:\n' + obj[curr];
                }, 'Items in *' + words[1] + '*:')
            } else {
                msg.text = ('Order *'+ words[1] +'* is empty os does not exists.');
            }
            resolve(msg);
        }).catch(() => resolve(internalError(payload, msg)));
    });
    return promise;
}

function list(dbo, payload, msg) {
    var promise = new Promise(function(resolve, reject) {
        const suffix = ':' + payload.channel_id;
        dbo.listOrdersWithSuffix(suffix)
        .then(list => {
            msg.text = list.reduce(elem => {
                const ord = elem.orderId.split(':')[1];
                const openStr = (elem.isOpen) ? 'open' : 'closed';
                return (ord + ': ' + openStr);
            }, 'Orders:');
        })
        .catch(() => resolve(internalError(payload, msg)));
    });
    return promise;
}

function startOrder(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            resolve(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id);
        dbo.createOrder(orderId)
        .then(() => {
            msg.response_type = 'in_channel';
            msg.text = 'Order session *' + words[1] + '* has been started.';
            resolve(msg);
        }).catch(err => {
            if (err === 'ALREADY_SET') {
                msg.text = 'Order *' + words[1] + '* has been already started.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

function stopOrder(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            reject(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id)
        dbo.closeOrder(orderId)
        .then(() => {
            msg.response_type = 'in_channel';
            msg.text = 'Order *' + words[1] + '* has been closed.';
            resolve(msg);
        })
        .catch(err => {
            if (err === 'CLOSED_OR_NULL') {
                msg.text = 'Order *' + words[1] + '* has been already closed or not exists.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

function addItem(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 3) {
            resolve(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id)
        dbo.addItemToOrder(orderId, payload.user_name, words.slice(3).join(' '))
        .then(() => {
            msg.text = 'Your order has been recorded. :wink:';
            resolve(msg);
        })
        .catch(err => {
            if (err === 'CLOSED_OR_NULL') {
                msg.text = 'Order *' + words[1] + '* has been already closed or not exists.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

function removeItem(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            reject(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id)
        dbo.removeItemFromOrder(orderId, payload.user_name)
        .then(() => {
            msg.text = 'Your item has been removed from order *' + words[1] + '*.';
            resolve(msg);
        }).catch(err => {
            if (err === 'CLOSED_OR_NULL') {
                msg.text = 'Order *' + words[1] + '* has been already closed or not exists.';
                resolve(msg);
            } else if (err === 'INVALID_ITEM') {
                msg.text = 'You haven\'t got any items in order *' + words[1] + '*.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

module.exports = function(dbo, payload, words) {
    var msgSkeleton = new MessageSkeleton();
    return {
        getOrder: getOrder.bind({}, dbo, payload, words, msgSkeleton),
        list: list.bind({}, dbo, payload, msgSkeleton),
        startOrder: startOrder.bind({}, dbo, payload, words, msgSkeleton),
        stopOrder: stopOrder.bind({}, dbo, payload, words, msgSkeleton),
        addItem: addItem.bind({}, dbo, payload, words, msgSkeleton),
        removeItem: removeItem.bind({}, dbo, payload, words, msgSkeleton),
        invalidCmd: invalidCmd.bind({}, msgSkeleton),
        help: help.bind({}, msgSkeleton)
    }
};
<commit_msg>bug on additem<commit_after>const Promise = require('bluebird');
const MessageSkeleton = require('./messageSkeleton')

function mkOrderId(id, chId) {
    return (id + ':' + chId);
}

function invalidCmd(msg) {
    msg.text = ':warning: Invalid command. :warning:\n \
    Take a look at our help. :arrow_right:  `/food4me help`';
    return msg;
}

function help(msg) {
    msg.text = 'Start new order: `/food4me (start|create) {orderName}`\n \
Get info about existing order: `/food4me get {orderName}`\n \
List existing orders: `/food4me get {orderName}`\n \
Close ongoing order: `/food4me (stop|kill|close) {orderName}`\n \
Join order: `/food4me (join|set) {orderName} {your text}`\n \
Update your order item: `/food4me (update|set) {orderName} {your text}`\n \
Remove your order item: `/food4me (rm|cancel) {orderName}`\n \
Print this help: `/food4me (h|help|halp|man) {orderName}`';
    return msg;
}

function internalError(payload, msg) {
    msg.icon_emoji = ':frowning:';
    msg.text = 'Pssssst... ' + payload.user_name +
        '... There was a little accident... Dunno what happened exactly, \
        but I can\'t do what you have requested right now... :confused: I\'m pretty \
        sure those evil developers are in charge for this... :unamused:';
    return msg;
}

function getOrder(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            resolve(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id);
        dbo.getOrder(orderId)
        .then(obj => {
            if (obj) {
                msg.text = Object.keys(obj).reduce((prev, curr) => {
                    return prev + '\n*' + curr + '*:\n' + obj[curr];
                }, 'Items in *' + words[1] + '*:')
            } else {
                msg.text = ('Order *'+ words[1] +'* is empty os does not exists.');
            }
            resolve(msg);
        }).catch(() => resolve(internalError(payload, msg)));
    });
    return promise;
}

function list(dbo, payload, msg) {
    var promise = new Promise(function(resolve, reject) {
        const suffix = ':' + payload.channel_id;
        dbo.listOrdersWithSuffix(suffix)
        .then(list => {
            msg.text = list.reduce(elem => {
                const ord = elem.orderId.split(':')[1];
                const openStr = (elem.isOpen) ? 'open' : 'closed';
                return (ord + ': ' + openStr);
            }, 'Orders:');
        })
        .catch(() => resolve(internalError(payload, msg)));
    });
    return promise;
}

function startOrder(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            resolve(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id);
        dbo.createOrder(orderId)
        .then(() => {
            msg.response_type = 'in_channel';
            msg.text = 'Order session *' + words[1] + '* has been started.';
            resolve(msg);
        }).catch(err => {
            if (err === 'ALREADY_SET') {
                msg.text = 'Order *' + words[1] + '* has been already started.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

function stopOrder(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            reject(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id)
        dbo.closeOrder(orderId)
        .then(() => {
            msg.response_type = 'in_channel';
            msg.text = 'Order *' + words[1] + '* has been closed.';
            resolve(msg);
        })
        .catch(err => {
            if (err === 'CLOSED_OR_NULL') {
                msg.text = 'Order *' + words[1] + '* has been already closed or not exists.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

function addItem(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 3) {
            resolve(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id)
        dbo.addItemToOrder(orderId, payload.user_name, words.slice(2).join(' '))
        .then(() => {
            msg.text = 'Your order has been recorded. :wink:';
            resolve(msg);
        })
        .catch(err => {
            if (err === 'CLOSED_OR_NULL') {
                msg.text = 'Order *' + words[1] + '* has been already closed or not exists.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

function removeItem(dbo, payload, words, msg) {
    var promise = new Promise(function(resolve, reject) {
        if (words.length < 2) {
            reject(invalidCmd(msg));
            return;
        }
        const orderId = mkOrderId(words[1], payload.channel_id)
        dbo.removeItemFromOrder(orderId, payload.user_name)
        .then(() => {
            msg.text = 'Your item has been removed from order *' + words[1] + '*.';
            resolve(msg);
        }).catch(err => {
            if (err === 'CLOSED_OR_NULL') {
                msg.text = 'Order *' + words[1] + '* has been already closed or not exists.';
                resolve(msg);
            } else if (err === 'INVALID_ITEM') {
                msg.text = 'You haven\'t got any items in order *' + words[1] + '*.';
                resolve(msg);
            } else {
                resolve(internalError(payload, msg));
            }
        });
    });
    return promise;
}

module.exports = function(dbo, payload, words) {
    var msgSkeleton = new MessageSkeleton();
    return {
        getOrder: getOrder.bind({}, dbo, payload, words, msgSkeleton),
        list: list.bind({}, dbo, payload, msgSkeleton),
        startOrder: startOrder.bind({}, dbo, payload, words, msgSkeleton),
        stopOrder: stopOrder.bind({}, dbo, payload, words, msgSkeleton),
        addItem: addItem.bind({}, dbo, payload, words, msgSkeleton),
        removeItem: removeItem.bind({}, dbo, payload, words, msgSkeleton),
        invalidCmd: invalidCmd.bind({}, msgSkeleton),
        help: help.bind({}, msgSkeleton)
    }
};
<|endoftext|>


<commit_before>const React = require('react');
const PropTypes = require('prop-types');
const Radium = require('radium');
const moment = require('moment');
const keycode = require('keycode');

import { withTheme } from './Theme';
const Icon = require('./Icon');

const { themeShape } = require('../constants/App');

const StyleUtils = require('../utils/Style');
const { deprecatePrimaryColor } = require('../utils/Deprecation');

export const getNewDateStateChange = ({
  code,
  focusedDay,
  startDate,
  endDate
}) => {
  let day = null;
  let currentDate = null;
  // Don't mutate existing focusedDay moment object
  const copyOfFocusedDay = moment(focusedDay);

  if (code === 'right') {
    day = copyOfFocusedDay.add(1, 'days').startOf('day');
  } else if (code === 'left') {
    day = copyOfFocusedDay.subtract(1, 'days').startOf('day');
  } else if (code === 'up') {
    day = copyOfFocusedDay.subtract(7, 'days').startOf('day');
  } else if (code === 'down') {
    day = copyOfFocusedDay.add(7, 'days').startOf('day');
  }

  if (day && (day.isBefore(startDate) || day.isAfter(endDate))) {
    currentDate = day.unix();
  }

  return day ?
  {
    focusedDay: day.unix(),
    ...(currentDate ? { currentDate } : {})
  } :
    null;
};

class Calendar extends React.Component {
  static propTypes = {
    locale: PropTypes.string,
    minimumDate: PropTypes.number,
    onDateSelect: PropTypes.func,
    primaryColor: PropTypes.string,
    selectedDate: PropTypes.number,
    style: PropTypes.object,
    theme: themeShape
  };

  static defaultProps = {
    locale: 'en',
    onDateSelect () {}
  };

  state = {
    currentDate:
      this.props.selectedDate || this.props.minimumDate || moment().unix(),
    focusedDay:
      this.props.selectedDate || this.props.minimumDate || moment().unix()
  };

  componentDidMount () {
    deprecatePrimaryColor(this.props);
  }

  componentWillReceiveProps (newProps) {
    if (
      newProps.selectedDate &&
      newProps.selectedDate !== this.props.selectedDate
    ) {
      this.setState({
        currentDate: newProps.selectedDate
      });
    }
  }

  componentDidUpdate (prevProps, prevState) {
    if (prevState.focusedDay !== this.state.focusedDay) {
      const focusedDayRef = this[this.state.focusedDay];

      if (focusedDayRef && focusedDayRef.focus) focusedDayRef.focus();
    }
  }

  _handleNextClick = () => {
    const currentDate = moment
      .unix(this.state.currentDate)
      .endOf('month')
      .add(1, 'd')
      .unix();

    this.setState({
      currentDate,
      focusedDay: currentDate
    });
  };

  _handlePreviousClick = () => {
    const currentDate = moment
      .unix(this.state.currentDate)
      .startOf('month')
      .subtract(1, 'm')
      .unix();

    this.setState({
      currentDate,
      focusedDay: currentDate
    });
  };

  _handleDayKeyDown = (e, day) => {
    if (keycode(e) === 'up' || keycode(e) === 'down') e.preventDefault();

    if (keycode(e) === 'enter')
      this.props.onDateSelect(day.unix(), e);

    const newDateStateChange = getNewDateStateChange({
      code: keycode(e),
      focusedDay: day,
      startDate: moment
        .unix(this.state.currentDate)
        .startOf('month')
        .startOf('week'),
      endDate: moment
        .unix(this.state.currentDate)
        .endOf('month')
        .endOf('week')
    });

    if (newDateStateChange !== null) this.setState(newDateStateChange);
  };

  _getWeeks = () => {
    const startDate = moment
      .unix(this.state.currentDate)
      .startOf('month')
      .startOf('week');
    const endDate = moment
      .unix(this.state.currentDate)
      .endOf('month')
      .endOf('week');
    const weekLength = 7;
    const weeks = [];
    let days = [];

    while (moment(startDate).isBefore(endDate)) {
      const day = startDate.clone();

      if (days.length < weekLength) {
        days.push(day);
        startDate.add(1, 'd');
      } else {
        days = [];
      }

      if (days.length === weekLength) {
        weeks.push(days);
      }
    }

    return weeks;
  };

  _renderMonthTable = styles => {
    const weeks = this._getWeeks();

    return weeks.map(week => {
      return (

          {week.map(day => {
            const isCurrentMonth = day.isSame(
              moment.unix(this.state.currentDate),
              'month'
            );
            const isSelectedDay = day.isSame(
              moment.unix(this.props.selectedDate),
              'day'
            );
            const isToday = day.isSame(moment(), 'day');
            const disabledDay = this.props.minimumDate ?
              day.isBefore(moment.unix(this.props.minimumDate), 'day') :
              null;

            return (
              <a
                aria-label={`${day.format('dddd, MMMM Do, YYYY')}${isSelectedDay ? ', Currently Selected' : ''}`}
                className='calendar-day'
                id={
                  day.isSame(moment.unix(this.state.focusedDay), 'day') ?
                    'focused-day' :
                    null
                }
                key={day.unix()}
                onClick={e => {
                  if (!disabledDay) this.props.onDateSelect(day.unix(), e);
                }}
                onKeyDown={e => this._handleDayKeyDown(e, day)}
                ref={dayAnchorTag => (this[day.unix()] = dayAnchorTag)}
                role='button'
                style={Object.assign(
                  {},
                  styles.calendarDay,
                  isCurrentMonth && styles.currentMonth,
                  disabledDay && styles.calendarDayDisabled,
                  isToday && styles.today,
                  isSelectedDay && styles.selectedDay
                )}
                tabIndex={
                  day.isSame(moment.unix(this.state.focusedDay), 'day') ?
                    0 :
                    null
                }
              >
                {day.date()}

            );
          })}

      );
    });
  };

  render () {
    const theme = StyleUtils.mergeTheme(
      this.props.theme,
      this.props.primaryColor
    );
    const styles = this.styles(theme);
    const daysOfWeek = [
      { label: 'Sunday', value: 'S' },
      { label: 'Monday', value: 'M' },
      { label: 'Tuesday', value: 'T' },
      { label: 'Wednesday', value: 'W' },
      { label: 'Thursday', value: 'T' },
      { label: 'Friday', value: 'F' },
      { label: 'Saturday', value: 'S' }
    ];
    const currentMonthText = moment.unix(this.state.currentDate).format('MMMM YYYY');
    const nextMonthText = moment.unix(this.state.currentDate).add(1, 'month').format('MMMM YYYY');
    const previousMonthText = moment.unix(this.state.currentDate).subtract(1, 'month').format('MMMM YYYY');

    return (

          <a
            aria-label={`Go back a month to ${previousMonthText}`}
            onClick={this._handlePreviousClick}
            onKeyUp={e => keycode(e) === 'enter' && this._handlePreviousClick()}
            role='button'
            tabIndex={0}
          >
            <Icon
              size={20}
              style={styles.calendayHeaderNav}
              type='caret-left'
            />

          <h2 aria-label={`Currently in ${currentMonthText}`} style={styles.calendarHeaderMonthText}>{currentMonthText}

          <a
            aria-label={`Go forward a month to ${nextMonthText}`}
            onClick={this._handleNextClick}
            onKeyUp={e => keycode(e) === 'enter' && this._handleNextClick()}
            role='button'
            tabIndex={0}
          >
            <Icon
              size={20}
              style={styles.calendayHeaderNav}
              type='caret-right'
            />

          {daysOfWeek.map((day) => {
            return (
              <div
                aria-label={`Column Header for ${day.label}`}
                key={day.label}
                role='option'
                style={styles.calendarWeekDay}
              >
                {day.value}

            );
          })}

        {this._renderMonthTable(styles)}

    );
  }

  styles = theme => {
    return {
      component: {
        backgroundColor: theme.Colors.WHITE,
        border: '1px solid ' + theme.Colors.GRAY_300,
        borderRadius: 3,
        boxSizing: 'border-box',
        marginTop: 10,
        padding: 20,
        ...this.props.style
      },

      //Calendar Header
      calendarHeaderBar: {
        alignItems: 'center',
        color: theme.Colors.GRAY_700,
        display: 'flex',
        fontSize: theme.FontSizes.LARGE,
        height: 30,
        justifyContent: 'space-between',
        marginBottom: 15,
        position: 'relative',
        textAlign: 'center'
      },
      calendayHeaderNav: {
        width: 35,
        cursor: 'pointer'
      },
      calendarHeaderMonthText: {
        fontSize: theme.FontSizes.LARGE,
        margin: 0,
        fontFamily: theme.Fonts.SEMIBOLD
      },

      //Calendar week
      calendarWeekHeader: {
        alignItems: 'center',
        color: theme.Colors.GRAY_500,
        display: 'flex',
        flex: '1 1 100%',
        fontFamily: theme.Fonts.SEMIBOLD,
        fontSize: theme.FontSizes.SMALL,
        height: 30,
        justifyContent: 'center',
        marginBottom: 2
      },
      calendarWeekDay: {
        textAlign: 'center',
        width: 35
      },
      calendarWeek: {
        display: 'flex',
        flex: '1 1 100%',
        justifyContent: 'center'
      },

      //Calenday table
      calendarDay: {
        alignItems: 'center',
        borderRadius: 3,
        boxSizing: 'border-box',
        color: theme.Colors.GRAY_300,
        cursor: 'pointer',
        display: 'flex',
        height: 30,
        justifyContent: 'center',
        marginBottom: 2,
        width: 35,

        ':hover': {
          border: '1px solid ' + theme.Colors.PRIMARY
        }
      },
      calendarDayDisabled: {
        color: theme.Colors.GRAY_300,

        ':hover': {
          cursor: 'default',
          border: 'none'
        }
      },

      today: {
        backgroundColor: theme.Colors.GRAY_300,
        color: theme.Colors.WHITE
      },
      currentMonth: {
        color: theme.Colors.GRAY_700
      },
      selectedDay: {
        backgroundColor: theme.Colors.PRIMARY,
        color: theme.Colors.WHITE
      }
    };
  };
}

export default withTheme(Radium(Calendar));
<commit_msg>Switch to using heading role on Calendar for currently selected month<commit_after>const React = require('react');
const PropTypes = require('prop-types');
const Radium = require('radium');
const moment = require('moment');
const keycode = require('keycode');

import { withTheme } from './Theme';
const Icon = require('./Icon');

const { themeShape } = require('../constants/App');

const StyleUtils = require('../utils/Style');
const { deprecatePrimaryColor } = require('../utils/Deprecation');

export const getNewDateStateChange = ({
  code,
  focusedDay,
  startDate,
  endDate
}) => {
  let day = null;
  let currentDate = null;
  // Don't mutate existing focusedDay moment object
  const copyOfFocusedDay = moment(focusedDay);

  if (code === 'right') {
    day = copyOfFocusedDay.add(1, 'days').startOf('day');
  } else if (code === 'left') {
    day = copyOfFocusedDay.subtract(1, 'days').startOf('day');
  } else if (code === 'up') {
    day = copyOfFocusedDay.subtract(7, 'days').startOf('day');
  } else if (code === 'down') {
    day = copyOfFocusedDay.add(7, 'days').startOf('day');
  }

  if (day && (day.isBefore(startDate) || day.isAfter(endDate))) {
    currentDate = day.unix();
  }

  return day ?
  {
    focusedDay: day.unix(),
    ...(currentDate ? { currentDate } : {})
  } :
    null;
};

class Calendar extends React.Component {
  static propTypes = {
    locale: PropTypes.string,
    minimumDate: PropTypes.number,
    onDateSelect: PropTypes.func,
    primaryColor: PropTypes.string,
    selectedDate: PropTypes.number,
    style: PropTypes.object,
    theme: themeShape
  };

  static defaultProps = {
    locale: 'en',
    onDateSelect () {}
  };

  state = {
    currentDate:
      this.props.selectedDate || this.props.minimumDate || moment().unix(),
    focusedDay:
      this.props.selectedDate || this.props.minimumDate || moment().unix()
  };

  componentDidMount () {
    deprecatePrimaryColor(this.props);
  }

  componentWillReceiveProps (newProps) {
    if (
      newProps.selectedDate &&
      newProps.selectedDate !== this.props.selectedDate
    ) {
      this.setState({
        currentDate: newProps.selectedDate
      });
    }
  }

  componentDidUpdate (prevProps, prevState) {
    if (prevState.focusedDay !== this.state.focusedDay) {
      const focusedDayRef = this[this.state.focusedDay];

      if (focusedDayRef && focusedDayRef.focus) focusedDayRef.focus();
    }
  }

  _handleNextClick = () => {
    const currentDate = moment
      .unix(this.state.currentDate)
      .endOf('month')
      .add(1, 'd')
      .unix();

    this.setState({
      currentDate,
      focusedDay: currentDate
    });
  };

  _handlePreviousClick = () => {
    const currentDate = moment
      .unix(this.state.currentDate)
      .startOf('month')
      .subtract(1, 'm')
      .unix();

    this.setState({
      currentDate,
      focusedDay: currentDate
    });
  };

  _handleDayKeyDown = (e, day) => {
    if (keycode(e) === 'up' || keycode(e) === 'down') e.preventDefault();

    if (keycode(e) === 'enter')
      this.props.onDateSelect(day.unix(), e);

    const newDateStateChange = getNewDateStateChange({
      code: keycode(e),
      focusedDay: day,
      startDate: moment
        .unix(this.state.currentDate)
        .startOf('month')
        .startOf('week'),
      endDate: moment
        .unix(this.state.currentDate)
        .endOf('month')
        .endOf('week')
    });

    if (newDateStateChange !== null) this.setState(newDateStateChange);
  };

  _getWeeks = () => {
    const startDate = moment
      .unix(this.state.currentDate)
      .startOf('month')
      .startOf('week');
    const endDate = moment
      .unix(this.state.currentDate)
      .endOf('month')
      .endOf('week');
    const weekLength = 7;
    const weeks = [];
    let days = [];

    while (moment(startDate).isBefore(endDate)) {
      const day = startDate.clone();

      if (days.length < weekLength) {
        days.push(day);
        startDate.add(1, 'd');
      } else {
        days = [];
      }

      if (days.length === weekLength) {
        weeks.push(days);
      }
    }

    return weeks;
  };

  _renderMonthTable = styles => {
    const weeks = this._getWeeks();

    return weeks.map(week => {
      return (

          {week.map(day => {
            const isCurrentMonth = day.isSame(
              moment.unix(this.state.currentDate),
              'month'
            );
            const isSelectedDay = day.isSame(
              moment.unix(this.props.selectedDate),
              'day'
            );
            const isToday = day.isSame(moment(), 'day');
            const disabledDay = this.props.minimumDate ?
              day.isBefore(moment.unix(this.props.minimumDate), 'day') :
              null;

            return (
              <a
                aria-label={`${day.format('dddd, MMMM Do, YYYY')}${isSelectedDay ? ', Currently Selected' : ''}`}
                className='calendar-day'
                id={
                  day.isSame(moment.unix(this.state.focusedDay), 'day') ?
                    'focused-day' :
                    null
                }
                key={day.unix()}
                onClick={e => {
                  if (!disabledDay) this.props.onDateSelect(day.unix(), e);
                }}
                onKeyDown={e => this._handleDayKeyDown(e, day)}
                ref={dayAnchorTag => (this[day.unix()] = dayAnchorTag)}
                role='button'
                style={Object.assign(
                  {},
                  styles.calendarDay,
                  isCurrentMonth && styles.currentMonth,
                  disabledDay && styles.calendarDayDisabled,
                  isToday && styles.today,
                  isSelectedDay && styles.selectedDay
                )}
                tabIndex={
                  day.isSame(moment.unix(this.state.focusedDay), 'day') ?
                    0 :
                    null
                }
              >
                {day.date()}

            );
          })}

      );
    });
  };

  render () {
    const theme = StyleUtils.mergeTheme(
      this.props.theme,
      this.props.primaryColor
    );
    const styles = this.styles(theme);
    const daysOfWeek = [
      { label: 'Sunday', value: 'S' },
      { label: 'Monday', value: 'M' },
      { label: 'Tuesday', value: 'T' },
      { label: 'Wednesday', value: 'W' },
      { label: 'Thursday', value: 'T' },
      { label: 'Friday', value: 'F' },
      { label: 'Saturday', value: 'S' }
    ];
    const currentMonthText = moment.unix(this.state.currentDate).format('MMMM YYYY');
    const nextMonthText = moment.unix(this.state.currentDate).add(1, 'month').format('MMMM YYYY');
    const previousMonthText = moment.unix(this.state.currentDate).subtract(1, 'month').format('MMMM YYYY');

    return (

          <a
            aria-label={`Go back a month to ${previousMonthText}`}
            onClick={this._handlePreviousClick}
            onKeyUp={e => keycode(e) === 'enter' && this._handlePreviousClick()}
            role='button'
            tabIndex={0}
          >
            <Icon
              size={20}
              style={styles.calendayHeaderNav}
              type='caret-left'
            />

          <div aria-label={`Currently in ${currentMonthText}`} role='heading'>{currentMonthText}

          <a
            aria-label={`Go forward a month to ${nextMonthText}`}
            onClick={this._handleNextClick}
            onKeyUp={e => keycode(e) === 'enter' && this._handleNextClick()}
            role='button'
            tabIndex={0}
          >
            <Icon
              size={20}
              style={styles.calendayHeaderNav}
              type='caret-right'
            />

          {daysOfWeek.map((day) => {
            return (
              <div
                aria-label={`Column Header for ${day.label}`}
                key={day.label}
                role='option'
                style={styles.calendarWeekDay}
              >
                {day.value}

            );
          })}

        {this._renderMonthTable(styles)}

    );
  }

  styles = theme => {
    return {
      component: {
        backgroundColor: theme.Colors.WHITE,
        border: '1px solid ' + theme.Colors.GRAY_300,
        borderRadius: 3,
        boxSizing: 'border-box',
        marginTop: 10,
        padding: 20,
        ...this.props.style
      },

      //Calendar Header
      calendarHeader: {
        alignItems: 'center',
        color: theme.Colors.GRAY_700,
        display: 'flex',
        fontSize: theme.FontSizes.LARGE,
        height: 30,
        justifyContent: 'space-between',
        marginBottom: 15,
        position: 'relative',
        textAlign: 'center'
      },
      calendayHeaderNav: {
        width: 35,
        cursor: 'pointer'
      },

      //Calendar week
      calendarWeekHeader: {
        alignItems: 'center',
        color: theme.Colors.GRAY_500,
        display: 'flex',
        flex: '1 1 100%',
        fontFamily: theme.Fonts.SEMIBOLD,
        fontSize: theme.FontSizes.SMALL,
        height: 30,
        justifyContent: 'center',
        marginBottom: 2
      },
      calendarWeekDay: {
        textAlign: 'center',
        width: 35
      },
      calendarWeek: {
        display: 'flex',
        flex: '1 1 100%',
        justifyContent: 'center'
      },

      //Calenday table
      calendarDay: {
        alignItems: 'center',
        borderRadius: 3,
        boxSizing: 'border-box',
        color: theme.Colors.GRAY_300,
        cursor: 'pointer',
        display: 'flex',
        height: 30,
        justifyContent: 'center',
        marginBottom: 2,
        width: 35,

        ':hover': {
          border: '1px solid ' + theme.Colors.PRIMARY
        }
      },
      calendarDayDisabled: {
        color: theme.Colors.GRAY_300,

        ':hover': {
          cursor: 'default',
          border: 'none'
        }
      },

      today: {
        backgroundColor: theme.Colors.GRAY_300,
        color: theme.Colors.WHITE
      },
      currentMonth: {
        color: theme.Colors.GRAY_700
      },
      selectedDay: {
        backgroundColor: theme.Colors.PRIMARY,
        color: theme.Colors.WHITE
      }
    };
  };
}

export default withTheme(Radium(Calendar));
<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react'
import { Box, Button, Flex, Heading, Text } from '@hackclub/design-system'
import { AutoSaver, Field, Form } from 'components/Forms'
import Table from 'components/Table'
import NoteForm from 'components/NoteForm'
import LoadingAnimation from 'components/LoadingAnimation'
import Login from 'components/apply/Login'
import LogoutButton from 'components/apply/LogoutButton'
import { Formik } from 'formik'
import api from 'api'

const Interviews = props => {
  const { application, authToken } = props

  const transformedApplication = {
    ...application,
    interview_duration: application.interview_duration
      ? application.interview_duration / 60
      : null,
    interviewed_at: application.interviewed_at
      ? application.interviewed_at.substr(0, 10)
      : null
  }

  return (
    <Formik
      initialValues={transformedApplication}
      enableReinitialize={true}
      onSubmit={(values, { props, setSubmitting }) => {
        const transformedValues = { ...values }
        if (values.interview_duration) {
          transformedValues.interview_duration = values.interview_duration * 60
        }
        api
          .patch(`v1/new_club_applications/${values.id}`, {
            authToken,
            data: transformedValues
          })
          .then(json => {
            updateApplicationList(json)
            setSubmitting(false)
          })
          .catch(e => {
            setSubmitting(false)
          })
      }}
    >
      {props => {
        const {
          handleChange,
          handleBlur,
          handleSubmit,
          isSubmitting,
          values
        } = props

        return (
          <Form onSubmit={handleSubmit}>
            <Heading>App #{values.id}</Heading>
            <Field
              name="interview_notes"
              label="Interview notes"
              onBlur={handleBlur}
              onChange={handleChange}
              value={values.interview_notes}
              type="textarea"
              renderMarkdown
            />
            <Field
              name="interviewed_at"
              label="Interview date"
              onBlur={handleBlur}
              onChange={handleChange}
              value={values.interviewed_at}
              type="date"
            />
            <Field
              name="interview_duration"
              label="Interview duration (minutes)"
              onBlur={handleBlur}
              onChange={handleChange}
              value={values.interview_duration}
              type="number"
            />
            <AutoSaver
              handleSubmit={handleSubmit}
              isSubmitting={isSubmitting}
              values={values}
            />
          </Form>
        )
      }}
    </Formik>
  )
}
class Notes extends Component {
  constructor(props) {
    super(props)

    this.state = {
      notes: [],
      status: 'loading'
    }

    this.loadNotes = this.loadNotes.bind(this)
    this.addNote = this.addNote.bind(this)
  }
  componentDidMount() {
    this.loadNotes(this.props.application.id)
  }
  componentWillReceiveProps(nextProps) {
    if (this.props.application.id !== nextProps.application.id) {
      this.loadNotes(nextProps.application.id)
    }
  }
  loadNotes(id) {
    const { authToken } = this.props
    api
      .get(`v1/new_club_applications/${id}/notes`, { authToken })
      .then(json => {
        this.setState({
          notes: json
        })
      })
  }
  addNote() {
    this.setState({ notes: [...this.state.notes, {}] })
  }
  render() {
    const { authToken, application, updateApplicationList } = this.props
    const { status, notes } = this.state
    return (
      <React.Fragment>
        <p>
          We have #{notes.length} notes on application #{application.id}
        </p>
        {notes.map((note, index) => (
          <NoteForm
            key={index}
            note={note}
            applicationId={application.id}
            authToken={authToken}
          />
        ))}
        <Button onClick={this.addNote} bg="info">
          Create Note
        </Button>
      </React.Fragment>
    )
  }
}

export default class extends Component {
  constructor(props) {
    super(props)
    this.state = { status: 'loading' }
    this.updateApplicationList = this.updateApplicationList.bind(this)
  }

  componentDidMount() {
    const authToken = window.localStorage.getItem('authToken')
    this.setState({ authToken })

    api
      .get('v1/new_club_applications', { authToken })
      .then(json => {
        let clubApplications = {}
        json.forEach(app => {
          if (app.submitted_at) {
            clubApplications[app.id] = app
          }
        })
        this.setState({
          status: 'success',
          clubApplications
        })
      })
      .catch(e => {
        if (e.status === 401) {
          this.setState({ status: 'needsToAuth' })
        } else {
          this.setState({ status: 'error' })
        }
      })
  }

  updateApplicationList(updatedApplication) {
    const { clubApplications } = this.state
    clubApplications[updatedApplication.id] = updatedApplication

    this.setState({ clubApplications })
  }

  pointOfContact(application) {
    const profile = application.leader_profiles.find(
      profile =>
        profile.user && profile.user.id === application.point_of_contact_id
    )

    return profile && profile.user.email
  }

  render() {
    const {
      authToken,
      status,
      clubApplications,
      selection,
      selectType
    } = this.state
    switch (status) {
      case 'loading':
        return <LoadingAnimation />
      case 'needsToAuth':
        return <Login userType="admin" />
      case 'success':
        return (
          <Fragment>
            <Flex justify="flex-end">
              <LogoutButton m={2} inverted={false} />
            </Flex>
            <Flex>
              <Box>
                <Table
                  headers={['ID', 'Name', 'POC', 'Interview', 'Notes']}
                  rows={Object.values(clubApplications).map(application => ({
                    ID: application.id,
                    Name: application.high_school_name,
                    POC: this.pointOfContact(application),
                    Interview: (
                      <Button
                        bg="info"
                        inverted={
                          !(
                            selection &&
                            selection.id !== application.id &&
                            selectType === 'Interview'
                          )
                        }
                        disabled={!application.submitted_at}
                        onClick={() => {
                          this.setState({
                            selection: application,
                            selectType: 'interview'
                          })
                        }}
                        children="✍"
                      />
                    ),
                    Notes: (
                      <Button
                        bg="info"
                        inverted={
                          !(
                            selection &&
                            selection.id !== application.id &&
                            selectType === 'Notes'
                          )
                        }
                        disabled={!application.submitted_at}
                        onClick={() => {
                          this.setState({
                            selection: application,
                            selectType: 'notes'
                          })
                        }}
                        children="✍"
                      />
                    )
                  }))}
                />
              </Box>
              {selection && (
                <Box>
                  {selectType === 'notes' ? (
                    <Notes authToken={authToken} application={selection} />
                  ) : null}
                  {selectType === 'interview' ? (
                    <Interviews
                      authToken={authToken}
                      application={selection}
                      updateApplicationList={this.updateApplicationList}
                    />
                  ) : null}
                </Box>
              )}
            </Flex>
          </Fragment>
        )
      default:
        return (
          <Text color="error" py={3} align="center">
            🚨 Something terrible has happened 🚨
          </Text>
        )
    }
  }
}
<commit_msg>Delete old dashboard page<commit_after><|endoftext|>


<commit_before>/**
 * @author Dennis Haulund Nielsen
 */

'use strict';

var fs				= require('fs');
var path			= require('path');
var yeoman		 	= require('yeoman-generator');
var util			= require('util');
var chalk			= require('chalk');
var _string			= require('underscore.string');
var nodesGenerator 	= require('../../script-base.js');

module.exports = nodesGenerator.extend({

	constructor: function() {

		nodesGenerator.apply(this, arguments);

	},

	prompting: {

		promptForLocation: function() {

			if(this.options.destinationPath) {
				return;
			}

			var done = this.async();

			var prompt = {
				type: 'input',
				name: 'location',
				message: chalk.green('Which folder should i create the module in? (remember to inlcude the "type" folder, ie. common)'),
				default: this.name
			};

			this.prompt(prompt, function(answer) {
				this.moduleLocation = answer.location;
				done();
			}.bind(this));

		}

	},

	writing: {

		module: function() {

			var fileName = this.name + '.module.js';
			var destinationPath = this.options.destinationPath ?
				this.options.destinationPath :
				this.moduleLocation;

			this.destinationFullPath = path.join(destinationPath, fileName);

			this.scriptTemplate(
				this.templatePath('javascript/module.js'),
				this.destinationPath(path.join('app', destinationPath, fileName)),
				{
					name: this.cameledName
				}
			);

			this.scriptToIndex(this.destinationFullPath);

		}

	},

	end: function() {

		this.dependencyToApp(this.name);

	}

});<commit_msg>Dependencies added to app.js will now use its cameledName<commit_after>/**
 * @author Dennis Haulund Nielsen
 */

'use strict';

var fs				= require('fs');
var path			= require('path');
var yeoman		 	= require('yeoman-generator');
var util			= require('util');
var chalk			= require('chalk');
var _string			= require('underscore.string');
var nodesGenerator 	= require('../../script-base.js');

module.exports = nodesGenerator.extend({

	constructor: function() {

		nodesGenerator.apply(this, arguments);

	},

	prompting: {

		promptForLocation: function() {

			if(this.options.destinationPath) {
				return;
			}

			var done = this.async();

			var prompt = {
				type: 'input',
				name: 'location',
				message: chalk.green('Which folder should i create the module in? (remember to inlcude the "type" folder, ie. common)'),
				default: this.name
			};

			this.prompt(prompt, function(answer) {
				this.moduleLocation = answer.location;
				done();
			}.bind(this));

		}

	},

	writing: {

		module: function() {

			var fileName = this.name + '.module.js';
			var destinationPath = this.options.destinationPath ?
				this.options.destinationPath :
				this.moduleLocation;

			this.destinationFullPath = path.join(destinationPath, fileName);

			this.scriptTemplate(
				this.templatePath('javascript/module.js'),
				this.destinationPath(path.join('app', destinationPath, fileName)),
				{
					name: this.cameledName
				}
			);

			this.scriptToIndex(this.destinationFullPath);

		}

	},

	end: function() {

		this.dependencyToApp(this.cameledName);

	}

});<|endoftext|>


<commit_before>/*global angular*/

(function() {
    'use strict';

    angular
        .module('angular-click-outside', [])
        .directive('clickOutside', ['$document', '$parse', clickOutside]);

    function clickOutside($document, $parse) {
        return {
            restrict: 'A',
            link: function($scope, elem, attr) {
                var classList = (attr.outsideIfNot !== undefined) ? attr.outsideIfNot.replace(', ', ',').split(',') : [],
                    fn,
                    event;

                // add the elements id so it is not counted in the click listening
                if (attr.id !== undefined) {
                    classList.push(attr.id);
                }

                function eventHandler(e) {

                    // check if our element already hidden and abort if so
                    if (angular.element(elem).hasClass("ng-hide")) {
                        return;
                    }

                    var i = 0,
                        element;

                    // if there is no click target, no point going on
                    if (!e || !e.target) {
                        return;
                    }

                    // loop through the available elements, looking for classes in the class list that might match and so will eat
                    for (element = e.target; element; element = element.parentNode) {
                        var id = element.id,
                            classNames = element.className,
                            l = classList.length;

                        // Unwrap SVGAnimatedString
                        if (classNames && classNames.baseVal !== undefined) {
                            classNames = classNames.baseVal;
                        }

                        // loop through the elements id's and classnames looking for exceptions
                        for (i = 0; i < l; i++) {
                            // check for id's or classes, but only if they exist in the first place
                            if ((id !== undefined && id === classList[i]) || (classNames && classNames === classList[i])) {
                                // now let's exit out as it is an element that has been defined as being ignored for clicking outside
                                return;
                            }
                        }
                    }

                    // if we have got this far, then we are good to go with processing the command passed in via the click-outside attribute
                    return $scope.$apply(function() {
                        fn = $parse(attr['clickOutside']);
                        return fn($scope);
                    });
                }

                // detect if touch device and listen to touchstart instead of click
                event = (_hasTouch()) ? "touchstart" : "click";

                // listen for click or touchstart depending on the device and handle the event
                $document.on(event, eventHandler);

                // when the scope is destroyed, clean up the documents click handler as we don't want it hanging around
                $scope.$on('$destroy', function() {
                    $document.off('click', eventHandler);
                });

                // private function to attempt to figure out if we are on a touch device
                function _hasTouch() {
                    // works on most browsers, IE10/11 and Surface
                    return 'ontouchstart' in window || navigator.maxTouchPoints;
                };
            }
        };
    }
})();<commit_msg>Fixed event deregister function<commit_after>/*global angular*/

(function() {
    'use strict';

    angular
        .module('angular-click-outside', [])
        .directive('clickOutside', ['$document', '$parse', clickOutside]);

    function clickOutside($document, $parse) {
        return {
            restrict: 'A',
            link: function($scope, elem, attr) {
                var classList = (attr.outsideIfNot !== undefined) ? attr.outsideIfNot.replace(', ', ',').split(',') : [],
                    fn,
                    event;

                // add the elements id so it is not counted in the click listening
                if (attr.id !== undefined) {
                    classList.push(attr.id);
                }

                function eventHandler(e) {

                    // check if our element already hidden and abort if so
                    if (angular.element(elem).hasClass("ng-hide")) {
                        return;
                    }

                    console.log('passed the hide test');

                    var i = 0,
                        element;

                    // if there is no click target, no point going on
                    if (!e || !e.target) {
                        return;
                    }

                    console.log('passed the event check');

                    // loop through the available elements, looking for classes in the class list that might match and so will eat
                    for (element = e.target; element; element = element.parentNode) {
                        var id = element.id,
                            classNames = element.className,
                            l = classList.length;

                        // Unwrap SVGAnimatedString classes
                        if (classNames && classNames.baseVal !== undefined) {
                            classNames = classNames.baseVal;
                        }

                        console.log('about to check');

                        // loop through the elements id's and classnames looking for exceptions
                        for (i = 0; i < l; i++) {
                            // check for id's or classes, but only if they exist in the first place
                            if ((id !== undefined && id === classList[i]) || (classNames && classNames === classList[i])) {
                                // now let's exit out as it is an element that has been defined as being ignored for clicking outside
                                return;
                            }
                        }
                    }

                    // if we have got this far, then we are good to go with processing the command passed in via the click-outside attribute
                    return $scope.$apply(function() {
                        fn = $parse(attr['clickOutside']);
                        return fn($scope);
                    });
                }

                // detect if touch device and listen to touchstart instead of click
                event = (_hasTouch()) ? "touchstart" : "click";

                // listen for click or touchstart depending on the device and handle the event
                $document.on(event, eventHandler);

                // when the scope is destroyed, clean up the documents click handler as we don't want it hanging around
                $scope.$on('$destroy', function() {
                    $document.off(event, eventHandler);
                });

                // private function to attempt to figure out if we are on a touch device
                function _hasTouch() {
                    // works on most browsers, IE10/11 and Surface
                    return 'ontouchstart' in window || navigator.maxTouchPoints;
                };
            }
        };
    }
})();<|endoftext|>


<commit_before>import {UICorePlugin, Events, Styler, template} from 'Clappr'

import pluginStyle from './public/context_menu.scss'
import templateHtml from './public/context_menu.html'

export default class ContextMenuPlugin extends UICorePlugin {
  get name() { return 'context_menu' }
  get attributes() { return { 'class': 'context-menu' } }
  get mediaControl() { return this.core.mediaControl }
  get template() { return template(templateHtml) }

  get exposeVersion() {
    return {
      label: `Clappr Player v${Clappr.version}`,
      name: 'version'
    }
  }

  get copyURL() {
    return {
      label: 'Copy URL',
      name: 'copyURL'
    }
  }

  get copyURLCurrentTime() {
    return {
      label: 'Copy URL on current time',
      name: 'copyURLCurrentTime'
    }
  }

  get loop() {
    return {
      label: 'Loop: ',
      name: 'loop',
      class: this.core.options.loop ? 'on' : 'off'
    }
  }

  get loopEnable() {
    return this.core.getCurrentPlayback().el.loop
  }

  get events() {
    return {
      'click [data-copyURL]': 'onCopyURL',
      'click [data-copyURLCurrentTime]': 'onCopyURLCurrentTime',
      'click [data-loop]': 'onToggleLoop'
    }
  }

  constructor(core) {
    super(core)
    this.bindEvents()
  }

  bindEvents() {
    if (this.mediaControl) {
      this.listenTo(this.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.containerChanged)

      if (this.container) {
        this.listenTo(this.container, Events.CONTAINER_CONTEXTMENU, this.toggleContextMenu)
        this.listenTo(this.container, Events.CONTAINER_CLICK, this.hide)
      }
    }

    $('body').on('click', this.hide.bind(this))
  }

  destroy() {
    $('body').off('click', this.hide.bind(this))
    this.stopListening()
    super.destroy()
  }

  containerChanged() {
    this.container = this.core.getCurrentContainer()
    this.stopListening()
    this.bindEvents()
  }

  toggleContextMenu(event) {
    event.preventDefault()
    this.show(event.offsetY, event.offsetX)
  }

  show(top, left) {
    this.hide()
    this.$el.css({top, left})
    this.$el.show()
  }

  hide() {
    this.$el.hide()
  }

  copyToClipboard(value, $el) {
    if (!$el) return

    let $copyTextarea = $('<textarea class="copytextarea"/>')
    $copyTextarea.text(value)
    $el.append($copyTextarea)

    let copyTextarea = document.querySelector('.copytextarea');
    copyTextarea.select();

    try {
      document.execCommand('copy');
    } catch (err) {
      throw Error(err)
    }

    $copyTextarea.remove()
  }

  onCopyURL() {
    this.copyToClipboard(window.location.href, this.$el)
  }

  onCopyURLCurrentTime() {
    let url = window.location.href
    const current_time = Math.floor(this.container.getCurrentTime())
    if (window.location.search == '') {
      url += `?s=${current_time}`
    } else {
      if (window.location.search.split(/[\?=&]/g).indexOf('s') == -1) {
        url += `&s=${current_time}`
      } else {
        let search = window.location.search.split(/s=\d+&*/g)[1]
        if (search == '') {
          url = `${window.location.href.replace(window.location.search, '')}${search}?s=${current_time}`
        } else {
          search = window.location.search.split(/s=\d+&*/g).join('')
          url = `${window.location.href.replace(window.location.search, '')}${search}&s=${current_time}`
        }
      }
    }

    this.copyToClipboard(url, this.$el)
  }

  onToggleLoop() {
    this.core.options.loop = !this.loopEnable
    this.core.getCurrentPlayback().el.loop = !this.loopEnable
    this.$el.find('[data-loop]').toggleClass('on', this.loopEnable)
    this.$el.find('[data-loop]').toggleClass('off', !this.loopEnable)
  }

  render() {
    this.menuOptions = [this.copyURL, this.copyURLCurrentTime, this.loop, this.exposeVersion]
    this.$el.html(this.template({options: this.menuOptions}))
    this.$el.append(Styler.getStyleFor(pluginStyle))
    this.core.$el.append(this.$el)
    this.hide()
    this.disable()
    return this
  }
}
<commit_msg>fix(src): use correct element reference<commit_after>import {UICorePlugin, Events, Styler, template} from 'Clappr'

import pluginStyle from './public/context_menu.scss'
import templateHtml from './public/context_menu.html'

export default class ContextMenuPlugin extends UICorePlugin {
  get name() { return 'context_menu' }
  get attributes() { return { 'class': 'context-menu' } }
  get mediaControl() { return this.core.mediaControl }
  get template() { return template(templateHtml) }

  get exposeVersion() {
    return {
      label: `Clappr Player v${Clappr.version}`,
      name: 'version'
    }
  }

  get copyURL() {
    return {
      label: 'Copy URL',
      name: 'copyURL'
    }
  }

  get copyURLCurrentTime() {
    return {
      label: 'Copy URL on current time',
      name: 'copyURLCurrentTime'
    }
  }

  get loop() {
    return {
      label: 'Loop: ',
      name: 'loop',
      class: this.core.options.loop ? 'on' : 'off'
    }
  }

  get loopEnable() {
    return this.core.getCurrentPlayback().el.loop
  }

  get events() {
    return {
      'click [data-copyURL]': 'onCopyURL',
      'click [data-copyURLCurrentTime]': 'onCopyURLCurrentTime',
      'click [data-loop]': 'onToggleLoop'
    }
  }

  constructor(core) {
    super(core)
    this.bindEvents()
  }

  bindEvents() {
    if (this.mediaControl) {
      this.listenTo(this.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.containerChanged)

      if (this.container) {
        this.listenTo(this.container, Events.CONTAINER_CONTEXTMENU, this.toggleContextMenu)
        this.listenTo(this.container, Events.CONTAINER_CLICK, this.hide)
      }
    }

    $('body').on('click', this.hide.bind(this))
  }

  destroy() {
    $('body').off('click', this.hide.bind(this))
    this.stopListening()
    super.destroy()
  }

  containerChanged() {
    this.container = this.core.getCurrentContainer()
    this.stopListening()
    this.bindEvents()
  }

  toggleContextMenu(event) {
    event.preventDefault()
    this.show(event.offsetY, event.offsetX)
  }

  show(top, left) {
    this.hide()
    this.$el.css({top, left})
    this.$el.show()
  }

  hide() {
    this.$el.hide()
  }

  copyToClipboard(value, $el) {
    if (!$el) return

    let $copyTextarea = $('<textarea class="copytextarea"/>')
    $copyTextarea.text(value)
    $el.append($copyTextarea)

    let copyTextarea = document.querySelector('.copytextarea');
    copyTextarea.select();

    try {
      document.execCommand('copy');
    } catch (err) {
      throw Error(err)
    }

    $copyTextarea.remove()
  }

  onCopyURL() {
    this.copyToClipboard(window.location.href, this.$el)
  }

  onCopyURLCurrentTime() {
    let url = window.location.href
    const current_time = Math.floor(this.container.getCurrentTime())
    if (window.location.search == '') {
      url += `?s=${current_time}`
    } else {
      if (window.location.search.split(/[\?=&]/g).indexOf('s') == -1) {
        url += `&s=${current_time}`
      } else {
        let search = window.location.search.split(/s=\d+&*/g)[1]
        if (search == '') {
          url = `${window.location.href.replace(window.location.search, '')}${search}?s=${current_time}`
        } else {
          search = window.location.search.split(/s=\d+&*/g).join('')
          url = `${window.location.href.replace(window.location.search, '')}${search}&s=${current_time}`
        }
      }
    }

    this.copyToClipboard(url, this.$el)
  }

  onToggleLoop() {
    this.core.options.loop = !this.loopEnable
    this.core.getCurrentPlayback().el.loop = !this.loopEnable
    this.$el.find('[data-loop]').toggleClass('on', this.loopEnable)
    this.$el.find('[data-loop]').toggleClass('off', !this.loopEnable)
  }

  render() {
    this.menuOptions = [this.copyURL, this.copyURLCurrentTime, this.loop, this.exposeVersion]
    this.$el.html(this.template({options: this.menuOptions}))
    this.$el.append(Styler.getStyleFor(pluginStyle))
    this.core.$el[0].append(this.$el[0])
    this.hide()
    this.disable()
    return this
  }
}
<|endoftext|>


<commit_before>'use strict';

var mongoose = require('mongoose');

mongoose.connect((process.env.MONGOLAB_URI || 'mongodb://localhost/mean-todo'), function(err) {
  if (err) {
    console.log('Failed connecting to MongoDB!');
  } else {
    console.log('Successfully connected to MongoDB!');
  }
});
<commit_msg>updated database file<commit_after>'use strict';

var mongoose = require('mongoose');

mongoose.connect(process.env.MONGOLAB_URI, function(err) {
  if (err) {
    console.log('Failed connecting to MongoDB!');
  } else {
    console.log('Successfully connected to MongoDB!');
  }
});
<|endoftext|>


<commit_before>/**<#-- This javascript source is a Freemarker template and is supposed to be included. -->*/
(function() {
    var userEventsSearch = document.getElementById('userEventsSearch');
    var userEventView = document.getElementById('userEventView');
    var nextDataEventId;
    var prevDataEventId;

    function $eventId(element) {
        return element.length && element.attr('data-event-id');
    }

    function displayUserEvent(eventId) {
        var dataElement = eventId && $('div[data-event-id=' + eventId + ']');
        if (!dataElement || !dataElement.length) {
            return;
        }

        /** <!#-- Handle buttons visibility. --> */
        nextDataEventId = $eventId(dataElement.next());
        prevDataEventId = $eventId(dataElement.prev());

        $('#buttonNextEvent')[0].style.visibility = nextDataEventId ? '' : 'hidden';
        $('#buttonPrevEvent')[0].style.visibility = prevDataEventId ? '' : 'hidden';

        /** <!#-- Populate the data. --> */
        var userTargetId = dataElement.attr('data-event-target-id');
        var userData = userTargetId + ' / ' + dataElement.attr('data-event-target-username');
        $('#userEventTarget').text(userTargetId ? userData : '');

        $('#userEventId').text(eventId);
        $('#userEventType').text(dataElement.attr('data-event-type'));
        $('#userEventUrl').text(dataElement.attr('data-event-url'));
        $('#userEventIP').text(dataElement.attr('data-event-ip'));
        $('#userEventTime').text(dataElement.attr('data-event-time'));

        var dataJson = JSON.parse($(dataElement).html());
        var details = dataJson['event.data'];
        delete dataJson['event.data'];
        $('#userEventData').text(JSON.stringify(dataJson));
        $('#userEventDataDetails').text(JSON.stringify(details));

        $(userEventsSearch).hide();
        $(userEventView).show();
    }

    $('#buttonBackToEvents').on('click touchend', function(e) {
        $(userEventsSearch).show();
        $(userEventView).hide();
        return false;
    });

    $('#buttonPrevEvent').on('click touchend', function(e) {
        if (prevDataEventId) displayUserEvent(prevDataEventId);
        return false;
    });

    $('#buttonNextEvent').on('click touchend', function(e) {
         if (nextDataEventId) displayUserEvent(nextDataEventId);
         return false;
    });

    /** <#-- Table row handler. --> */
    $('#' + userEventsSearch.id + ' table tr .btn-event-view').on('click touchend', function(e) {
        e.preventDefault();
        e.stopPropagation();
        var eventId = $eventId($(e.target).closest('tr'));
        displayUserEvent(eventId);
        return false;
    });

 })();<commit_msg>Nicer json for events<commit_after>/**<#-- This javascript source is a Freemarker template and is supposed to be included. -->*/
(function() {
    var userEventsSearch = document.getElementById('userEventsSearch');
    var userEventView = document.getElementById('userEventView');
    var nextDataEventId;
    var prevDataEventId;

    function $eventId(element) {
        return element.length && element.attr('data-event-id');
    }

    function displayUserEvent(eventId) {
        var dataElement = eventId && $('div[data-event-id=' + eventId + ']');
        if (!dataElement || !dataElement.length) {
            return;
        }

        /** <!#-- Handle buttons visibility. --> */
        nextDataEventId = $eventId(dataElement.next());
        prevDataEventId = $eventId(dataElement.prev());

        $('#buttonNextEvent')[0].style.visibility = nextDataEventId ? '' : 'hidden';
        $('#buttonPrevEvent')[0].style.visibility = prevDataEventId ? '' : 'hidden';

        /** <!#-- Populate the data. --> */
        var userTargetId = dataElement.attr('data-event-target-id');
        var userData = userTargetId + ' / ' + dataElement.attr('data-event-target-username');
        $('#userEventTarget').text(userTargetId ? userData : '');

        $('#userEventId').text(eventId);
        $('#userEventType').text(dataElement.attr('data-event-type'));
        $('#userEventUrl').text(dataElement.attr('data-event-url'));
        $('#userEventIP').text(dataElement.attr('data-event-ip'));
        $('#userEventTime').text(dataElement.attr('data-event-time'));

        var dataJson = JSON.parse($(dataElement).html());
        var details = dataJson['event.data'];
        delete dataJson['event.data'];
        $('#userEventData').text(JSON.stringify(dataJson, null, '  '));
        $('#userEventDataDetails').text(JSON.stringify(details, null, '  '));

        $(userEventsSearch).hide();
        $(userEventView).show();
    }

    $('#buttonBackToEvents').on('click touchend', function(e) {
        $(userEventsSearch).show();
        $(userEventView).hide();
        return false;
    });

    $('#buttonPrevEvent').on('click touchend', function(e) {
        if (prevDataEventId) displayUserEvent(prevDataEventId);
        return false;
    });

    $('#buttonNextEvent').on('click touchend', function(e) {
         if (nextDataEventId) displayUserEvent(nextDataEventId);
         return false;
    });

    /** <#-- Table row handler. --> */
    $('#' + userEventsSearch.id + ' table tr .btn-event-view').on('click touchend', function(e) {
        e.preventDefault();
        e.stopPropagation();
        var eventId = $eventId($(e.target).closest('tr'));
        displayUserEvent(eventId);
        return false;
    });

 })();<|endoftext|>


<commit_before>// TODO I suppose my attitude is to use an event emitter very sparingly, because
// I want to use one to emit Kibitzer events, the paxos log, but I don't want to
// create an interface that looks like one of the EventEmitter heavy interfaces.
//
// One of the challenges here is that events are going to flow immediately,
// unless I do something compliacated like only emit events when there is a
// listener. Having looked at that, it is not actually that complicated.
//
// Now we can fuss about some terminate logic.
//
// Put it out again, I prefer to program with error-first callbacks, but events
// happen. They are generally a way in which information enters the system. They
// are synchronous. Here is more information. Generally, they shouldn't block.
//
// That's how I use EventEmitters, but I'm not in for a penny, in for a pound.
// If there is a source of events, I treat that as a stream of events. Not in
// the Node.js streams sense, but I create an event emitter that emits a
// homogenous series of events terminated by a specific termination event.
//
// Then I stop. I don't go on to implement multi-interfaces that could take an
// error-first callback, or maybe register an event handler, or maybe use some
// other form of asynchronous notification.
//
// TODO Which is why I've made the event emitter in this class a separate
// object. Which is such a good idea, I belive I'll go and do it Happenstance.
var assert = require('assert')
var events = require('events')

var slice = [].slice

var Vestibule = require('vestibule')
var cadence = require('cadence')

var Reactor = require('reactor')
var Legislator = require('paxos/legislator')
var Islander = require('islander')

var Scheduler = require('happenstance')

var interrupt = require('interrupt').createInterrupter('bigeasy.kibitz')
var abend = require('abend')

var logger = require('prolific').createLogger('bigasy.kibitz.kibitzer')

function Kibitzer (islandId, id, options) {
    assert(id != null, 'id is required')

    options || (options = {})

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)

    this._pulser = new Reactor({ object: this, method: '_pulse' })
    this._publisher = new Reactor({ object: this, method: '_publish' })

    this._Date = options.Date || Date
    this.scheduler = new Scheduler({ Date: options.Date || Date })

    this.location = options.location
    this._ua = options.ua
    this.id = id

    this.legislator = new Legislator(islandId, id, this._Date.now(), {
        ping: options.ping,
        timeout: options.timeout,
        scheduler: { Date: options.Date || Date }
    })
// TODO Scheduler is not shutting down, so maybe `unref` for now.
    this.legislator.scheduler.on('timeout', this._send.bind(this))

    this.islander = new Islander(this.legislator.id)

    this.iterators = {
        legislator: this.legislator.log.min(),
        islander: { dummy: true }
    }

    // TODO Very much crufty.
    this.log = new events.EventEmitter
    this.log.on('newListener', this._newLogListener.bind(this))

    this._advanced = new Vestibule
    this._terminated = false
}

Kibitzer.prototype.terminate = function () {
    if (!this._terminated) {
        this._terminated = true
        this.scheduler.shutdown()
        this.legislator.scheduler.shutdown()
        this._advanced.notify()
    }
}

Kibitzer.prototype._newLogListener = function () {
    setImmediate(this._advanced.notify.bind(this._advanced))
}

Kibitzer.prototype._logger = function (level, message, context) {
    logger[level](message, context)
}

Kibitzer.prototype._publish = cadence(function (async) {
    var outgoing = this.islander.outbox()
    if (outgoing.length == 0) {
        return
    }
    var post
    async(function () {
        var location = this.legislator.locations[this.legislator.government.majority[0]]
        this._ua.send(location, post = {
            islandId: this.legislator.islandId,
            type: 'enqueue',
            entries: outgoing
        }, async())
    }, function (body) {
        this._logger('info', 'enqueued', {
            kibitzerId: this.legislator.id,
            sent: post,
            received: body
        })
        if (body == null) {
            body = { entries: [] }
        }
        if (body) {
            this.islander.published(body.entries)
        }
        var delay = this._Date.now() + (body.entries == 0 ? 1000 : 0)
        this.scheduler.schedule(delay, 'publish', { object: this, method: '_checkPublisher' })
    })
})

// TODO Wierd but scheduler calls us with a timing, so that is interpreted as
// the callback to `check`. Could spend all day trying to decide if that's a
// correct use of `Operation`.
Kibitzer.prototype._checkPublisher = function () {
    this._publisher.check()
}

// TODO Annoying how difficult it is to stop this crazy thing. There are going
// to be race conditions where we have a termination, come in, we shut things
// down, but then we continue with processing a pulse which triggers a timer.
// Sending messages to legislator can restart it's scheduler.
//
// TODO We could kill the timer in the scheduler, set the boolean we added to
// tell it to no longer schedule.
Kibitzer.prototype._pulse = cadence(function (async, timeout, pulse) {
    var responses = {}
    async(function () {
        async.forEach(function (id) {
            async(function () {
                if (id == this.legislator.id) {
                    this._receive(pulse, async())
                } else {
                    var location = this.legislator.locations[id]
                    this._ua.send(location, { type: 'receive', pulse: pulse }, async())
                }
            }, function (response) {
                this._logger('info', 'published', {
                    kibitzerId: this.legislator.id,
                    sent: pulse,
                    received: response
                })
                responses[id] = response
            })
        })(pulse.route)
    }, function () {
        this.legislator.sent(this._Date.now(), pulse, responses)
    }, function () {
        if (this.iterators.legislator.next != null) {
            this._advanced.notify()
        }
    }, function () {
        this._send()
    })
})

Kibitzer.prototype._send = function () {
    var now = this._Date.now()
    for (;;) {
        var outbox = this.legislator.synchronize(now)
        if (outbox.length == 0) {
            var consensus = this.legislator.consensus(now)
            if (consensus != null) {
                outbox.push(consensus)
            }
        }
        if (outbox.length == 0) {
            break
        }
        outbox.forEach(function (pulse) {
            this._pulser.push(pulse)
        }, this)
    }
}

Kibitzer.prototype.locations = function () {
    var locations = []
    for (var key in this.legislator.locations) {
        locations.push(this.legislator.locations[key])
    }
    return locations
}

Kibitzer.prototype._prime = cadence(function (async) {
    async(function () {
        var loop = async(function () {
            async(function () {
                this._advanced.enter(async())
            }, function () {
                if (this.iterators.legislator.next != null) {
                    this.iterators.legislator = this.iterators.legislator.next
                    this.iterators.islander = {
                        next: this.islander.prime(this.iterators.legislator)
                    }
                    return [ loop.break ]
                }
            })
        })()
    }, function () {
        this._advance(abend)
    })
})

Kibitzer.prototype.bootstrap = cadence(function (async) {
    this._logger('info', 'bootstrap', { kibitzerId: this.legislator.id })
    this._prime(async())
    this.legislator.bootstrap(this._Date.now(), this.location)
    this._send()
})

// TODO Use Isochronous to repeatedly send join message.
Kibitzer.prototype.join = cadence(function (async, location) {
    this._prime(async())
    this.scheduler.schedule(this._Date.now() + 0, 'join', { object: this, method: '_checkJoin' }, location)
})

Kibitzer.prototype._checkJoin = function (when, location) {
    this._join(location, abend)
}

Kibitzer.prototype._join = cadence(function (async, location) {
    async(function () {
        this._logger('info', 'join', {
            kibitzerId: this.legislator.id,
            received: JSON.stringify(location)
        })
        this._ua.send(location, {
            type: 'naturalize',
            islandId: this.legislator.islandId,
            id: this.legislator.id,
            cookie: this.legislator.cookie,
            location: this.location,
            hops: 0
        }, async())
    }, function (response) {
        if (response == null || !response.enqueued) {
            var delay = this._Date.now() + 1000
            this.scheduler.schedule(delay, 'join', { object: this, method: '_checkJoin' }, location)
        }
    })
})

Kibitzer.prototype.dispatch = cadence(function (async, body) {
    switch (body.type) {
    case 'naturalize':
        this._naturalize(body, async())
        break
    case 'receive':
        this._receive(body.pulse, async())
        break
    case 'enqueue':
        this._enqueue(body, async())
        break
    }
})

Kibitzer.prototype.publish = function (entry) {
    var cookie = this.islander.publish(entry, false)
    if (this.scheduler.scheduled('publish') == null) {
        this.scheduler.schedule(0, 'publish', { object: this, method: '_checkPublisher' })
    }
    return cookie
}

Kibitzer.prototype._naturalize = cadence(function (async, post) {
    assert(post.hops != null)
    var outcome = this.legislator.naturalize(this._Date.now(), post.islandId, post.id, post.cookie, post.location)
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        outcome: JSON.stringify(outcome)
    })
    if (!outcome.enqueued && outcome.leader != null && post.hops == 0) {
        var location = this.legislator.locations[this.legislator.government.majority[0]]
        post.hops++
        this._ua.send(location, post, async())
    } else {
        this._send()
        return [ outcome ]
    }
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    var response = { posted: false, entries: [] }
// TODO Redirect enqueue or wait for stability and retry.
    for (var i = 0, I = post.entries.length; i < I; i++) {
        var entry = post.entries[i]
        var outcome = this.legislator.enqueue(this._Date.now(), post.islandId, entry)
        if (!outcome.enqueued) {
            response.entries.length = 0
            break
        }
        response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
    }
    this._send()
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        sent: JSON.stringify(response)
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, pulse) {
    this._logger('info', 'receive', {
        kibitzerId: this.legislator.id,
        received: pulse
    })
    var ret = [ this.legislator.receive(this._Date.now(), pulse, pulse.messages) ]
    if (this.iterators.legislator.next != null) {
        this._advanced.notify()
    }
    this._send()
    return ret
})

Kibitzer.prototype._advance = cadence(function (async) {
    var loop = async(function () {
        if (this._terminated) {
            this.log.emit('terminated')
            return [ loop.break ]
        }
        while (this.iterators.legislator.next != null) {
            this.iterators.legislator = this.iterators.legislator.next
            this._logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                route: this.iterators.legislator.route,
                promise: this.iterators.legislator.promise,
                value: this.iterators.legislator.value
            })
        }
        if (this.log.listenerCount('entry') == 0 || this.iterators.islander.next == null) {
            this._advanced.enter(async())
        } else {
            this.log.emit('entry', this.iterators.islander = this.iterators.islander.next)
        }
    })()
})

module.exports = Kibitzer
<commit_msg>Tidy emitted message.<commit_after>// TODO I suppose my attitude is to use an event emitter very sparingly, because
// I want to use one to emit Kibitzer events, the paxos log, but I don't want to
// create an interface that looks like one of the EventEmitter heavy interfaces.
//
// One of the challenges here is that events are going to flow immediately,
// unless I do something compliacated like only emit events when there is a
// listener. Having looked at that, it is not actually that complicated.
//
// Now we can fuss about some terminate logic.
//
// Put it out again, I prefer to program with error-first callbacks, but events
// happen. They are generally a way in which information enters the system. They
// are synchronous. Here is more information. Generally, they shouldn't block.
//
// That's how I use EventEmitters, but I'm not in for a penny, in for a pound.
// If there is a source of events, I treat that as a stream of events. Not in
// the Node.js streams sense, but I create an event emitter that emits a
// homogenous series of events terminated by a specific termination event.
//
// Then I stop. I don't go on to implement multi-interfaces that could take an
// error-first callback, or maybe register an event handler, or maybe use some
// other form of asynchronous notification.
//
// TODO Which is why I've made the event emitter in this class a separate
// object. Which is such a good idea, I belive I'll go and do it Happenstance.
var assert = require('assert')
var events = require('events')

var slice = [].slice

var Vestibule = require('vestibule')
var cadence = require('cadence')

var Reactor = require('reactor')
var Legislator = require('paxos/legislator')
var Islander = require('islander')

var Scheduler = require('happenstance')

var interrupt = require('interrupt').createInterrupter('bigeasy.kibitz')
var abend = require('abend')

var logger = require('prolific').createLogger('bigasy.kibitz.kibitzer')

function Kibitzer (islandId, id, options) {
    assert(id != null, 'id is required')

    options || (options = {})

    options.ping || (options.ping = 250)
    options.timeout || (options.timeout = 1000)

    this._pulser = new Reactor({ object: this, method: '_pulse' })
    this._publisher = new Reactor({ object: this, method: '_publish' })

    this._Date = options.Date || Date
    this.scheduler = new Scheduler({ Date: options.Date || Date })

    this.location = options.location
    this._ua = options.ua
    this.id = id

    this.legislator = new Legislator(islandId, id, this._Date.now(), {
        ping: options.ping,
        timeout: options.timeout,
        scheduler: { Date: options.Date || Date }
    })
// TODO Scheduler is not shutting down, so maybe `unref` for now.
    this.legislator.scheduler.on('timeout', this._send.bind(this))

    this.islander = new Islander(this.legislator.id)

    this.iterators = {
        legislator: this.legislator.log.min(),
        islander: { dummy: true }
    }

    // TODO Very much crufty.
    this.log = new events.EventEmitter
    this.log.on('newListener', this._newLogListener.bind(this))

    this._advanced = new Vestibule
    this._terminated = false
}

Kibitzer.prototype.terminate = function () {
    if (!this._terminated) {
        this._terminated = true
        this.scheduler.shutdown()
        this.legislator.scheduler.shutdown()
        this._advanced.notify()
    }
}

Kibitzer.prototype._newLogListener = function () {
    setImmediate(this._advanced.notify.bind(this._advanced))
}

Kibitzer.prototype._logger = function (level, message, context) {
    logger[level](message, context)
}

Kibitzer.prototype._publish = cadence(function (async) {
    var outgoing = this.islander.outbox()
    if (outgoing.length == 0) {
        return
    }
    var post
    async(function () {
        var location = this.legislator.locations[this.legislator.government.majority[0]]
        this._ua.send(location, post = {
            islandId: this.legislator.islandId,
            type: 'enqueue',
            entries: outgoing
        }, async())
    }, function (body) {
        this._logger('info', 'enqueued', {
            kibitzerId: this.legislator.id,
            sent: post,
            received: body
        })
        if (body == null) {
            body = { entries: [] }
        }
        if (body) {
            this.islander.published(body.entries)
        }
        var delay = this._Date.now() + (body.entries == 0 ? 1000 : 0)
        this.scheduler.schedule(delay, 'publish', { object: this, method: '_checkPublisher' })
    })
})

// TODO Wierd but scheduler calls us with a timing, so that is interpreted as
// the callback to `check`. Could spend all day trying to decide if that's a
// correct use of `Operation`.
Kibitzer.prototype._checkPublisher = function () {
    this._publisher.check()
}

// TODO Annoying how difficult it is to stop this crazy thing. There are going
// to be race conditions where we have a termination, come in, we shut things
// down, but then we continue with processing a pulse which triggers a timer.
// Sending messages to legislator can restart it's scheduler.
//
// TODO We could kill the timer in the scheduler, set the boolean we added to
// tell it to no longer schedule.
Kibitzer.prototype._pulse = cadence(function (async, timeout, pulse) {
    var responses = {}
    async(function () {
        async.forEach(function (id) {
            async(function () {
                if (id == this.legislator.id) {
                    this._receive(pulse, async())
                } else {
                    var location = this.legislator.locations[id]
                    this._ua.send(location, { type: 'receive', pulse: pulse }, async())
                }
            }, function (response) {
                this._logger('info', 'published', {
                    kibitzerId: this.legislator.id,
                    sent: pulse,
                    received: response
                })
                responses[id] = response
            })
        })(pulse.route)
    }, function () {
        this.legislator.sent(this._Date.now(), pulse, responses)
    }, function () {
        if (this.iterators.legislator.next != null) {
            this._advanced.notify()
        }
    }, function () {
        this._send()
    })
})

Kibitzer.prototype._send = function () {
    var now = this._Date.now()
    for (;;) {
        var outbox = this.legislator.synchronize(now)
        if (outbox.length == 0) {
            var consensus = this.legislator.consensus(now)
            if (consensus != null) {
                outbox.push(consensus)
            }
        }
        if (outbox.length == 0) {
            break
        }
        outbox.forEach(function (pulse) {
            this._pulser.push(pulse)
        }, this)
    }
}

Kibitzer.prototype.locations = function () {
    var locations = []
    for (var key in this.legislator.locations) {
        locations.push(this.legislator.locations[key])
    }
    return locations
}

Kibitzer.prototype._prime = cadence(function (async) {
    async(function () {
        var loop = async(function () {
            async(function () {
                this._advanced.enter(async())
            }, function () {
                if (this.iterators.legislator.next != null) {
                    this.iterators.legislator = this.iterators.legislator.next
                    this.iterators.islander = {
                        next: this.islander.prime(this.iterators.legislator)
                    }
                    return [ loop.break ]
                }
            })
        })()
    }, function () {
        this._advance(abend)
    })
})

Kibitzer.prototype.bootstrap = cadence(function (async) {
    this._logger('info', 'bootstrap', { kibitzerId: this.legislator.id })
    this._prime(async())
    this.legislator.bootstrap(this._Date.now(), this.location)
    this._send()
})

// TODO Use Isochronous to repeatedly send join message.
Kibitzer.prototype.join = cadence(function (async, location) {
    this._prime(async())
    this.scheduler.schedule(this._Date.now() + 0, 'join', { object: this, method: '_checkJoin' }, location)
})

Kibitzer.prototype._checkJoin = function (when, location) {
    this._join(location, abend)
}

Kibitzer.prototype._join = cadence(function (async, location) {
    async(function () {
        this._logger('info', 'join', {
            kibitzerId: this.legislator.id,
            received: JSON.stringify(location)
        })
        this._ua.send(location, {
            type: 'naturalize',
            islandId: this.legislator.islandId,
            id: this.legislator.id,
            cookie: this.legislator.cookie,
            location: this.location,
            hops: 0
        }, async())
    }, function (response) {
        if (response == null || !response.enqueued) {
            var delay = this._Date.now() + 1000
            this.scheduler.schedule(delay, 'join', { object: this, method: '_checkJoin' }, location)
        }
    })
})

Kibitzer.prototype.dispatch = cadence(function (async, body) {
    switch (body.type) {
    case 'naturalize':
        this._naturalize(body, async())
        break
    case 'receive':
        this._receive(body.pulse, async())
        break
    case 'enqueue':
        this._enqueue(body, async())
        break
    }
})

Kibitzer.prototype.publish = function (entry) {
    var cookie = this.islander.publish(entry, false)
    if (this.scheduler.scheduled('publish') == null) {
        this.scheduler.schedule(0, 'publish', { object: this, method: '_checkPublisher' })
    }
    return cookie
}

Kibitzer.prototype._naturalize = cadence(function (async, post) {
    assert(post.hops != null)
    var outcome = this.legislator.naturalize(this._Date.now(), post.islandId, post.id, post.cookie, post.location)
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        outcome: JSON.stringify(outcome)
    })
    if (!outcome.enqueued && outcome.leader != null && post.hops == 0) {
        var location = this.legislator.locations[this.legislator.government.majority[0]]
        post.hops++
        this._ua.send(location, post, async())
    } else {
        this._send()
        return [ outcome ]
    }
})

Kibitzer.prototype._enqueue = cadence(function (async, post) {
    var response = { posted: false, entries: [] }
// TODO Redirect enqueue or wait for stability and retry.
    for (var i = 0, I = post.entries.length; i < I; i++) {
        var entry = post.entries[i]
        var outcome = this.legislator.enqueue(this._Date.now(), post.islandId, entry)
        if (!outcome.enqueued) {
            response.entries.length = 0
            break
        }
        response.entries.push({ cookie: entry.cookie, promise: outcome.promise })
    }
    this._send()
    this._logger('info', 'enqueue', {
        kibitzerId: this.legislator.id,
        received: JSON.stringify(post),
        sent: JSON.stringify(response)
    })
    return response
})

Kibitzer.prototype._receive = cadence(function (async, pulse) {
    this._logger('info', 'receive', {
        kibitzerId: this.legislator.id,
        received: pulse
    })
    var ret = [ this.legislator.receive(this._Date.now(), pulse, pulse.messages) ]
    if (this.iterators.legislator.next != null) {
        this._advanced.notify()
    }
    this._send()
    return ret
})

Kibitzer.prototype._advance = cadence(function (async) {
    var loop = async(function () {
        if (this._terminated) {
            this.log.emit('terminated')
            return [ loop.break ]
        }
        while (this.iterators.legislator.next != null) {
            this.iterators.legislator = this.iterators.legislator.next
            this._logger('info', 'consuming', {
                kibitzerId: this.legislator.id,
                route: this.iterators.legislator.route,
                promise: this.iterators.legislator.promise,
                value: this.iterators.legislator.value
            })
        }
        if (this.log.listenerCount('entry') == 0 || this.iterators.islander.next == null) {
            this._advanced.enter(async())
        } else {
            var entry = this.iterators.islander = this.iterators.islander.next
            this.log.emit('entry', { promise: entry.promise, value: entry.value })
        }
    })()
})

module.exports = Kibitzer
<|endoftext|>