<commit_before>/**
 * JavaScript grass animation
 * Requires Fabric.js
 * @author Andrew Richardson <awrichardson6@gmail.com>
 *
 * Usage:
 *   // Attach to <canvas> with id "my_canvas"
 *   var field = new grassyField('my_canvas');
 */
function grassyField(canvasId) {
  var self = this;
  self.element = document.getElementById(canvasId);
  self.canvas = new fabric.Canvas(canvasId);
  self.blades = null;
  self.lastUpdate = new Date().getTime();

  var green = { r: 0, g: 128, b: 0 },
    brown = { r: 222, g: 184, b: 135 };

  /**
   * Resize the canvas
   */
  self.resizeCanvas = function(width, height) {
    var curWidth = self.canvas.getWidth();
    var curHeight = self.canvas.getHeight();

    if (curWidth != width || curHeight != height) {
      self.canvas.clear();
      self.canvas.setWidth(width);
      self.canvas.setHeight(height);
      self._prepareCanvas();
    }
  };

  /**
   * Get a color partially between two other colors
   * c1 - starting color
   * c2 - ending color
   * t - position in the range 0-1
   */
  function colorStep(c1, c2, t) {
    if (t < 0) {
      t = 0;
    } else if (t > 1) {
      t = 1;
    }

    return {
      r: parseInt(c1['r'] + (c2['r'] - c1['r']) * t),
      g: parseInt(c1['g'] + (c2['g'] - c1['g']) * t),
      b: parseInt(c1['b'] + (c2['b'] - c1['b']) * t)
    };
  }

  /**
   * Return a random number within the given range
   */
  function randRange(min, max) {
    var range = max - min;
    return Math.floor((Math.random() * range) + min);
  }

  /**
   * Return x or -x with a 50/50 probability
   */
  function maybeNegative(x) {
    return (Math.random() > .5) ? x : -x;
  }

  self._makeBlade = function(centerX, height) {
    var topY = randRange(0, 15);
    var bottomY = height;
    var middleY = (topY + bottomY) / 2;

    var width = randRange(20, 40);
    var halfWidth = width / 2;
    var curve = maybeNegative(randRange(width / 3, width * 2 / 3));

    var leftX = centerX - halfWidth;
    var leftCurveX = leftX + curve;

    var rightX = leftX + width;
    var rightCurveX = rightX + curve;

    var startX = randRange(leftX, rightX);

    var colorVariance = maybeNegative(Math.random() * .25);
    var color = colorStep(brown, green, window.saturation + colorVariance);

    var path = new fabric.Path('M ' + leftX + ' ' + bottomY + ' ' +
                               'Q ' + leftCurveX + ' ' + middleY + ' ' + startX + ' ' + topY + ' ' +
                               'Q ' + rightCurveX + ' ' + middleY + ' ' + rightX + ' ' + bottomY + ' z');
    path.set({
      fill: 'rgb(' + color['r'] + ',' + color['g'] + ',' + color['b'] + ')',
      selectable: false
    });

    return {
      path: path,
      left: centerX - width / 3,
      right: centerX + width / 3,
      speed: maybeNegative(randRange(1, 5)) / 1000
    };
  };

  self._prepareCanvas = function() {
    var width = self.canvas.getWidth();
    var height = self.canvas.getHeight();
    var numBlades = width / 10;
    self.blades = [];

    for (var i=0; i<numBlades; i++) {
      var blade = self._makeBlade(randRange(0, width), height);
      self.blades.push(blade);
      self.canvas.add(blade.path);
    }
  };

  self._getTipX = function(i) {
    return self.blades[i].path.path[1][3];
  };

  self._setTipX = function(i, x) {
    self.blades[i].path.path[1][3] = x;
  };

  self._updatePaths = function() {
    var now = new Date().getTime();
    var elapsed = now - self.lastUpdate;
    self.lastUpdate = now;

    for (var i=0; i<self.blades.length; i++) {
      var blade = self.blades[i];

      var x = self._getTipX(i) + blade.speed * elapsed;
      if (x >= blade.right) {
        x = blade.right;
        blade.speed *= -1;
      } else if (x <= blade.left) {
        x = blade.left;
        blade.speed *= -1;
      }
      self._setTipX(i, x);
    }

    self.canvas.renderAll();
    self._scheduleUpdate();
  };

  self._scheduleUpdate = function() {
    requestAnimationFrame(function() {
      self._updatePaths();
    });
  };

  self._prepareCanvas();
  self._scheduleUpdate();
}<commit_msg>Fix saturation parameter<commit_after>/**
 * JavaScript grass animation
 * Requires Fabric.js
 * @author Andrew Richardson <awrichardson6@gmail.com>
 *
 * Usage:
 *   // Draw on <canvas> with id "my_canvas" with 50% saturation
 *   var field = new grassyField('my_canvas', 0.5);
 */
function grassyField(canvasId, saturation) {
  var self = this;
  self.element = document.getElementById(canvasId);
  self.canvas = new fabric.Canvas(canvasId);
  self.saturation = saturation;
  self.blades = null;
  self.lastUpdate = new Date().getTime();

  var green = { r: 0, g: 128, b: 0 },
    brown = { r: 222, g: 184, b: 135 };

  /**
   * Resize the canvas
   */
  self.resizeCanvas = function(width, height) {
    var curWidth = self.canvas.getWidth();
    var curHeight = self.canvas.getHeight();

    if (curWidth != width || curHeight != height) {
      self.canvas.clear();
      self.canvas.setWidth(width);
      self.canvas.setHeight(height);
      self._prepareCanvas();
    }
  };

  /**
   * Get a color partially between two other colors
   * c1 - starting color
   * c2 - ending color
   * t - position in the range 0-1
   */
  function colorStep(c1, c2, t) {
    if (t < 0) {
      t = 0;
    } else if (t > 1) {
      t = 1;
    }

    return {
      r: parseInt(c1['r'] + (c2['r'] - c1['r']) * t),
      g: parseInt(c1['g'] + (c2['g'] - c1['g']) * t),
      b: parseInt(c1['b'] + (c2['b'] - c1['b']) * t)
    };
  }

  /**
   * Return a random number within the given range
   */
  function randRange(min, max) {
    var range = max - min;
    return Math.floor((Math.random() * range) + min);
  }

  /**
   * Return x or -x with a 50/50 probability
   */
  function maybeNegative(x) {
    return (Math.random() > .5) ? x : -x;
  }

  self._makeBlade = function(centerX, height) {
    var topY = randRange(0, 15);
    var bottomY = height;
    var middleY = (topY + bottomY) / 2;

    var width = randRange(20, 40);
    var halfWidth = width / 2;
    var curve = maybeNegative(randRange(width / 3, width * 2 / 3));

    var leftX = centerX - halfWidth;
    var leftCurveX = leftX + curve;

    var rightX = leftX + width;
    var rightCurveX = rightX + curve;

    var startX = randRange(leftX, rightX);

    var colorVariance = maybeNegative(Math.random() * .25);
    var color = colorStep(brown, green, self.saturation + colorVariance);

    var path = new fabric.Path('M ' + leftX + ' ' + bottomY + ' ' +
                               'Q ' + leftCurveX + ' ' + middleY + ' ' + startX + ' ' + topY + ' ' +
                               'Q ' + rightCurveX + ' ' + middleY + ' ' + rightX + ' ' + bottomY + ' z');
    path.set({
      fill: 'rgb(' + color['r'] + ',' + color['g'] + ',' + color['b'] + ')',
      selectable: false
    });

    return {
      path: path,
      left: centerX - width / 3,
      right: centerX + width / 3,
      speed: maybeNegative(randRange(1, 5)) / 1000
    };
  };

  self._prepareCanvas = function() {
    var width = self.canvas.getWidth();
    var height = self.canvas.getHeight();
    var numBlades = width / 10;
    self.blades = [];

    for (var i=0; i<numBlades; i++) {
      var blade = self._makeBlade(randRange(0, width), height);
      self.blades.push(blade);
      self.canvas.add(blade.path);
    }
  };

  self._getTipX = function(i) {
    return self.blades[i].path.path[1][3];
  };

  self._setTipX = function(i, x) {
    self.blades[i].path.path[1][3] = x;
  };

  self._updatePaths = function() {
    var now = new Date().getTime();
    var elapsed = now - self.lastUpdate;
    self.lastUpdate = now;

    for (var i=0; i<self.blades.length; i++) {
      var blade = self.blades[i];

      var x = self._getTipX(i) + blade.speed * elapsed;
      if (x >= blade.right) {
        x = blade.right;
        blade.speed *= -1;
      } else if (x <= blade.left) {
        x = blade.left;
        blade.speed *= -1;
      }
      self._setTipX(i, x);
    }

    self.canvas.renderAll();
    self._scheduleUpdate();
  };

  self._scheduleUpdate = function() {
    requestAnimationFrame(function() {
      self._updatePaths();
    });
  };

  self._prepareCanvas();
  self._scheduleUpdate();
}
<|endoftext|>


<commit_before>var _ = require('lodash'),
    Store = require('../lib/core/store'),
    TasksContants = require('../constants/tasks'),
    savedTask;

module.exports = Store.extend({
    name: 'TasksStore',

    getDefaultHandlers: function() {
        var handlers = {};

        handlers[TasksContants.RETRIEVE_SUCCESS] = '_onRetrieveSuccess';
        handlers[TasksContants.CREATE_START] = '_onCreateStart';
        handlers[TasksContants.CREATE_ERROR] = '_onCreateError';
        handlers[TasksContants.UPDATE_START] = '_onUpdateStart';
        handlers[TasksContants.UPDATE_ERROR] = '_onUpdateError';
        handlers[TasksContants.DESTROY_START] = '_onDestroyStart';
        handlers[TasksContants.DESTROY_ERROR] = '_onDestroyError';
        handlers[TasksContants.FILTER] = '_onFilter';

        return handlers;
    },

    initialize: function() {
        this.tasks = [];
    },

    createTask: function(task) {
        return {
            id: 'tsk_' + (new Date()).valueOf(),
            completed: false,
            editing: false,
            text: task
        };
    },

    getAll: function() {
        return this.tasks;
    },

    saveState: function() {
        return {
            tasks: this.tasks
        };
    },

    restoreState: function(state) {
        this.tasks = state.tasks;
    },

    _onRetrieveSuccess: function(tasks) {
        this.tasks = tasks;

        this.emitChanges();
    },

    _onCreateStart: function(task) {
        this.tasks.push(task);

        this.emitChanges();
    },

    _onCreateError: function(error) {
        this.tasks.pop();

        this.emitChanges();
    },

    _onUpdateStart: function(task) {
        var oldTask = _.first(this.tasks, {id: task.id});

        if (oldTask) {
            savedTask = _.clone(oldTask);

            oldTask.completed = task.completed;
            oldTask.text = task.text;

            this.emitChanges();
        }
    },

    _onUpdateError: function(error) {
        var newTask = _.first(this.tasks, {id: savedTask.id});

        if (newTask) {
            newTask.completed = savedTask.completed;
            newTask.text = savedTask.text;

            savedTask = null;

            this.emitChanges();
        }
    },

    _onDestroyStart: function(task) {
        _.remove(this.tasks, {id: task.id});

        savedTask = task;

        this.emitChanges();
    },

    _onDestroyError: function(error) {
        this.tasks.push(savedTask);

        savedTask = null;

        this.emitChanges();
    },

    _onFilter: function(filter) {
        var tasks = this.tasks;

        if (filter.byCompleted) {
            tasks = _.filter(this.tasks, 'completed');
        }

        if (filter.byText) {
            tasks = _.filter(this.tasks, function(task) {
                new RegExp(filter.byText).test(task.text);
            });
        }

        this.tasks = tasks;

        this.emitChanges();
    }
});<commit_msg>Tasks store documented.<commit_after>var _ = require('lodash'),
    Store = require('../lib/core/store'),
    TasksContants = require('../constants/tasks'),
    savedTask;

module.exports = Store.extend({
    /**
     * Identifier of the store
     *
     * @type {string}
     */
    name: 'TasksStore',

    /**
     * Returns the default handlers for actions that the store listens to
     *
     * @return {Object}
     */
    getDefaultHandlers: function() {
        var handlers = {};

        handlers[TasksContants.RETRIEVE_SUCCESS] = '_onRetrieveSuccess';
        handlers[TasksContants.CREATE_START] = '_onCreateStart';
        handlers[TasksContants.CREATE_ERROR] = '_onCreateError';
        handlers[TasksContants.UPDATE_START] = '_onUpdateStart';
        handlers[TasksContants.UPDATE_ERROR] = '_onUpdateError';
        handlers[TasksContants.DESTROY_START] = '_onDestroyStart';
        handlers[TasksContants.DESTROY_ERROR] = '_onDestroyError';
        handlers[TasksContants.FILTER] = '_onFilter';

        return handlers;
    },

    /**
     * Initializes the store
     *
     * @returns {void}
     */
    initialize: function() {
        this.tasks = [];
    },

    /**
     * Returns a new task object
     *
     * @param {string} task
     * @return {Object}
     */
    createTask: function(task) {
        return {
            id: 'tsk_' + (new Date()).valueOf(),
            completed: false,
            editing: false,
            text: task
        };
    },

    /**
     * Returns all tasks present now in the store
     *
     * @return {Array}
     */
    getAll: function() {
        return this.tasks;
    },

    /**
     * Returns the current state of the store for being restored later
     *
     * @return {Object}
     */
    saveState: function() {
        return {tasks: this.tasks};
    },

    /**
     * Restores the saved state of the store
     *
     * @param {string} state
     * @return {void}
     */
    restoreState: function(state) {
        this.tasks = state.tasks || [];
    },

    /**
     * Handler for when the retrieve_success action is executed
     *
     * @param {Array} tasks
     * @return {void}
     * @protected
     */
    _onRetrieveSuccess: function(tasks) {
        this.tasks = tasks;

        this.emitChanges();
    },

    /**
     * Handler for when the create_start action is executed
     *
     * @param {Object} task
     * @return {void}
     * @protected
     */
    _onCreateStart: function(task) {
        this.tasks.push(task);

        this.emitChanges();
    },

    /**
     * Handler for when the create_error action is executed
     *
     * @param {Error} error
     * @return {void}
     * @protected
     */
    _onCreateError: function(error) {
        this.tasks.pop();

        this.emitChanges();
    },

    /**
     * Handler for when the update_start action is executed
     *
     * @param {Object} task
     * @return {void}
     * @protected
     */
    _onUpdateStart: function(task) {
        var oldTask = _.first(this.tasks, {id: task.id});

        if (oldTask) {
            savedTask = _.clone(oldTask);

            oldTask.completed = task.completed;
            oldTask.text = task.text;

            this.emitChanges();
        }
    },

    /**
     * Handler for when the update_error action is executed
     *
     * @param {Error} error
     * @return {void}
     * @protected
     */
    _onUpdateError: function(error) {
        var newTask = _.first(this.tasks, {id: savedTask.id});

        if (newTask) {
            newTask.completed = savedTask.completed;
            newTask.text = savedTask.text;

            savedTask = null;

            this.emitChanges();
        }
    },

    /**
     * Handler for when the destroy_start action is executed
     *
     * @param {Object} task
     * @return {void}
     * @protected
     */
    _onDestroyStart: function(task) {
        _.remove(this.tasks, {id: task.id});

        savedTask = task;

        this.emitChanges();
    },

    /**
     * Handler for when the destroy_error action is executed
     *
     * @return {void}
     * @protected
     */
    _onDestroyError: function() {
        this.tasks.push(savedTask);

        savedTask = null;

        this.emitChanges();
    },

    /**
     * Handler for when the filter action is executed
     *
     * @param {Object} filter
     * @protected
     */
    _onFilter: function(filter) {
        var tasks = this.tasks;

        if (filter.byCompleted) {
            tasks = _.filter(this.tasks, 'completed');
        }

        if (filter.byText) {
            tasks = _.filter(this.tasks, function(task) {
                new RegExp(filter.byText).test(task.text);
            });
        }

        this.tasks = tasks;

        this.emitChanges();
    }
});<|endoftext|>


<commit_before>(function (factory) {
  /* global define */
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else {
    // Browser globals: jQuery
    factory(window.jQuery);
  }
}(function ($) {
  // template
  var tmpl = $.summernote.renderer.getTemplate();

  // core functions: range, dom
  var range = $.summernote.core.range;
  var dom = $.summernote.core.dom;

  var KEY = {
    UP: 38,
    DOWN: 40,
    LEFT: 37,
    RIGHT: 39,
    ENTER: 13
  };
  var COLUMN_LENGTH = 15;
  var COLUMN_WIDTH = 35;

  var currentColumn, currentRow, totalColumn, totalRow = 0;

  // special characters data set
  var specialCharDataSet = [
    '"',   // "
    '&',    // &
    '<',     // <
    '>',     // >
    '¡',
    '¢',
    '£',
    '¤',
    '¥',
    '¦',
    '§',
    '¨',
    '©',
    'ª',
    '«',
    '¬',
    //'',
    '®',
    '¯',
    '°',
    '±',
    '²',
    '³',
    '´',
    'µ',
    '¶',
    '·',
    '¸',
    '¹',
    'º',
    '»',
    '¼',
    '½',
    '¾',
    '¿',
    '×',
    '÷',
    'ƒ',
    'ˆ',
    '˜',
    /*' ',
    ' ',
    ' ',
    '',
    '',
    '',
    '',*/
    '–',
    '—',
    '‘',
    '’',
    '‚',
    '“',
    '”',
    '„',
    '†',
    '‡',
    '•',
    '…',
    '‰',
    '′',
    '″',
    '‹',
    '›',
    '‾',
    '⁄',
    '€',
    'ℑ',
    '℘',
    'ℜ',
    '™',
    'ℵ',
    '←',
    '↑',
    '→',
    '↓',
    '↔',
    '↵',
    '⇐',
    '⇑',
    '⇒',
    '⇓',
    '⇔',
    '∀',
    '∂',
    '∃',
    '∅',
    '∇',
    '∈',
    '∉',
    '∋',
    '∏',
    '∑',
    '−',
    '∗',
    '√',
    '∝',
    '∞',
    '∠',
    '∧',
    '∨',
    '∩',
    '∪',
    '∫',
    '∴',
    '∼',
    '≅',
    '≈',
    '≠',
    '≡',
    '≤',
    '≥',
    '⊂',
    '⊃',
    '⊄',
    '⊆',
    '⊇',
    '⊕',
    '⊗',
    '⊥',
    '⋅',
    '⌈',
    '⌉',
    '⌊',
    '⌋',
    //'⟨',
    //'⟩',
    '◊',
    '♠',
    '♣',
    '♥',
    '♦'
  ];

  /**
   * @member plugin.specialChar
   * @private
   * @param {jQuery} $editable
   * @return {String}
   */
  var getTextOnRange = function ($editable) {
    $editable.focus();

    var rng = range.create();

    // if range on anchor, expand range with anchor
    if (rng.isOnAnchor()) {
      var anchor = dom.ancestor(rng.sc, dom.isAnchor);
      rng = range.createFromNode(anchor);
    }

    return rng.toString();
  };

  /**
   * Make Special Characters Table
   *
   * @member plugin.specialChar
   * @private
   * @return {jQuery}
   */
  var makeSpecialCharSetTable = function () {
    var $table = $('
');
    $.each(specialCharDataSet, function (idx, text) {
      var $td = $('').addClass('note-specialchar-node');
      var $tr = (idx % COLUMN_LENGTH === 0) ? $('
') : $table.find('tr').last();

      $td.append($(tmpl.button(text, {
        title: text,
        value: encodeURIComponent(text)
      })).css({
        width: COLUMN_WIDTH
      }));

      $tr.append($td);
      if (idx % COLUMN_LENGTH === 0) {
        $table.append($tr);
      }
    });

    totalRow = $table.find('tr').length;
    totalColumn = COLUMN_LENGTH;

    return $table;
  };

  /**
   * Show Special Characters and set event handlers on dialog controls.
   *
   * @member plugin.specialChar
   * @private
   * @param {jQuery} $dialog
   * @param {jQuery} $dialog
   * @param {Object} text
   * @return {Promise}
   */
  var showSpecialCharDialog = function ($editable, $dialog, text) {
    return $.Deferred(function (deferred) {
      var $specialCharDialog = $dialog.find('.note-specialchar-dialog');
      var $specialCharNode = $specialCharDialog.find('.note-specialchar-node');
      var $selectedNode = null;
      var ARROW_KEYS = [KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT];
      var ENTER_KEY = KEY.ENTER;

      function addActiveClass($target) {
        if (!$target) {
          return;
        }
        $target.find('button').addClass('active');
        $selectedNode = $target;
      }

      function removeActiveClass($target) {
        $target.find('button').removeClass('active');
        $selectedNode = null;
      }

      // find next node
      function findNextNode(row, column) {
        var findNode = null;
        $.each($specialCharNode, function (idx, $node) {
          var findRow = Math.ceil((idx + 1) / COLUMN_LENGTH);
          var findColumn = ((idx + 1) % COLUMN_LENGTH === 0) ? COLUMN_LENGTH : (idx + 1) % COLUMN_LENGTH;
          if (findRow === row && findColumn === column) {
            findNode = $node;
            return false;
          }
        });
        return $(findNode);
      }

      function arrowKeyHandler(keyCode) {
        // left, right, up, down key
        var $nextNode;
        var lastRowColumnLength = $specialCharNode.length % totalColumn;

        if (KEY.LEFT === keyCode) {

          if (currentColumn > 1) {
            currentColumn = currentColumn - 1;
          } else if (currentRow === 1 && currentColumn === 1) {
            currentColumn = lastRowColumnLength;
            currentRow = totalRow;
          } else {
            currentColumn = totalColumn;
            currentRow = currentRow - 1;
          }

        } else if (KEY.RIGHT === keyCode) {

          if (currentRow === totalRow && lastRowColumnLength === currentColumn) {
            currentColumn = 1;
            currentRow = 1;
          } else if (currentColumn < totalColumn) {
            currentColumn = currentColumn + 1;
          } else {
            currentColumn = 1;
            currentRow = currentRow + 1;
          }

        } else if (KEY.UP === keyCode) {
          if (currentRow === 1 && lastRowColumnLength < currentColumn) {
            currentRow = totalRow - 1;
          } else {
            currentRow = currentRow - 1;
          }
        } else if (KEY.DOWN === keyCode) {
          currentRow = currentRow + 1;
        }

        if (currentRow === totalRow && currentColumn > lastRowColumnLength) {
          currentRow = 1;
        } else if (currentRow > totalRow) {
          currentRow = 1;
        } else if (currentRow < 1) {
          currentRow = totalRow;
        }

        $nextNode = findNextNode(currentRow, currentColumn);

        if ($nextNode) {
          removeActiveClass($selectedNode);
          addActiveClass($nextNode);
        }
      }

      function enterKeyHandler() {
        if (!$selectedNode) {
          return;
        }

        deferred.resolve(decodeURIComponent($selectedNode.find('button').attr('data-value')));
        $specialCharDialog.modal('hide');
      }

      function keyDownEventHandler(event) {
        event.preventDefault();
        var keyCode = event.keyCode;
        if (keyCode === undefined || keyCode === null) {
          return;
        }
        // check arrowKeys match
        if (ARROW_KEYS.indexOf(keyCode) > -1) {
          if ($selectedNode === null) {
            addActiveClass($specialCharNode.eq(0));
            currentColumn = 1;
            currentRow = 1;
            return;
          }
          arrowKeyHandler(keyCode);
        } else if (keyCode === ENTER_KEY) {
          enterKeyHandler();
        }
        return false;
      }

      // remove class
      removeActiveClass($specialCharNode);
      // find selected node
      if (text) {
        for (var i = 0; i < $specialCharNode.length; i++) {
          var $checkNode = $($specialCharNode[i]);
          if ($checkNode.text() === text) {
            addActiveClass($checkNode);
            currentRow = Math.ceil((i + 1) / COLUMN_LENGTH);
            currentColumn = (i + 1) % COLUMN_LENGTH;
          }
        }
      }

      $specialCharDialog.one('shown.bs.modal', function () {
        $(document).on('keydown', keyDownEventHandler);
        $specialCharNode.on('click', function (event) {
          event.preventDefault();
          deferred.resolve(decodeURIComponent($(event.currentTarget).find('button').attr('data-value')));
          $specialCharDialog.modal('hide');
        });
      }).one('hidden.bs.modal', function () {
        $specialCharNode.off('click');
        $(document).off('keydown', keyDownEventHandler);
        if (deferred.state() === 'pending') {
          deferred.reject();
        }
      }).modal('show');

      // tooltip
      $dialog.find('button').tooltip({
        container: $specialCharDialog.find('.form-group'),
        trigger: 'hover',
        placement: 'top'
      }).on('click', function () {
        $(this).tooltip('hide');
      });

      // $editable blur
      $editable.blur();
    });
  };

  /**
   * @class plugin.specialChar
   *
   * Special Characters Plugin
   *
   * ### load script
   *
   * ```
   * < script src="plugin/summernote-ext-specialchar.js"></script >
   * ```
   *
   * ### use a plugin in toolbar
   * ```
   *    $("#editor").summernote({
   *    ...
   *    toolbar : [
   *        ['group', [ 'specialChar' ]]
   *    ]
   *    ...
   *    });
   * ```
   */
  $.summernote.addPlugin({
    /** @property {String} name name of plugin */
    name: 'specialChar',
    /**
     * @property {Object} buttons
     * @property {function(object): string} buttons.specialChar
     */
    buttons: {
      specialChar: function (lang, options) {
        return tmpl.iconButton(options.iconPrefix + 'font ' + options.iconPrefix + 'flip-vertical', {
          event: 'showSpecialCharDialog',
          title: lang.specialChar.specialChar,
          hide: true
        });
      }
    },

    /**
     * @property {Object} dialogs
     * @property {function(object, object): string} dialogs.specialChar
    */
    dialogs: {
      specialChar: function (lang) {
        var body = '
' +
                      makeSpecialCharSetTable()[0].outerHTML +
                   '
';
        return tmpl.dialog('note-specialchar-dialog', lang.specialChar.select, body);
      }
    },
    /**
     * @property {Object} events
     * @property {Function} events.showSpecialCharDialog
     */
    events: {
      showSpecialCharDialog: function (event, editor, layoutInfo) {
        var $dialog = layoutInfo.dialog(),
            $editable = layoutInfo.editable(),
            currentSpecialChar = getTextOnRange($editable);

        // save current range
        editor.saveRange($editable);

        showSpecialCharDialog($editable, $dialog, currentSpecialChar).then(function (selectChar) {
          // when ok button clicked

          // restore range
          editor.restoreRange($editable);

          // build node
          var $node = $('').html(selectChar)[0];

          if ($node) {
            // insert video node
            editor.insertNode($editable, $node);
          }
        }).fail(function () {
          // when cancel button clicked
          editor.restoreRange($editable);
        });
      }
    },

    // define language
    langs: {
      'en-US': {
        specialChar: {
          specialChar: 'SPECIAL CHARACTERS',
          select: 'Select Special characters'
        }
      },
      'ko-KR': {
        specialChar: {
          specialChar: '특수문자',
          select: '특수문자를 선택하세요'
        }
      }
    }
  });
}));
<commit_msg>Remove specialchar.js<commit_after><|endoftext|>


<commit_before>/*
 * This file defines the base webpack configuration that is shared across both
 * build and testing environments. If you need to add anything to the general
 * webpack config, like adding loaders for different asset types, different
 * preLoaders or Plugins - they should be done here. If you are looking to add
 * dev specific features, please do so in webpack.config.dev.js - if you wish
 * to add test specific features, these can be done in the karma.conf.js.
 *
 * Note:
 *  This file is not called directly by webpack.
 *  It copied once for each plugin by parse_bundle_plugin.js
 *  and used as a template, with additional plugin-specific
 *  modifications made on top.
 */
var path = require('path');

/*
 * This is a filthy hack. Do as I say, not as I do.
 * Taken from: https://gist.github.com/branneman/8048520#6-the-hack
 * This forces the NODE_PATH environment variable to include the main
 * kolibri node_modules folder, so that even plugins being built outside
 * of the kolibri folder will have access to all installed loaders, etc.
 * Doing it here, rather than at command invocation, allows us to do this
 * in a cross platform way, and also to avoid having to prepend it to all
 * our commands that end up invoking webpack.
 */

process.env.NODE_PATH = path.resolve(path.join(__dirname, '..', '..', 'node_modules'));
require('module').Module._initPaths();

var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
var merge = require('webpack-merge');

var production = process.env.NODE_ENV === 'production';
var lint = (process.env.LINT || production);

// helps convert to older string syntax for vue-loader
var combineLoaders = require('webpack-combine-loaders');

var postCSSLoader = {
  loader: 'postcss-loader',
  options: { config: path.resolve(__dirname, '../../postcss.config.js') }
};

var cssLoader = {
  loader: 'css-loader',
  options: { minimize: production, sourceMap: !production }
};

// for stylus blocks in vue files.
// note: vue-style-loader includes postcss processing
var vueStylusLoaders = [ 'vue-style-loader',  cssLoader, 'stylus-loader' ];
if (lint) {
  vueStylusLoaders.push('stylint-loader')
}

// for scss blocks in vue files (e.g. Keen-UI files)
var vueSassLoaders = [
  'vue-style-loader', // includes postcss processing
  cssLoader,
  {
    loader: 'sass-loader',
    // prepends these variable override values to every parsed vue SASS block
    options: { data: '@import "~kolibri.styles.keenVars";' }
  }
];

// primary webpack config
var config = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            js: 'buble-loader',
            stylus: combineLoaders(vueStylusLoaders),
            scss: combineLoaders(vueSassLoaders),
          },
          // handles <mat-svg/>, <ion-svg/>, <iconic-svg/>, and <file-svg/> svg inlining
          preLoaders: { html: 'svg-icon-inline-loader' }
        }
      },
      {
        test: /\.js$/,
        loader: 'buble-loader',
        exclude: /node_modules\/(?!(keen-ui)\/).*/
      },
      {
        test: /\.css$/,
        use: [ 'style-loader', cssLoader, postCSSLoader ]
      },
      {
        test: /\.styl$/,
        use: [ 'style-loader', cssLoader, postCSSLoader, 'stylus-loader' ]
      },
      {
        test: /\.s[a|c]ss$/,
        use: [ 'style-loader', cssLoader, postCSSLoader, 'sass-loader' ]
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        use: {
          loader: 'url-loader',
          options: { limit: 10000, name: '[name].[ext]?[hash]' }
        }
      },
      // Use file loader to load font files.
      {
        test: /\.(eot|woff|ttf|woff2)$/,
        use: {
          loader: 'file-loader',
          options: { name: '[name].[ext]?[hash]' }
        }
      },
      // Hack to make the onloadCSS node module properly export-able.
      // Not currently used - we may be able to delete this if we
      // deprecate our custom KolibriModule async css loading functionality.
      {
        test: /fg-loadcss\/src\/onloadCSS/,
        use: 'exports-loader?onloadCSS'
      }
    ]
  },
  plugins: [],
  resolve: {
    extensions: [ ".js", ".vue", ".styl" ],
  },
  node: {
    __filename: true
  }
};

// Only lint in dev mode if LINT env is set. Always lint in production.
if (lint) {

  // adds custom rules
  require('./htmlhint_custom');

  var lintConfig = {
    module: {
      rules: [
        {
          test: /\.(vue|js)$/,
          enforce: 'pre',
          use: {
            loader: 'eslint-loader',
            options: { failOnError: true }
          },
          exclude: /node_modules/
        },
        {
          test: /\.(vue|html)/,
          enforce: 'pre',
          use: {
            loader: 'htmlhint-loader',
            options: { failOnError: true, emitAs: "error" }
          },
          exclude: /node_modules/
        },
        {
          test: /\.styl$/,
          enforce: 'pre',
          loader: 'stylint-loader'
        }
      ]
    }
  };
  config = merge.smart(config, lintConfig);
}

module.exports = config;
<commit_msg>Add html5media rule in base config<commit_after>/*
 * This file defines the base webpack configuration that is shared across both
 * build and testing environments. If you need to add anything to the general
 * webpack config, like adding loaders for different asset types, different
 * preLoaders or Plugins - they should be done here. If you are looking to add
 * dev specific features, please do so in webpack.config.dev.js - if you wish
 * to add test specific features, these can be done in the karma.conf.js.
 *
 * Note:
 *  This file is not called directly by webpack.
 *  It copied once for each plugin by parse_bundle_plugin.js
 *  and used as a template, with additional plugin-specific
 *  modifications made on top.
 */
var path = require('path');

/*
 * This is a filthy hack. Do as I say, not as I do.
 * Taken from: https://gist.github.com/branneman/8048520#6-the-hack
 * This forces the NODE_PATH environment variable to include the main
 * kolibri node_modules folder, so that even plugins being built outside
 * of the kolibri folder will have access to all installed loaders, etc.
 * Doing it here, rather than at command invocation, allows us to do this
 * in a cross platform way, and also to avoid having to prepend it to all
 * our commands that end up invoking webpack.
 */

process.env.NODE_PATH = path.resolve(path.join(__dirname, '..', '..', 'node_modules'));
require('module').Module._initPaths();

var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
var merge = require('webpack-merge');

var production = process.env.NODE_ENV === 'production';
var lint = (process.env.LINT || production);

// helps convert to older string syntax for vue-loader
var combineLoaders = require('webpack-combine-loaders');

var postCSSLoader = {
  loader: 'postcss-loader',
  options: { config: path.resolve(__dirname, '../../postcss.config.js') }
};

var cssLoader = {
  loader: 'css-loader',
  options: { minimize: production, sourceMap: !production }
};

// for stylus blocks in vue files.
// note: vue-style-loader includes postcss processing
var vueStylusLoaders = [ 'vue-style-loader',  cssLoader, 'stylus-loader' ];
if (lint) {
  vueStylusLoaders.push('stylint-loader')
}

// for scss blocks in vue files (e.g. Keen-UI files)
var vueSassLoaders = [
  'vue-style-loader', // includes postcss processing
  cssLoader,
  {
    loader: 'sass-loader',
    // prepends these variable override values to every parsed vue SASS block
    options: { data: '@import "~kolibri.styles.keenVars";' }
  }
];

// primary webpack config
var config = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            js: 'buble-loader',
            stylus: combineLoaders(vueStylusLoaders),
            scss: combineLoaders(vueSassLoaders),
          },
          // handles <mat-svg/>, <ion-svg/>, <iconic-svg/>, and <file-svg/> svg inlining
          preLoaders: { html: 'svg-icon-inline-loader' }
        }
      },
      {
        test: /\.js$/,
        loader: 'buble-loader',
        exclude: /node_modules\/(?!(keen-ui)\/).*/
      },
      {
        test: /\.css$/,
        use: [ 'style-loader', cssLoader, postCSSLoader ]
      },
      {
        test: /\.styl$/,
        use: [ 'style-loader', cssLoader, postCSSLoader, 'stylus-loader' ]
      },
      {
        test: /html5media\/dist\/api\/1\.1\.8\/html5media/,
        use: [{
          loader: "imports-loader?this=>window"
        }],
      },
      {
        test: /\.s[a|c]ss$/,
        use: [ 'style-loader', cssLoader, postCSSLoader, 'sass-loader' ]
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        use: {
          loader: 'url-loader',
          options: { limit: 10000, name: '[name].[ext]?[hash]' }
        }
      },
      // Use file loader to load font files.
      {
        test: /\.(eot|woff|ttf|woff2)$/,
        use: {
          loader: 'file-loader',
          options: { name: '[name].[ext]?[hash]' }
        }
      },
      // Hack to make the onloadCSS node module properly export-able.
      // Not currently used - we may be able to delete this if we
      // deprecate our custom KolibriModule async css loading functionality.
      {
        test: /fg-loadcss\/src\/onloadCSS/,
        use: 'exports-loader?onloadCSS'
      }
    ]
  },
  plugins: [],
  resolve: {
    extensions: [ ".js", ".vue", ".styl" ],
  },
  node: {
    __filename: true
  }
};

// Only lint in dev mode if LINT env is set. Always lint in production.
if (lint) {

  // adds custom rules
  require('./htmlhint_custom');

  var lintConfig = {
    module: {
      rules: [
        {
          test: /\.(vue|js)$/,
          enforce: 'pre',
          use: {
            loader: 'eslint-loader',
            options: { failOnError: true }
          },
          exclude: /node_modules/
        },
        {
          test: /\.(vue|html)/,
          enforce: 'pre',
          use: {
            loader: 'htmlhint-loader',
            options: { failOnError: true, emitAs: "error" }
          },
          exclude: /node_modules/
        },
        {
          test: /\.styl$/,
          enforce: 'pre',
          loader: 'stylint-loader'
        }
      ]
    }
  };
  config = merge.smart(config, lintConfig);
}

module.exports = config;
<|endoftext|>


<commit_before>var Promise = require('bluebird');
var moment = require('moment');
var fs = Promise.promisifyAll(require('fs'));
var path = require('path');
var ent = require('ent');
var ObjectId = require('mongodb').ObjectID;

module.exports.getStats = function (roomMember) {
	return generateStats(roomMember, 'statsForSelfTemplate.ejs');
};

module.exports.getStatsForUser = function (username, roomId) {
	return RoomService.getRoomMemberByNickAndRoom(username, roomId)
		.then(function (roomMember) {
			if (!roomMember) throw new Error('User not found');
			return generateStats(roomMember, 'statsForUserTemplate.ejs');
		});
};

function generateStats(roomMember, template) {
	return Message.count({author: roomMember.user.id})
		.then(function (messageCount) {
			return Promise.join(
				fs.readFileAsync(path.join(__dirname, template)),
				messageCount,
				Message.count({author: roomMember.user.id, edited: true}),
				getActiveDays(roomMember),
				Message.find({author: roomMember.user.id, type: 'standard'}).sort('createdAt ASC').limit(1),
				Message.find({author: roomMember.user.id, type: 'standard'}).skip(_.random(0, messageCount)).limit(1),
				getEmoticonCounts(roomMember),
				getHangmanStats(roomMember.user.id),
				getFightStatistics(roomMember.user.id)
			)
				.spread(function (template, messageCount, editCount, activeDays, firstMessage, randomMessage, emoticonCounts, hangmanStats, fightStats) {
					firstMessage = firstMessage[0];
					randomMessage = randomMessage[0];

					var dateFormat = 'dddd MMMM Do, YYYY';
					var dateTimeFormat = 'dddd MMMM Do, YYYY @ h:mm:ssa';

					var averageCountPerDay = 0;

					if (messageCount && activeDays.length) {
						averageCountPerDay = messageCount / activeDays.length;
					}

					var data = {
						user: roomMember.user.nick,
						messageCount: messageCount,
						averageMessageCountPerDay: ~~averageCountPerDay,
						editCount: editCount,
						startDate: moment(roomMember.user.createdAt).format(dateFormat),
						totalDays: moment().diff(roomMember.user.createdAt, 'days'),
						activeDays: activeDays.length,
						firstMessage: firstMessage ? '"' + ent.decode(firstMessage.text) + '" (' + moment(firstMessage.createdAt).format(dateTimeFormat) + ')' : '',
						emotes: ent.decode(_.pluck(emoticonCounts, 'emoticon').join(' ')),
						randomMessage: randomMessage ? '"' + ent.decode(randomMessage.text) + '" (' + moment(randomMessage.createdAt).format(dateTimeFormat) + ')' : '',
						hangmanGuessCount: hangmanStats.count,
						hangmanGuessAccuracy: hangmanStats.guessAccuracy ? hangmanStats.guessAccuracy + '%': 'N/A',
						hangmanPrivateWinLoss: (hangmanStats.privateWins ? hangmanStats.privateWins : 0) + ' - ' + (hangmanStats.privateLosses ? hangmanStats.privateLosses : 0),
						hangmanPublicWinLoss: hangmanStats.publicWins + ' - ' + hangmanStats.publicLosses,
						fightWinPercentage: fightStats.fightWinPercentage,
						fightRoundWinPercentage: fightStats.fightRoundWinPercentage,
						fightVictim1: fightStats.topVictims && fightStats.topVictims[0] ? fightStats.topVictims[0].userNick + " " + fightStats.topVictims[0].beatings : '',
						fightVictim2: fightStats.topVictims && fightStats.topVictims[1] ? fightStats.topVictims[1].userNick + " " + fightStats.topVictims[1].beatings : '',
						fightVictim3: fightStats.topVictims && fightStats.topVictims[2] ? fightStats.topVictims[2].userNick + " " + fightStats.topVictims[2].beatings : '',
						fightVictim4: fightStats.topVictims && fightStats.topVictims[3] ? fightStats.topVictims[3].userNick + " " + fightStats.topVictims[3].beatings : '',
						fightVictim5: fightStats.topVictims && fightStats.topVictims[4] ? fightStats.topVictims[4].userNick + " " + fightStats.topVictims[4].beatings : ''
					};

					if (activeDays) {
						var mostActive = makeActiveModel(activeDays);

						var activeDaysSorted = _(activeDays)
							.sortByAll([
								function (day) {
									return day._id.year;
								},
								function (day) {
									return day._id.dayOfYear;
								}
							])
							.reverse()
							.value();

						var lastActive = makeActiveModel(activeDaysSorted);

						data.activeDate = mostActive.day.format(dateFormat) + ' (' + mostActive.object.count + ' messages)';
						data.lastActiveDate = lastActive.day.format(dateFormat) + ' (' + lastActive.object.count + ' messages)';
					}

					return ent.encode(_.template(template)(data));
				});
		});
}

function makeActiveModel(activeDays) {
	if (!activeDays) return;

	var mostActiveDayObject = _.first(activeDays);
	var mostActiveYear = mostActiveDayObject._id.year;
	var mostActiveDayOfYear = mostActiveDayObject._id.dayOfYear;
	var mostActiveDay = moment().year(mostActiveYear).dayOfYear(mostActiveDayOfYear);

	return {
		object: mostActiveDayObject,
		year: mostActiveYear,
		dayOfYear: mostActiveDayOfYear,
		day: mostActiveDay
	}
}

function getActiveDays(roomMember) {
	return new Promise(function (resolve, reject) {
		Message.native(function (err, messageCollection) {
			if (err) return reject(err);
			messageCollection.aggregate([
				{
					$match: {
						author: new ObjectId(roomMember.user.id)
					}
				},
				{
					$group: {
						_id: {
							year: {$year: '$createdAt'},
							dayOfYear: {$dayOfYear: '$createdAt'}
						},
						count: {$sum: 1}
					}
				},
				{
					$sort: {count: -1}
				}
			], function (err, daysWithMessages) {
				if (err) return reject(err);
				resolve(daysWithMessages);
			});
		});
	});
}

function getEmoticonCounts(roomMember) {
	var emoticonRegex = /:\w+:/g;
	var countMap = {};

	return new Promise(function (resolve, reject) {
		Message.native(function (err, messageCollection) {
			if (err) return reject(err);

			messageCollection.find({author: new ObjectId(roomMember.user.id), text: {$regex: emoticonRegex}})
				.toArray(function (err, messages) {
					if (err) return reject(err);
					_.each(messages, function (message) {

						var matches = _.unique(message.text.match(emoticonRegex));
						if (matches) {
							_.each(matches, function (match) {
								countMap[match] = countMap[match] ? countMap[match] + 1 : 1;
							});
						}
					});

					var counts = _(countMap)
						.map(function (value, key) {
							return {count: value, emoticon: key, name: key.replace(/:/g, '')};
						})
						.sortBy('count')
						.reverse()
						.take(10)
						.value();

					resolve(counts);
				});
		});
	});
}

function getHangmanStats(userId) {
	return Promise.join(
		getHangmanUserStatistics(userId),
		getHangmanPublicStatistics()
	)
	.spread(function (userStats, publicStats) {
		stats = {};
		stats.count = userStats.guessMisses + userStats.guessHits;

		if (!stats.count || !userStats.guessHits) {
			stats.guessAccuracy = 0;
		}
		else {
			stats.guessAccuracy = Math.round((userStats.guessHits / stats.count) * 100);
		}

		stats.publicWins = publicStats.winCount;
		stats.publicLosses = publicStats.lossCount;

		stats.privateWins = userStats.privateGameWinCount;
		stats.privateLosses = userStats.privateGameLossCount;

		return stats;
	});
}

function getHangmanUserStatistics(userId) {
	return HangmanUserStatistics.findOne({user: userId}).then(function (hangmanUserStatistics) {
		if (hangmanUserStatistics) return hangmanUserStatistics;

		return HangmanUserStatistics.create({
			user: userId
		});
	});
}

function getHangmanPublicStatistics() {
	return HangmanPublicGameStatistics.find().then(function (publicStats) {
		if (publicStats && publicStats[0]) return publicStats[0];

		return HangmanPublicGameStatistics.create();
	});
}

function getFightStatistics(userId) {
	return new Promise(function (resolve, reject) {
		Fight.native(function (err, fightCollection) {
			if (err) return reject(err);
			fightCollection.aggregate(
				[
					{ $match: { $or: [ { opponent: ObjectId(userId) },{ challenger: ObjectId(userId) } ] } },
					{
						$project:
						{
							winningUser: '$winningUser',
							losingUser:
							{
								$cond: [
									{ $ne: [ '$winningUser', null ] },
									{ $cond: [ { $ne: [ '$winningUser', '$challenger' ] }, '$challenger', '$opponent' ] },
									null
								]
							},
							opponentUser: '$opponent',
							challengerUser: '$challenger',
						}
					}
				], function (err, fightData) {
					if (err) return reject(err);
					resolve(fightData);
				});
		});
	}).then(function(fightData) {

			var fightResults = {};
			var victims = {};
			var userIds = [];
			if (fightData) {
				var userStats = {};
				userStats.wins = 0;
				userStats.losses = 0;
				userStats.ties = 0;
				userStats.totalGames = 0;

				var fightIds = [];

				_.forEach(fightData, function (fightDataElement) {

					fightIds.push(fightDataElement._id);
					if (fightDataElement.winningUser) {
						if (fightDataElement.winningUser == userId) {
							userStats.wins++;

							if (fightDataElement.opponentUser == userId) {
								if (!victims[fightDataElement.challengerUser]) {
									victims[fightDataElement.challengerUser] = {};
									victims[fightDataElement.challengerUser].beatings = 0;
									victims[fightDataElement.challengerUser].id = fightDataElement.challengerUser;
								}

								victims[fightDataElement.challengerUser].beatings++;
								userIds.push(fightDataElement.challengerUser);
							}
							else {
								if (!victims[fightDataElement.opponentUser]) {
									victims[fightDataElement.opponentUser] = {};
									victims[fightDataElement.opponentUser].beatings = 0;
									victims[fightDataElement.opponentUser].id = fightDataElement.opponentUser;
								}

								victims[fightDataElement.opponentUser].beatings++;
								userIds.push(fightDataElement.opponentUser);
							}
						}
						else {
							userStats.losses++;
						}

						userStats.totalGames++;
					}
					else {
						userStats.ties++;
						userStats.totalGames++;
					}

					userStats.winPercentage = userStats.wins > 0 ?
						Math.round(((userStats.wins + (userStats.ties * 0.5)) / userStats.totalGames) * 100) : 0;
				});

				fightResults.fightWinPercentage = userStats.winPercentage + '% (' + userStats.wins + '-' + userStats.ties + '-' + userStats.losses + ')';
				fightResults.topVictims = _.take(_.sortByOrder(victims, ['beatings'], [false]), 5);

				userIds = _.unique(userIds);

				return User.find({ "_id": { $in : userIds } }).then(function(users) {

					_.forEach(fightResults.topVictims, function (victim) {
						var userIndex = _.findIndex(users, function (user) {
							return user.id == victim.id;
						});

						victim.userNick = users[userIndex].nick;
					});

					return FightRound.find( { fight: { $in: fightIds } } ).then(function (fightRounds) {
						var fightRoundStats = {};
						fightRoundStats.wins = 0;
						fightRoundStats.losses = 0;
						fightRoundStats.ties = 0;
						fightRoundStats.totalRounds = 0;

						_.forEach(fightRounds, function(fightRound) {
							if (fightRound.winningUser) {
								if (fightRound.winningUser == userId) {
									fightRoundStats.wins++;
								}
								else {
									fightRoundStats.losses++;
								}
							}
							else {
								fightRoundStats.ties++;
							}

							fightRoundStats.totalRounds++;

							fightRoundStats.winPercentage = fightRoundStats.wins > 0 ?
								Math.round(((fightRoundStats.wins + (fightRoundStats.ties * 0.5)) / fightRoundStats.totalRounds) * 100) : 0;
						});

						fightResults.fightRoundWinPercentage = fightRoundStats.winPercentage + '% (' + fightRoundStats.wins + '-' + fightRoundStats.losses + '-' + fightRoundStats.ties + ')';

						return fightResults;
					});
				});
			}

			fightResults.fightWinPercentage = '0% (0-0-0)';
			fightResults.victims = null;
			fightResults.fightRoundWinPercentage = '0% (0-0-0)';

			return fightResults;
		});
}
<commit_msg>fix stats fight order<commit_after>var Promise = require('bluebird');
var moment = require('moment');
var fs = Promise.promisifyAll(require('fs'));
var path = require('path');
var ent = require('ent');
var ObjectId = require('mongodb').ObjectID;

module.exports.getStats = function (roomMember) {
	return generateStats(roomMember, 'statsForSelfTemplate.ejs');
};

module.exports.getStatsForUser = function (username, roomId) {
	return RoomService.getRoomMemberByNickAndRoom(username, roomId)
		.then(function (roomMember) {
			if (!roomMember) throw new Error('User not found');
			return generateStats(roomMember, 'statsForUserTemplate.ejs');
		});
};

function generateStats(roomMember, template) {
	return Message.count({author: roomMember.user.id})
		.then(function (messageCount) {
			return Promise.join(
				fs.readFileAsync(path.join(__dirname, template)),
				messageCount,
				Message.count({author: roomMember.user.id, edited: true}),
				getActiveDays(roomMember),
				Message.find({author: roomMember.user.id, type: 'standard'}).sort('createdAt ASC').limit(1),
				Message.find({author: roomMember.user.id, type: 'standard'}).skip(_.random(0, messageCount)).limit(1),
				getEmoticonCounts(roomMember),
				getHangmanStats(roomMember.user.id),
				getFightStatistics(roomMember.user.id)
			)
				.spread(function (template, messageCount, editCount, activeDays, firstMessage, randomMessage, emoticonCounts, hangmanStats, fightStats) {
					firstMessage = firstMessage[0];
					randomMessage = randomMessage[0];

					var dateFormat = 'dddd MMMM Do, YYYY';
					var dateTimeFormat = 'dddd MMMM Do, YYYY @ h:mm:ssa';

					var averageCountPerDay = 0;

					if (messageCount && activeDays.length) {
						averageCountPerDay = messageCount / activeDays.length;
					}

					var data = {
						user: roomMember.user.nick,
						messageCount: messageCount,
						averageMessageCountPerDay: ~~averageCountPerDay,
						editCount: editCount,
						startDate: moment(roomMember.user.createdAt).format(dateFormat),
						totalDays: moment().diff(roomMember.user.createdAt, 'days'),
						activeDays: activeDays.length,
						firstMessage: firstMessage ? '"' + ent.decode(firstMessage.text) + '" (' + moment(firstMessage.createdAt).format(dateTimeFormat) + ')' : '',
						emotes: ent.decode(_.pluck(emoticonCounts, 'emoticon').join(' ')),
						randomMessage: randomMessage ? '"' + ent.decode(randomMessage.text) + '" (' + moment(randomMessage.createdAt).format(dateTimeFormat) + ')' : '',
						hangmanGuessCount: hangmanStats.count,
						hangmanGuessAccuracy: hangmanStats.guessAccuracy ? hangmanStats.guessAccuracy + '%': 'N/A',
						hangmanPrivateWinLoss: (hangmanStats.privateWins ? hangmanStats.privateWins : 0) + ' - ' + (hangmanStats.privateLosses ? hangmanStats.privateLosses : 0),
						hangmanPublicWinLoss: hangmanStats.publicWins + ' - ' + hangmanStats.publicLosses,
						fightWinPercentage: fightStats.fightWinPercentage,
						fightRoundWinPercentage: fightStats.fightRoundWinPercentage,
						fightVictim1: fightStats.topVictims && fightStats.topVictims[0] ? fightStats.topVictims[0].userNick + " " + fightStats.topVictims[0].beatings : '',
						fightVictim2: fightStats.topVictims && fightStats.topVictims[1] ? fightStats.topVictims[1].userNick + " " + fightStats.topVictims[1].beatings : '',
						fightVictim3: fightStats.topVictims && fightStats.topVictims[2] ? fightStats.topVictims[2].userNick + " " + fightStats.topVictims[2].beatings : '',
						fightVictim4: fightStats.topVictims && fightStats.topVictims[3] ? fightStats.topVictims[3].userNick + " " + fightStats.topVictims[3].beatings : '',
						fightVictim5: fightStats.topVictims && fightStats.topVictims[4] ? fightStats.topVictims[4].userNick + " " + fightStats.topVictims[4].beatings : ''
					};

					if (activeDays) {
						var mostActive = makeActiveModel(activeDays);

						var activeDaysSorted = _(activeDays)
							.sortByAll([
								function (day) {
									return day._id.year;
								},
								function (day) {
									return day._id.dayOfYear;
								}
							])
							.reverse()
							.value();

						var lastActive = makeActiveModel(activeDaysSorted);

						data.activeDate = mostActive.day.format(dateFormat) + ' (' + mostActive.object.count + ' messages)';
						data.lastActiveDate = lastActive.day.format(dateFormat) + ' (' + lastActive.object.count + ' messages)';
					}

					return ent.encode(_.template(template)(data));
				});
		});
}

function makeActiveModel(activeDays) {
	if (!activeDays) return;

	var mostActiveDayObject = _.first(activeDays);
	var mostActiveYear = mostActiveDayObject._id.year;
	var mostActiveDayOfYear = mostActiveDayObject._id.dayOfYear;
	var mostActiveDay = moment().year(mostActiveYear).dayOfYear(mostActiveDayOfYear);

	return {
		object: mostActiveDayObject,
		year: mostActiveYear,
		dayOfYear: mostActiveDayOfYear,
		day: mostActiveDay
	}
}

function getActiveDays(roomMember) {
	return new Promise(function (resolve, reject) {
		Message.native(function (err, messageCollection) {
			if (err) return reject(err);
			messageCollection.aggregate([
				{
					$match: {
						author: new ObjectId(roomMember.user.id)
					}
				},
				{
					$group: {
						_id: {
							year: {$year: '$createdAt'},
							dayOfYear: {$dayOfYear: '$createdAt'}
						},
						count: {$sum: 1}
					}
				},
				{
					$sort: {count: -1}
				}
			], function (err, daysWithMessages) {
				if (err) return reject(err);
				resolve(daysWithMessages);
			});
		});
	});
}

function getEmoticonCounts(roomMember) {
	var emoticonRegex = /:\w+:/g;
	var countMap = {};

	return new Promise(function (resolve, reject) {
		Message.native(function (err, messageCollection) {
			if (err) return reject(err);

			messageCollection.find({author: new ObjectId(roomMember.user.id), text: {$regex: emoticonRegex}})
				.toArray(function (err, messages) {
					if (err) return reject(err);
					_.each(messages, function (message) {

						var matches = _.unique(message.text.match(emoticonRegex));
						if (matches) {
							_.each(matches, function (match) {
								countMap[match] = countMap[match] ? countMap[match] + 1 : 1;
							});
						}
					});

					var counts = _(countMap)
						.map(function (value, key) {
							return {count: value, emoticon: key, name: key.replace(/:/g, '')};
						})
						.sortBy('count')
						.reverse()
						.take(10)
						.value();

					resolve(counts);
				});
		});
	});
}

function getHangmanStats(userId) {
	return Promise.join(
		getHangmanUserStatistics(userId),
		getHangmanPublicStatistics()
	)
	.spread(function (userStats, publicStats) {
		stats = {};
		stats.count = userStats.guessMisses + userStats.guessHits;

		if (!stats.count || !userStats.guessHits) {
			stats.guessAccuracy = 0;
		}
		else {
			stats.guessAccuracy = Math.round((userStats.guessHits / stats.count) * 100);
		}

		stats.publicWins = publicStats.winCount;
		stats.publicLosses = publicStats.lossCount;

		stats.privateWins = userStats.privateGameWinCount;
		stats.privateLosses = userStats.privateGameLossCount;

		return stats;
	});
}

function getHangmanUserStatistics(userId) {
	return HangmanUserStatistics.findOne({user: userId}).then(function (hangmanUserStatistics) {
		if (hangmanUserStatistics) return hangmanUserStatistics;

		return HangmanUserStatistics.create({
			user: userId
		});
	});
}

function getHangmanPublicStatistics() {
	return HangmanPublicGameStatistics.find().then(function (publicStats) {
		if (publicStats && publicStats[0]) return publicStats[0];

		return HangmanPublicGameStatistics.create();
	});
}

function getFightStatistics(userId) {
	return new Promise(function (resolve, reject) {
		Fight.native(function (err, fightCollection) {
			if (err) return reject(err);
			fightCollection.aggregate(
				[
					{ $match: { $or: [ { opponent: ObjectId(userId) },{ challenger: ObjectId(userId) } ] } },
					{
						$project:
						{
							winningUser: '$winningUser',
							losingUser:
							{
								$cond: [
									{ $ne: [ '$winningUser', null ] },
									{ $cond: [ { $ne: [ '$winningUser', '$challenger' ] }, '$challenger', '$opponent' ] },
									null
								]
							},
							opponentUser: '$opponent',
							challengerUser: '$challenger',
						}
					}
				], function (err, fightData) {
					if (err) return reject(err);
					resolve(fightData);
				});
		});
	}).then(function(fightData) {

			var fightResults = {};
			var victims = {};
			var userIds = [];
			if (fightData) {
				var userStats = {};
				userStats.wins = 0;
				userStats.losses = 0;
				userStats.ties = 0;
				userStats.totalGames = 0;

				var fightIds = [];

				_.forEach(fightData, function (fightDataElement) {

					fightIds.push(fightDataElement._id);
					if (fightDataElement.winningUser) {
						if (fightDataElement.winningUser == userId) {
							userStats.wins++;

							if (fightDataElement.opponentUser == userId) {
								if (!victims[fightDataElement.challengerUser]) {
									victims[fightDataElement.challengerUser] = {};
									victims[fightDataElement.challengerUser].beatings = 0;
									victims[fightDataElement.challengerUser].id = fightDataElement.challengerUser;
								}

								victims[fightDataElement.challengerUser].beatings++;
								userIds.push(fightDataElement.challengerUser);
							}
							else {
								if (!victims[fightDataElement.opponentUser]) {
									victims[fightDataElement.opponentUser] = {};
									victims[fightDataElement.opponentUser].beatings = 0;
									victims[fightDataElement.opponentUser].id = fightDataElement.opponentUser;
								}

								victims[fightDataElement.opponentUser].beatings++;
								userIds.push(fightDataElement.opponentUser);
							}
						}
						else {
							userStats.losses++;
						}

						userStats.totalGames++;
					}
					else {
						userStats.ties++;
						userStats.totalGames++;
					}

					userStats.winPercentage = userStats.wins > 0 ?
						Math.round(((userStats.wins + (userStats.ties * 0.5)) / userStats.totalGames) * 100) : 0;
				});

				fightResults.fightWinPercentage = userStats.winPercentage + '% (' + userStats.wins + '-' + userStats.losses + '-' + userStats.ties + ')';
				fightResults.topVictims = _.take(_.sortByOrder(victims, ['beatings'], [false]), 5);

				userIds = _.unique(userIds);

				return User.find({ "_id": { $in : userIds } }).then(function(users) {

					_.forEach(fightResults.topVictims, function (victim) {
						var userIndex = _.findIndex(users, function (user) {
							return user.id == victim.id;
						});

						victim.userNick = users[userIndex].nick;
					});

					return FightRound.find( { fight: { $in: fightIds } } ).then(function (fightRounds) {
						var fightRoundStats = {};
						fightRoundStats.wins = 0;
						fightRoundStats.losses = 0;
						fightRoundStats.ties = 0;
						fightRoundStats.totalRounds = 0;

						_.forEach(fightRounds, function(fightRound) {
							if (fightRound.winningUser) {
								if (fightRound.winningUser == userId) {
									fightRoundStats.wins++;
								}
								else {
									fightRoundStats.losses++;
								}
							}
							else {
								fightRoundStats.ties++;
							}

							fightRoundStats.totalRounds++;

							fightRoundStats.winPercentage = fightRoundStats.wins > 0 ?
								Math.round(((fightRoundStats.wins + (fightRoundStats.ties * 0.5)) / fightRoundStats.totalRounds) * 100) : 0;
						});

						fightResults.fightRoundWinPercentage = fightRoundStats.winPercentage + '% (' + fightRoundStats.wins + '-' + fightRoundStats.losses + '-' + fightRoundStats.ties + ')';

						return fightResults;
					});
				});
			}

			fightResults.fightWinPercentage = '0% (0-0-0)';
			fightResults.victims = null;
			fightResults.fightRoundWinPercentage = '0% (0-0-0)';

			return fightResults;
		});
}
<|endoftext|>


<commit_before>const jwt = require('jsonwebtoken')
const userModel = require('../../models/users')
const { buildResponse } = require('../../utils/responseService')

module.exports = (req, res) => {
  userModel.findOne(req.body.username)
    .then(user => {
      if(user == null) return buildResponse(res, 401, { message: "user not found" })

      if (userModel.comparePasswords(req.body.password, user.password) === true) {
        const payload = { username: user.username }
        const token = jwt.sign(payload, process.env.JWT_SECRET)
        buildResponse(res, 200, { message: 'success', token })
      } else {
        buildResponse(res, 401, { message:"passwords did not match" })
      }
    })

    .catch(err => {
      buildResponse(res, 500, { message:"Internal server error" })
    })
}
<commit_msg>Write the lgin controller to pass the tests<commit_after>const jwt = require('jsonwebtoken')
const userModel = require('../../models/users')
const { buildResponse } = require('../../utils/responseService')
const validator = require('./loginValidation')

module.exports = (req, res) => {
    const {error, value} = validator.validate(req.body)
    if (error) return buildResponse(res, 400, { message: 'bad request', error: error.details[0].message})

    userModel.login(value.username, value.password)
      .then(token => buildResponse(res, 200, { message: 'success', token }))
      .catch(error => {
        if (error && error.status === 401) return buildResponse(res, 401, { message: 'invalid login details' })
        buildResponse(res, 500, { message: 'Internal server error'})
      })
}
<|endoftext|>


<commit_before>var _ = require('lodash')
var React = require('react')

var AreaOfStudy = require('./areaOfStudy')
var StudentSummary = require('./studentSummary')

var asianStudies = require('../../mockups/demo_major')
var getCourses = require('../helpers/getCourses').getCourses

var AsianStudies = React.createClass({
	load: function() {
		asianStudies(this.props).bind(this).then(function(result) {
			console.log('calculated asian studies graduation possibility', result)
			this.setState({
				result: JSON.stringify(result, undefined, 2)
			})
		})
	},
	getInitialState: function() {
		return {
			status: {}
		}
	},
	componentWillReceiveProps: function() {
		this.load()
	},
	componentDidMount: function() {
		this.load()
	},
	render: function() {
		return React.DOM.article(
			{className: 'area-of-study'},
			React.DOM.pre(null, this.state.result)
		)
	}
})

var GraduationStatus = React.createClass({
	putActiveCoursesIntoState: function() {
		var clbids = _.chain(this.props.schedules.val())
			.filter('active')
			.pluck('clbids')
			.flatten()
			.uniq()
			.value()

		var self = this
		getCourses(clbids).then(function(courses) {
			console.log('retrieved ' + courses.length + ' courses for graduation-status')
			self.setState({
				courses: courses
			})
		})
	},
	getInitialState: function() {
		return {
			courses: []
		}
	},
	componentWillReceiveProps: function() {
		this.putActiveCoursesIntoState()
	},
	componentDidMount: function() {
		this.putActiveCoursesIntoState()
	},
	render: function() {
		console.log('graduation-status render')

		// Get areas of study
		var areasOfStudy = _.groupBy(this.props.studies.val(), 'type')
		areasOfStudy = _.mapValues(areasOfStudy, function(areas) {
			return _.map(areas, function(area) {
				area.key = area.id
				area.courses = this.state.courses
				return AreaOfStudy(area)
			}, this)
		}, this)

		return React.DOM.section({className: 'graduation-status'},
			StudentSummary(_.merge(this.props, {courses: this.state.courses})),
			React.DOM.section({id: 'degrees'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Degrees'),
					React.DOM.button({className: 'add-area-of-study', title: 'Add Degree'})
				),
				areasOfStudy.degree
			),
			React.DOM.section({id: 'majors'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Majors'),
					React.DOM.button({className: 'add-area-of-study', title: 'Add Major'})
				),
				areasOfStudy.major
			),
			React.DOM.section({id: 'concentrations'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Concentrations'),
					React.DOM.button({className: 'add-area-of-study', title: 'Add Concentration'})
				),
				areasOfStudy.concentration
			),
			React.DOM.section({id: 'asian'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Asian Studies')
				),
				AsianStudies(_.merge(this.props, {courses: this.state.courses}))
			)
		)
	}
});

module.exports = GraduationStatus
<commit_msg>Render ComputerScience<commit_after>var _ = require('lodash')
var React = require('react')

var AreaOfStudy = require('./areaOfStudy')
var StudentSummary = require('./studentSummary')

var asianStudies = require('../../mockups/demo_major')
var computerScience = require('../../mockups/demo_csci_major')
var getCourses = require('../helpers/getCourses').getCourses

var AsianStudies = React.createClass({
	load: function() {
		asianStudies(this.props).bind(this).then(function(result) {
			console.log('calculated asian studies graduation possibility', result)
			this.setState({
				result: JSON.stringify(result, undefined, 2)
			})
		})
	},
	getInitialState: function() {
		return {
			status: {}
		}
	},
	componentWillReceiveProps: function() {
		this.load()
	},
	componentDidMount: function() {
		this.load()
	},
	render: function() {
		return React.DOM.article(
			{className: 'area-of-study'},
			React.DOM.pre(null, this.state.result)
		)
	}
})

var ComputerScience = React.createClass({
	load: function() {
		computerScience(this.props).bind(this).then(function(result) {
			console.log('calculated comp sci graduation possibility', result)
			this.setState({
				result: JSON.stringify(result, undefined, 2)
			})
		})
	},
	getInitialState: function() {
		return {
			status: {}
		}
	},
	componentWillReceiveProps: function() {
		this.load()
	},
	componentDidMount: function() {
		this.load()
	},
	render: function() {
		return React.DOM.article(
			{className: 'area-of-study'},
			React.DOM.pre(null, this.state.result)
		)
	}
})

var GraduationStatus = React.createClass({
	putActiveCoursesIntoState: function() {
		var clbids = _.chain(this.props.schedules.val())
			.filter('active')
			.pluck('clbids')
			.flatten()
			.uniq()
			.value()

		var self = this
		getCourses(clbids).then(function(courses) {
			console.log('retrieved ' + courses.length + ' courses for graduation-status')
			self.setState({
				courses: courses
			})
		})
	},
	getInitialState: function() {
		return {
			courses: []
		}
	},
	componentWillReceiveProps: function() {
		this.putActiveCoursesIntoState()
	},
	componentDidMount: function() {
		this.putActiveCoursesIntoState()
	},
	render: function() {
		console.log('graduation-status render')

		// Get areas of study
		var areasOfStudy = _.groupBy(this.props.studies.val(), 'type')
		areasOfStudy = _.mapValues(areasOfStudy, function(areas) {
			return _.map(areas, function(area) {
				area.key = area.id
				area.courses = this.state.courses
				return AreaOfStudy(area)
			}, this)
		}, this)

		return React.DOM.section({className: 'graduation-status'},
			StudentSummary(_.merge(this.props, {courses: this.state.courses})),
			React.DOM.section({id: 'degrees'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Degrees'),
					React.DOM.button({className: 'add-area-of-study', title: 'Add Degree'})
				),
				areasOfStudy.degree
			),
			React.DOM.section({id: 'majors'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Majors'),
					React.DOM.button({className: 'add-area-of-study', title: 'Add Major'})
				),
				areasOfStudy.major
			),
			React.DOM.section({id: 'concentrations'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Concentrations'),
					React.DOM.button({className: 'add-area-of-study', title: 'Add Concentration'})
				),
				areasOfStudy.concentration
			),
			React.DOM.section({id: 'asian'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Asian Studies')
				),
				AsianStudies(_.merge(this.props, {courses: this.state.courses}))
			),
			React.DOM.section({id: 'csci'},
				React.DOM.header({className: 'area-type-heading'},
					React.DOM.h1(null, 'Computer Science')
				),
				ComputerScience(_.merge(this.props, {courses: this.state.courses}))
			)
		)
	}
});

module.exports = GraduationStatus
<|endoftext|>


<commit_before>import Ember from 'ember';
import OutputComponent from '../../mixins/component-output';

const { Component, computed } = Ember;

export default Component.extend(OutputComponent, {
  type: 'phone',
  hideButtons: false
});
<commit_msg>little refactor<commit_after>import Ember from 'ember';
import OutputComponent from '../../mixins/component-output';

const { Component, computed } = Ember;

export default Component.extend(OutputComponent, {
  type: 'phone'
});
<|endoftext|>


<commit_before>/*jshint node:true */
/* vim: set ts=2 sw=2: */
/*
 * grunt-patternlibrary
 * https://github.com/sambowler/grunt-patternlibrary
 *
 * Copyright (c) 2014 Sam Bowler
 * Licensed under the MIT license.
 */
module.exports = function(grunt) {
  'use strict';

  var _ = require('lodash');
  var chalk = require('chalk');
  var path = require('path');
  var pluginRoot = path.join(__dirname, '../');
  var pattern = require('./lib/pattern')(grunt);
  var includes = require('./lib/includes')(grunt);
  var processData = require('./lib/processData')(grunt);
  var defaults = {};

  defaults.wrapperTemplate = pluginRoot + '/templates/wrapper.html';
  defaults.patternTemplate = pluginRoot + '/templates/pattern.html';
  defaults.indexTemplate = pluginRoot + '/templates/index.html';
  defaults.headerTemplate = pluginRoot + '/templates/header.html';
  defaults.indexName = 'index.html';
  defaults.title = 'Pattern Library';
  defaults.stylesheets = [
    'css/prism.css',
    'css/style.css'
  ];
  defaults.javascripts = [
    'js/jquery.min.js',
    'js/prism.js',
    'js/path.min.js',
    'js/main.js'
  ];
  defaults.include = [
    {
      src: pluginRoot + '/css',
      dest: 'css'
    },
    {
      src: pluginRoot + '/js',
      dest: 'js'
    }
  ];

  grunt.registerMultiTask('patternlibrary', 'Create a pattern library with a set of HTML files.', function() {
    var options = this.options(defaults);

    this.files.forEach(function(f) {
      if(typeof f.dest === 'undefined') {
        grunt.fail.warn('No destination specified, use the "dest" paramater.');
      }
      var indexName = f.indexName || options.indexName;
      var wrapperTemplate = f.wrapperTemplate || options.wrapperTemplate;
      var patternTemplate = f.patternTemplate || options.patternTemplate;
      var headerTemplate = f.headerTemplate || options.headerTemplate;
      var indexTemplate = f.indexTemplate || options.indexTemplate;
      var stylesheets = f.stylesheets ? options.stylesheets.concat(f.stylesheets) : options.stylesheets;
      var javascripts = f.javascripts ? options.javascripts.concat(f.javascripts) : options.javascripts;

      /**
       *  @type {object} patterns
       *  @example
       *  {
       *      categoryName: {
       *          category: true,
       *          patterns: [{},{}]
       *      },
       *      patternName: {}
       *  }
       */
      var patterns = {};
      var templateData = {
        title: f.title || options.title
      };

      f.src.filter(function(filepath) {
        if (!grunt.file.exists(filepath)) {
          grunt.log.warn('Source file "' + filepath + '" not found.');
          return false;
        } else {
          return true;
        }
      }).map(function(path) {
        var patternData = pattern.processPatternData( path );
        if( typeof patternData.category !== 'undefined' ){
          if( typeof patterns[ patternData.category ] !== 'undefined' ) {
            patterns[ patternData.category ].patterns.push( patternData );
          } else {
            patterns[ patternData.category ] = { title: patternData.category, category: true, patterns: [ patternData ] };
          }
        } else {
          patterns[ patternData.title ] = patternData;
        }
        return patternData;
      }).map( function( data ){
        var markup;
        if( typeof templateData.patterns === 'undefined' ) templateData.patterns = patterns;
        if( typeof templateData.header === 'undefined' ) templateData.header = processData.getMarkup( headerTemplate, templateData );

        // if( typeof data.template !== 'undefined' ){
        //   data.content = processData.getMarkup( pluginRoot + '/src/' + data.template, templateData );
        // }

        data = _.defaults( { content: data.content, slug: data.slug }, templateData );
        markup = processData.getMarkup( patternTemplate, data  );

        data.content = markup;
        data.stylesheets = stylesheets;
        data.javascripts = javascripts;
        markup = processData.getMarkup( wrapperTemplate, _.defaults( data, templateData ) );

        grunt.file.write(f.dest + '/patterns/' + data.slug + '.html', markup);
        grunt.log.writeln(chalk.green('>>') + ' Pattern HTML created at "' + f.dest + '/patterns/' + data.slug + '.html');
      });

      var indexContent = processData.getMarkup( indexTemplate, templateData );
      templateData.stylesheets = stylesheets;
      templateData.javascripts = javascripts;
      var html = processData.getMarkup(wrapperTemplate, _.defaults( {content: indexContent}, templateData ) );

      grunt.file.write(f.dest + '/' + indexName, html);
      grunt.log.writeln(chalk.green('>>') + ' Patterns HTML created at "' + f.dest + '/' + indexName + '".');

      grunt.file.write(f.dest + '/patterns.json', processData.getJSON(patterns));
      grunt.log.writeln(chalk.green('>>') + ' Patterns JSON created at "' + f.dest + '/patterns.json".');

      var includesArr = includes.processArray(options.include, f.include, f.blankCanvas );

      includes.processIncludes(f.dest, includesArr);
    });
  });
};
<commit_msg>ability to add pattern specific templates via the template option in frontmatter<commit_after>/*jshint node:true */
/* vim: set ts=2 sw=2: */
/*
 * grunt-patternlibrary
 * https://github.com/sambowler/grunt-patternlibrary
 *
 * Copyright (c) 2014 Sam Bowler
 * Licensed under the MIT license.
 */
module.exports = function(grunt) {
  'use strict';

  var _ = require('lodash');
  var chalk = require('chalk');
  var path = require('path');
  var pluginRoot = path.join(__dirname, '../');
  var pattern = require('./lib/pattern')(grunt);
  var includes = require('./lib/includes')(grunt);
  var processData = require('./lib/processData')(grunt);
  var defaults = {};

  defaults.wrapperTemplate = pluginRoot + '/templates/wrapper.html';
  defaults.patternTemplate = pluginRoot + '/templates/pattern.html';
  defaults.indexTemplate = pluginRoot + '/templates/index.html';
  defaults.headerTemplate = pluginRoot + '/templates/header.html';
  defaults.indexName = 'index.html';
  defaults.title = 'Pattern Library';
  defaults.stylesheets = [
    'css/prism.css',
    'css/style.css'
  ];
  defaults.javascripts = [
    'js/jquery.min.js',
    'js/prism.js',
    'js/path.min.js',
    'js/main.js'
  ];
  defaults.include = [
    {
      src: pluginRoot + '/css',
      dest: 'css'
    },
    {
      src: pluginRoot + '/js',
      dest: 'js'
    }
  ];

  grunt.registerMultiTask('patternlibrary', 'Create a pattern library with a set of HTML files.', function() {
    var options = this.options(defaults);

    this.files.forEach(function(f) {
      if(typeof f.dest === 'undefined') {
        grunt.fail.warn('No destination specified, use the "dest" paramater.');
      }
      var indexName = f.indexName || options.indexName;
      var wrapperTemplate = f.wrapperTemplate || options.wrapperTemplate;
      var patternTemplate = f.patternTemplate || options.patternTemplate;
      var headerTemplate = f.headerTemplate || options.headerTemplate;
      var indexTemplate = f.indexTemplate || options.indexTemplate;
      var stylesheets = f.stylesheets ? options.stylesheets.concat(f.stylesheets) : options.stylesheets;
      var javascripts = f.javascripts ? options.javascripts.concat(f.javascripts) : options.javascripts;

      /**
       *  @type {object} patterns
       *  @example
       *  {
       *      categoryName: {
       *          category: true,
       *          patterns: [{},{}]
       *      },
       *      patternName: {}
       *  }
       */
      var patterns = {};
      var templateData = {
        title: f.title || options.title
      };

      f.src.filter(function(filepath) {
        if (!grunt.file.exists(filepath)) {
          grunt.log.warn('Source file "' + filepath + '" not found.');
          return false;
        } else {
          return true;
        }
      }).map(function(path) {
        var patternData = pattern.processPatternData( path );
        if( typeof patternData.category !== 'undefined' ){
          if( typeof patterns[ patternData.category ] !== 'undefined' ) {
            patterns[ patternData.category ].patterns.push( patternData );
          } else {
            patterns[ patternData.category ] = { title: patternData.category, category: true, patterns: [ patternData ] };
          }
        } else {
          patterns[ patternData.title ] = patternData;
        }
        return patternData;
      }).map( function( data ){
        var markup;
        if( typeof templateData.patterns === 'undefined' ) templateData.patterns = patterns;
        if( typeof templateData.header === 'undefined' ) templateData.header = processData.getMarkup( headerTemplate, templateData );

        // if( typeof data.template !== 'undefined' ){
        //   data.content = processData.getMarkup( pluginRoot + '/src/' + data.template, templateData );
        // }

        data = _.defaults( { content: data.content, slug: data.slug, template: data.template }, templateData );
        markup = processData.getMarkup( patternTemplate, data  );

        if( typeof data.template !== 'undefined' ){
          markup = processData.getMarkup( 'patterns/templates/' + data.template + '.html', _.defaults( { content: markup }, data ) );
        }

        data.content = markup;
        data.stylesheets = stylesheets;
        data.javascripts = javascripts;
        markup = processData.getMarkup( wrapperTemplate, _.defaults( data, templateData ) );

        grunt.file.write(f.dest + '/patterns/' + data.slug + '.html', markup);
        grunt.log.writeln(chalk.green('>>') + ' Pattern HTML created at "' + f.dest + '/patterns/' + data.slug + '.html');
      });

      var indexContent = processData.getMarkup( indexTemplate, templateData );
      templateData.stylesheets = stylesheets;
      templateData.javascripts = javascripts;
      var html = processData.getMarkup(wrapperTemplate, _.defaults( {content: indexContent}, templateData ) );

      grunt.file.write(f.dest + '/' + indexName, html);
      grunt.log.writeln(chalk.green('>>') + ' Patterns HTML created at "' + f.dest + '/' + indexName + '".');

      grunt.file.write(f.dest + '/patterns.json', processData.getJSON(patterns));
      grunt.log.writeln(chalk.green('>>') + ' Patterns JSON created at "' + f.dest + '/patterns.json".');

      var includesArr = includes.processArray(options.include, f.include, f.blankCanvas );

      includes.processIncludes(f.dest, includesArr);
    });
  });
};
<|endoftext|>