<commit_before>/**
 * Provides the prototypes for dynamically creating survey questions.
 */

/**
 * Represents a survey question.
 * @constructor
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 */
function Question(questionType, question, required) {
  this.questionType = questionType;
  this.question = question;
  this.required = required;
  this.userResponse = "";
}

/**
 * Stores the answer chosen by the user. Should be used after the respondent
 * has submitted the survey form.
 * @param {string} The text of the user's answer.
 */
Question.prototype.setUserResponse = function(response) {
  this.userResponse = response;
};

/**
 * Creates the DOM representation of a question. This should be implemented
 * by any subclasses!
 * @return {Object} The DOM node that contains the question.
 */
Question.prototype.makeDOMTree = function() {
  return document.createElement('div');
};

// FIXED

/**
 * Represents questions with a fixed set of non-scale responses. Currently
 * supports CHECKBOX, RADIO, and DROPDOWN question types. If randomized, the
 * full list will be ordered pseudorandomly.
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 * @param {Array.string} answers The list of answers to be shown.
 * @param {String} randomize Type of randomization (constants.Randomize).
 */
function FixedQuestion(
    questionType, question, required, answers, randomize) {
  Question.call(this, questionType, question, required);
  this.answers = answers;
  this.randomize = randomize;
}

FixedQuestion.prototype = Object.create(Question.prototype);
FixedQuestion.prototype.constructor = FixedQuestion;

/**
 * Creates the DOM representation of a FixedQuestion question.
 * @return {Object} The DOM node that contains the question.
 */
FixedQuestion.prototype.makeDOMTree = function() {
  var container = document.createElement('div');
  container.classList.add('fieldset');

  var legend = document.createElement('legend');
  legend.textContent = this.question;
  container.appendChild(legend);

  var shrunkenQuestion = getDomNameFromValue(this.question);
  switch (this.questionType) {
    case constants.QuestionType.CHECKBOX:
    case constants.QuestionType.RADIO:
      var answerChoices = [];
      for (var i = 0; i < this.answers.length; i++) {
        var answer = document.createElement('div');
        var shrunkenAnswer = i + '-' + getDomNameFromValue(this.answers[i]);
        var input = document.createElement('input');
        input.setAttribute('id', shrunkenAnswer);
        input.setAttribute('name', shrunkenQuestion);
        input.setAttribute('type', this.questionType);
        input.setAttribute('value', shrunkenAnswer);
        input.setAttribute('required', this.required);
        answer.appendChild(input);

        var label = document.createElement('label');
        label.setAttribute('for', shrunkenAnswer);
        label.textContent = this.answers[i];
        answer.appendChild(label);

        if (this.answers[i] == constants.OTHER) {
          var textInput = document.createElement('input');
          textInput.setAttribute('type', 'text');
          textInput.setAttribute('name', shrunkenAnswer);
          answer.appendChild(textInput);
        }
        answerChoices.push(answer);
      }
      if (this.randomize != constants.Randomize.NONE)
        answerChoices = knuthShuffle(answerChoices, this.randomize);
      for (var i = 0; i < answerChoices.length; i++)
        container.appendChild(answerChoices[i]);
      break;
    case constants.QuestionType.DROPDOWN:
      var select = document.createElement('select');
      var answerChoices = [];
      for (var i = 0; i < this.answers.length; i++) {
        var option = document.createElement('option');
        option.value = i + '-' + getDomNameFromValue(this.answers[i]);
        option.textContent = this.answers[i];
        answerChoices.push(option);
      }
      if (this.randomize != constants.Randomize.NONE)
        answerChoices = knuthShuffle(answerChoices, this.randomize);
      for (var i = 0; i < answerChoices.length; i++)
        select.appendChild(answerChoices[i]);
      container.appendChild(select);
      break;
    default:
      throw new Error('Question "' + this.question + '" has an unexpected ' +
        'question type: ' + this.questionType);
  }
  return container;
}

// SCALE

/**
 * Represents scale questions. Currently supports HORIZONTAL_SCALE and
 * VERTICAL_SCALE question types. Randomization simply flips the order to
 * preserve the scale structure.
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 * @param {Array.string} scale The labels for each point on the scale.
 * @param {String} randomize Type of randomization (constants.Randomize).
 */
function ScaleQuestion(questionType, question, required, scale, randomize) {
  Question.call(this, questionType, question, required);
  this.scale = scale;
  this.randomize = randomize;
  this.attributes = [];
}

ScaleQuestion.prototype = Object.create(Question.prototype);
ScaleQuestion.prototype.constructor = ScaleQuestion;

/**
 * For multiple horizontal scales, like this:
 *    How are you feeling?
 *    - Sad 1 2 3 4 5
 *    - Happy 1 2 3 4 5
 * Here the adjectives "Sad" and "Happy" are attributes. This must be set
 * before makeDOMTree().
 * @param {Array.string} attributes The attributes that the user is rating.
 */
ScaleQuestion.prototype.setAttributes = function(attributes) {
  if (this.questionType != constants.QuestionType.MULT_HORIZ_SCALE)
    throw new Error('Only set attributes for MULT_HORIZ_SCALE questions.');
  this.attributes = attributes;
};

/**
 * A helper method for makeDOMTree.
 * @param {boolean} horizontal Whether it is a horizontal scale.
 * @param {string} questionName The DOM-ready question name.
 * @param {boolean} reverse Whether to flip the array.
 * @param {boolean} showLabels If horizontal, whether to show the labels.
 * @private
 */
ScaleQuestion.prototype.makeSingleRow =
    function(horizontal, questionName, reverse, showLabels) {
  var container = document.createElement('div');
  var scaleElements = [];
  for (var i = 0; i < this.scale.length; i++) {
    var answer = document.createElement('div');
    if (horizontal)
      answer.classList.add('horizontal-scale');
    var shrunkenAnswer = i + '-' + getDomNameFromValue(this.scale[i]);

    var radio = document.createElement('input');
    radio.setAttribute('id', shrunkenAnswer);
    radio.setAttribute('name', questionName);
    radio.setAttribute('type', 'radio');
    radio.setAttribute('value', shrunkenAnswer);
    if (this.required)
      radio.setAttribute('required', this.required);

    var label = document.createElement('label');
    label.setAttribute('for', shrunkenAnswer);
    label.textContent = this.scale[i];

    if (horizontal) {
      if (showLabels) {
        answer.appendChild(label);
        answer.appendChild(document.createElement('br'));
      }
      answer.appendChild(radio);
    } else {
      answer.appendChild(radio);
      answer.appendChild(label);
    }
    scaleElements.push(answer);
  }
  if (reverse)
    scaleElements = flipArray(scaleElements, this.randomize);
  for (var i = 0; i < scaleElements.length; i++)
    container.appendChild(scaleElements[i]);

  if (horizontal) {
    var clearDiv = document.createElement('div');
    clearDiv.classList.add('clear-div');
    container.appendChild(clearDiv);
  }
  return container;
};

/**
 * Creates the DOM representation of a ScaleQuestion.
 * @return {Object} The DOM node that contains the question.
 */
ScaleQuestion.prototype.makeDOMTree = function() {
  var horizontal =
      this.questionType == constants.QuestionType.HORIZ_SCALE ||
      this.questionType == constants.QuestionType.MULT_HORIZ_SCALE;
  var multi =
      (this.questionType == constants.QuestionType.MULT_HORIZ_SCALE) &&
      this.attributes.length > 0;
  var container = document.createElement('div');
  container.classList.add('fieldset');

  var legend = document.createElement('legend');
  legend.textContent = this.question;
  container.appendChild(legend);

  var reverse = this.randomize == constants.Randomize.NONE ? false : coinToss();
  var shrunkenQuestion = getDomNameFromValue(this.question);
  if (multi) {
    var shuffledAttributes =
        knuthShuffle(this.attributes, constants.Randomize.ALL);
    for (var i = 0; i < shuffledAttributes.length; i++) {
      var floatScale = document.createElement('div');
      floatScale.classList.add('horizontal-rowlabel');
      if (i == 0)
        floatScale.classList.add('first-rowlabel');
      floatScale.innerText = shuffledAttributes[i] + ':';
      container.appendChild(floatScale);
      var questionName =
          getDomNameFromValue(shuffledAttributes[i]) + shrunkenQuestion;
      container.appendChild(
          this.makeSingleRow(horizontal, questionName, reverse, (i == 0)));
    }
  } else {
    container.appendChild(
        this.makeSingleRow(horizontal, shrunkenQuestion, reverse, true));
  }
  return container;
}

// ESSAY

/**
 * Represents free response questions. Currently supports SHORT_STRING,
 * SHORT_ESSAY and LONG_ESSAY question types.
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 */
function EssayQuestion(questionType, question, required) {
  Question.call(this, questionType, question, required);
}

EssayQuestion.prototype = Object.create(Question.prototype);
EssayQuestion.prototype.constructor = EssayQuestion;

/**
 * Creates the DOM representation of an EssayQuestion.
 * @return {Object} The DOM node that contains the question.
 */
EssayQuestion.prototype.makeDOMTree = function() {
  var container = document.createElement('div');
  container.classList.add('fieldset');

  var legend = document.createElement('legend');
  legend.textContent = this.question;
  container.appendChild(legend);

  switch (this.questionType) {
    case constants.QuestionType.SHORT_STRING:
      var input = document.createElement('input');
      input.setAttribute('type', 'text');
      input.setAttribute('name', getDomNameFromValue(this.question));
      input.setAttribute('size', 60);
      if (this.required)
        input.setAttribute('required', this.required);
      container.appendChild(input);
      break;
    case constants.QuestionType.SHORT_ESSAY:
    case constants.QuestionType.LONG_ESSAY:
      var textarea = document.createElement('textarea');
      textarea.setAttribute('name', getDomNameFromValue(this.question));
      textarea.setAttribute('cols', 60);
      textarea.setAttribute(
          'rows',
          this.questionType == constants.QuestionType.SHORT_ESSAY ? 5 : 10);
      if (this.required)
        textarea.setAttribute('required', this.required);
      container.appendChild(textarea);
      break;
    default:
      throw new Error('Question "' + this.question + '" has an unexpected ' +
        'question type: ' + this.questionType);
  }
  return container;
}

// HELPER METHODS

/**
 * Turns the full string into a short name for a value property. It does this
 * by removing all non-alphabetic characters and truncating to 40 characters.
 * @param {string} The original input answer.
 * @returns {string} The shrunken version of the answer.
 */
function getDomNameFromValue(answer) {
  return answer.replace(/[\W\s]+/g, '').substring(0, 40);
}

/**
 * Use the knuth (aka Fisher-Yates) shuffle to randomize an array.
 * http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
 * @param {Array.Object} arr The array to randomize.
 * @param {string} randomType The type of randomization (constants.Randomize)
 * @returns {Array.Object} The randomized array.
 */
function knuthShuffle(arr, randomType) {
  var end = randomType == constants.Randomize.ANCHOR_LAST ?
            arr.length - 1 : arr.length;
  for (var i = 0; i < end; i++) {
    // Random int: i <= randomIndex < arr.length
    var randomIndex = Math.floor(Math.random() * (end - i)) + i;

    // Swap with current element.
    var swapTemp = arr[i];
    arr[i] = arr[randomIndex];
    arr[randomIndex] = swapTemp;
  }
  return arr;
}

/**
 * A wrapper method for Math.random.
 * @returns {boolean} Returns true half the time.
 */
function coinToss() {
  if ((Math.random()) < 0.5)
    return true;
  return false;
}

/**
 * Flip an array. Will anchor the last element for ANCHOR_LAST.
 * @param {Array.Object} arr The array to flip.
 * @param {string} randomType The type of randomization (constants.Randomize)
 * @returns {Array.Object} The flipped array.
 */
function flipArray(arr, randomType) {
  var end = randomType == constants.Randomize.ANCHOR_LAST ?
            arr.length - 1 : arr.length;
  var reverseArr = [];
  for (var i = end - 1; i >= 0; i--)
    reverseArr.push(arr[i]);
  if (randomType == constants.Randomize.ANCHOR_LAST)
    reverseArr.push(arr[arr.length - 1]);
  return reverseArr;
}

/**
 * Creates a submit button for a form.
 * @returns {Object} The DOM node with the submit button.
 */
function makeSubmitButtonDOM() {
  var button = document.createElement('input');
  button.setAttribute('id', 'submit-button');
  button.setAttribute('type', 'submit');
  button.setAttribute('value', 'Submit');

  var fieldset = document.createElement('fieldset');
  fieldset.classList.add('submit');
  fieldset.appendChild(button);
  return fieldset;
}
<commit_msg>Missing semicolon<commit_after>/**
 * Provides the prototypes for dynamically creating survey questions.
 */

/**
 * Represents a survey question.
 * @constructor
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 */
function Question(questionType, question, required) {
  this.questionType = questionType;
  this.question = question;
  this.required = required;
  this.userResponse = "";
}

/**
 * Stores the answer chosen by the user. Should be used after the respondent
 * has submitted the survey form.
 * @param {string} The text of the user's answer.
 */
Question.prototype.setUserResponse = function(response) {
  this.userResponse = response;
};

/**
 * Creates the DOM representation of a question. This should be implemented
 * by any subclasses!
 * @return {Object} The DOM node that contains the question.
 */
Question.prototype.makeDOMTree = function() {
  return document.createElement('div');
};

// FIXED

/**
 * Represents questions with a fixed set of non-scale responses. Currently
 * supports CHECKBOX, RADIO, and DROPDOWN question types. If randomized, the
 * full list will be ordered pseudorandomly.
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 * @param {Array.string} answers The list of answers to be shown.
 * @param {String} randomize Type of randomization (constants.Randomize).
 */
function FixedQuestion(
    questionType, question, required, answers, randomize) {
  Question.call(this, questionType, question, required);
  this.answers = answers;
  this.randomize = randomize;
}

FixedQuestion.prototype = Object.create(Question.prototype);
FixedQuestion.prototype.constructor = FixedQuestion;

/**
 * Creates the DOM representation of a FixedQuestion question.
 * @return {Object} The DOM node that contains the question.
 */
FixedQuestion.prototype.makeDOMTree = function() {
  var container = document.createElement('div');
  container.classList.add('fieldset');

  var legend = document.createElement('legend');
  legend.textContent = this.question;
  container.appendChild(legend);

  var shrunkenQuestion = getDomNameFromValue(this.question);
  switch (this.questionType) {
    case constants.QuestionType.CHECKBOX:
    case constants.QuestionType.RADIO:
      var answerChoices = [];
      for (var i = 0; i < this.answers.length; i++) {
        var answer = document.createElement('div');
        var shrunkenAnswer = i + '-' + getDomNameFromValue(this.answers[i]);
        var input = document.createElement('input');
        input.setAttribute('id', shrunkenAnswer);
        input.setAttribute('name', shrunkenQuestion);
        input.setAttribute('type', this.questionType);
        input.setAttribute('value', shrunkenAnswer);
        input.setAttribute('required', this.required);
        answer.appendChild(input);

        var label = document.createElement('label');
        label.setAttribute('for', shrunkenAnswer);
        label.textContent = this.answers[i];
        answer.appendChild(label);

        if (this.answers[i] == constants.OTHER) {
          var textInput = document.createElement('input');
          textInput.setAttribute('type', 'text');
          textInput.setAttribute('name', shrunkenAnswer);
          answer.appendChild(textInput);
        }
        answerChoices.push(answer);
      }
      if (this.randomize != constants.Randomize.NONE)
        answerChoices = knuthShuffle(answerChoices, this.randomize);
      for (var i = 0; i < answerChoices.length; i++)
        container.appendChild(answerChoices[i]);
      break;
    case constants.QuestionType.DROPDOWN:
      var select = document.createElement('select');
      var answerChoices = [];
      for (var i = 0; i < this.answers.length; i++) {
        var option = document.createElement('option');
        option.value = i + '-' + getDomNameFromValue(this.answers[i]);
        option.textContent = this.answers[i];
        answerChoices.push(option);
      }
      if (this.randomize != constants.Randomize.NONE)
        answerChoices = knuthShuffle(answerChoices, this.randomize);
      for (var i = 0; i < answerChoices.length; i++)
        select.appendChild(answerChoices[i]);
      container.appendChild(select);
      break;
    default:
      throw new Error('Question "' + this.question + '" has an unexpected ' +
        'question type: ' + this.questionType);
  }
  return container;
}

// SCALE

/**
 * Represents scale questions. Currently supports HORIZONTAL_SCALE and
 * VERTICAL_SCALE question types. Randomization simply flips the order to
 * preserve the scale structure.
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 * @param {Array.string} scale The labels for each point on the scale.
 * @param {String} randomize Type of randomization (constants.Randomize).
 */
function ScaleQuestion(questionType, question, required, scale, randomize) {
  Question.call(this, questionType, question, required);
  this.scale = scale;
  this.randomize = randomize;
  this.attributes = [];
}

ScaleQuestion.prototype = Object.create(Question.prototype);
ScaleQuestion.prototype.constructor = ScaleQuestion;

/**
 * For multiple horizontal scales, like this:
 *    How are you feeling?
 *    - Sad 1 2 3 4 5
 *    - Happy 1 2 3 4 5
 * Here the adjectives "Sad" and "Happy" are attributes. This must be set
 * before makeDOMTree().
 * @param {Array.string} attributes The attributes that the user is rating.
 */
ScaleQuestion.prototype.setAttributes = function(attributes) {
  if (this.questionType != constants.QuestionType.MULT_HORIZ_SCALE)
    throw new Error('Only set attributes for MULT_HORIZ_SCALE questions.');
  this.attributes = attributes;
};

/**
 * A helper method for makeDOMTree.
 * @param {boolean} horizontal Whether it is a horizontal scale.
 * @param {string} questionName The DOM-ready question name.
 * @param {boolean} reverse Whether to flip the array.
 * @param {boolean} showLabels If horizontal, whether to show the labels.
 * @private
 */
ScaleQuestion.prototype.makeSingleRow =
    function(horizontal, questionName, reverse, showLabels) {
  var container = document.createElement('div');
  var scaleElements = [];
  for (var i = 0; i < this.scale.length; i++) {
    var answer = document.createElement('div');
    if (horizontal)
      answer.classList.add('horizontal-scale');
    var shrunkenAnswer = i + '-' + getDomNameFromValue(this.scale[i]);

    var radio = document.createElement('input');
    radio.setAttribute('id', shrunkenAnswer);
    radio.setAttribute('name', questionName);
    radio.setAttribute('type', 'radio');
    radio.setAttribute('value', shrunkenAnswer);
    if (this.required)
      radio.setAttribute('required', this.required);

    var label = document.createElement('label');
    label.setAttribute('for', shrunkenAnswer);
    label.textContent = this.scale[i];

    if (horizontal) {
      if (showLabels) {
        answer.appendChild(label);
        answer.appendChild(document.createElement('br'));
      }
      answer.appendChild(radio);
    } else {
      answer.appendChild(radio);
      answer.appendChild(label);
    }
    scaleElements.push(answer);
  }
  if (reverse)
    scaleElements = flipArray(scaleElements, this.randomize);
  for (var i = 0; i < scaleElements.length; i++)
    container.appendChild(scaleElements[i]);

  if (horizontal) {
    var clearDiv = document.createElement('div');
    clearDiv.classList.add('clear-div');
    container.appendChild(clearDiv);
  }
  return container;
};

/**
 * Creates the DOM representation of a ScaleQuestion.
 * @return {Object} The DOM node that contains the question.
 */
ScaleQuestion.prototype.makeDOMTree = function() {
  var horizontal =
      this.questionType == constants.QuestionType.HORIZ_SCALE ||
      this.questionType == constants.QuestionType.MULT_HORIZ_SCALE;
  var multi =
      (this.questionType == constants.QuestionType.MULT_HORIZ_SCALE) &&
      this.attributes.length > 0;
  var container = document.createElement('div');
  container.classList.add('fieldset');

  var legend = document.createElement('legend');
  legend.textContent = this.question;
  container.appendChild(legend);

  var reverse = this.randomize == constants.Randomize.NONE ? false : coinToss();
  var shrunkenQuestion = getDomNameFromValue(this.question);
  if (multi) {
    var shuffledAttributes =
        knuthShuffle(this.attributes, constants.Randomize.ALL);
    for (var i = 0; i < shuffledAttributes.length; i++) {
      var floatScale = document.createElement('div');
      floatScale.classList.add('horizontal-rowlabel');
      if (i == 0)
        floatScale.classList.add('first-rowlabel');
      floatScale.innerText = shuffledAttributes[i] + ':';
      container.appendChild(floatScale);
      var questionName =
          getDomNameFromValue(shuffledAttributes[i]) + shrunkenQuestion;
      container.appendChild(
          this.makeSingleRow(horizontal, questionName, reverse, (i == 0)));
    }
  } else {
    container.appendChild(
        this.makeSingleRow(horizontal, shrunkenQuestion, reverse, true));
  }
  return container;
};

// ESSAY

/**
 * Represents free response questions. Currently supports SHORT_STRING,
 * SHORT_ESSAY and LONG_ESSAY question types.
 * @param {string} questionType Question presentation (constants.QuestionType).
 * @param {string} question The question to be asked.
 * @param {boolean} required Whether the question needs to be answered.
 */
function EssayQuestion(questionType, question, required) {
  Question.call(this, questionType, question, required);
}

EssayQuestion.prototype = Object.create(Question.prototype);
EssayQuestion.prototype.constructor = EssayQuestion;

/**
 * Creates the DOM representation of an EssayQuestion.
 * @return {Object} The DOM node that contains the question.
 */
EssayQuestion.prototype.makeDOMTree = function() {
  var container = document.createElement('div');
  container.classList.add('fieldset');

  var legend = document.createElement('legend');
  legend.textContent = this.question;
  container.appendChild(legend);

  switch (this.questionType) {
    case constants.QuestionType.SHORT_STRING:
      var input = document.createElement('input');
      input.setAttribute('type', 'text');
      input.setAttribute('name', getDomNameFromValue(this.question));
      input.setAttribute('size', 60);
      if (this.required)
        input.setAttribute('required', this.required);
      container.appendChild(input);
      break;
    case constants.QuestionType.SHORT_ESSAY:
    case constants.QuestionType.LONG_ESSAY:
      var textarea = document.createElement('textarea');
      textarea.setAttribute('name', getDomNameFromValue(this.question));
      textarea.setAttribute('cols', 60);
      textarea.setAttribute(
          'rows',
          this.questionType == constants.QuestionType.SHORT_ESSAY ? 5 : 10);
      if (this.required)
        textarea.setAttribute('required', this.required);
      container.appendChild(textarea);
      break;
    default:
      throw new Error('Question "' + this.question + '" has an unexpected ' +
        'question type: ' + this.questionType);
  }
  return container;
}

// HELPER METHODS

/**
 * Turns the full string into a short name for a value property. It does this
 * by removing all non-alphabetic characters and truncating to 40 characters.
 * @param {string} The original input answer.
 * @returns {string} The shrunken version of the answer.
 */
function getDomNameFromValue(answer) {
  return answer.replace(/[\W\s]+/g, '').substring(0, 40);
}

/**
 * Use the knuth (aka Fisher-Yates) shuffle to randomize an array.
 * http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
 * @param {Array.Object} arr The array to randomize.
 * @param {string} randomType The type of randomization (constants.Randomize)
 * @returns {Array.Object} The randomized array.
 */
function knuthShuffle(arr, randomType) {
  var end = randomType == constants.Randomize.ANCHOR_LAST ?
            arr.length - 1 : arr.length;
  for (var i = 0; i < end; i++) {
    // Random int: i <= randomIndex < arr.length
    var randomIndex = Math.floor(Math.random() * (end - i)) + i;

    // Swap with current element.
    var swapTemp = arr[i];
    arr[i] = arr[randomIndex];
    arr[randomIndex] = swapTemp;
  }
  return arr;
}

/**
 * A wrapper method for Math.random.
 * @returns {boolean} Returns true half the time.
 */
function coinToss() {
  if ((Math.random()) < 0.5)
    return true;
  return false;
}

/**
 * Flip an array. Will anchor the last element for ANCHOR_LAST.
 * @param {Array.Object} arr The array to flip.
 * @param {string} randomType The type of randomization (constants.Randomize)
 * @returns {Array.Object} The flipped array.
 */
function flipArray(arr, randomType) {
  var end = randomType == constants.Randomize.ANCHOR_LAST ?
            arr.length - 1 : arr.length;
  var reverseArr = [];
  for (var i = end - 1; i >= 0; i--)
    reverseArr.push(arr[i]);
  if (randomType == constants.Randomize.ANCHOR_LAST)
    reverseArr.push(arr[arr.length - 1]);
  return reverseArr;
}

/**
 * Creates a submit button for a form.
 * @returns {Object} The DOM node with the submit button.
 */
function makeSubmitButtonDOM() {
  var button = document.createElement('input');
  button.setAttribute('id', 'submit-button');
  button.setAttribute('type', 'submit');
  button.setAttribute('value', 'Submit');

  var fieldset = document.createElement('fieldset');
  fieldset.classList.add('submit');
  fieldset.appendChild(button);
  return fieldset;
}
<|endoftext|>


<commit_before>$(function() {
"use strict";

function vercmp(a, b) {
  var re = /^([0-9]*)([^0-9]*)(.*)$/;
  var abits, bbits;
  var anum, bnum;
  while (a.length && b.length) {
    abits = re.exec(a);
    bbits = re.exec(b);
    if ((abits[1] === '') != (bbits[1] === '')) {
      return abits[1] ? -1 : 1;
    }
    if (abits[1] !== '') {
      anum = parseInt(abits[1]);
      bnum = parseInt(bbits[1])
      if (anum !== bnum)
        return anum - bnum;
    }

    if (abits[2] !== bbits[2]) {
      return abits[2] < bbits[2] ? -1 : 1
    }

    a = abits[3];
    b = bbits[3];
  }

  return a.length - b.length;
}

function shorten(ref) {
  var match = /^refs\/(tags|branches)\/(.*)/.exec(ref);
  if (match)
    return match[2];
  return ref;
}

var Match = Backbone.Model.extend({
  initialize: function() {
    this.get('contexts').forEach(function (ctx) {
        ctx.paths.sort(function (a,b) {return vercmp(a.ref, b.ref);})
    });
    this.get('contexts').sort(function (a,b) {
        return vercmp(a.paths[0].ref, b.paths[0].ref);
    });
    this.set({
               context: this.get('contexts')[0],
               path: this.get('contexts')[0].paths[0]
             });
  },
  url: function() {
    return "https://github.com/torvalds/linux/blob/" + shorten(this.get('path').ref) +
      "/" + this.get('path').path + "#L" + this.get('context').lno;
  }
});

var MatchView = Backbone.View.extend({
  tagName: 'div',
  initialize: function() {
    this.model.on('change', this.render, this);
  },
  render: function() {
    var div = this._render();
    this.$el.empty();
    this.$el.append(div);
    return this;
  },
  _render: function() {
    var h = new HTMLFactory();
    var ctx = this.model.get('context');
    var i;
    var ctx_before = [], ctx_after = [];
    for (i = 0; i < ctx.context_before.length; i ++) {
      ctx_before.unshift(h.div([
                                 h.span({cls: 'lno'}, [ctx.lno - i - 1, ":"]),
                                 ctx.context_before[i]
                               ]));
    }
    for (i = 0; i < ctx.context_after.length; i ++) {
      ctx_after.push(h.div([
                             h.span({cls: 'lno'}, [ctx.lno + i + 1, ":"]),
                             ctx.context_after[i]
                           ]));
    }
    var line = this.model.get('line');
    var bounds = this.model.get('bounds');
    var pieces = [line.substring(0, bounds[0]),
                  line.substring(bounds[0], bounds[1]),
                  line.substring(bounds[1])];

    return h.div({cls: 'match'}, [
        h.div({}, [
          h.span({cls: 'label'}, [
            h.a({href: this.model.url()}, [
                  shorten(this.model.get('path').ref),
                  ":",
                  this.model.get('path').path])])]),
        h.div({cls: 'contents'}, [
                ctx_before,
                h.div({cls: 'matchline'}, [
                  h.span({cls: 'lno'}, [ctx.lno + ":"]),
                  pieces[0],
                  h.span({cls: 'matchstr'}, [pieces[1]]),
                  pieces[2]
                ]),
                ctx_after]),
        this.render_contexts(h)]);
  },
  render_contexts: function(h) {
    var self = this;
    return h.div({cls: 'contexts'}, [
          h.span({cls: 'label'}, ["Also in:"]),
          h.ul({},
          this.model.get('contexts').map(function (ctx) {
            return h.li(ctx === self.model.get('context') ? {cls: 'selected'} : {}, [
                h.a({href: "#",
                     click: _.bind(self.switch_context, self, ctx)}, [
                shorten(ctx.paths[0].ref)]),
                ctx.paths.length > 1 ? (" +" + (ctx.paths.length - 1) + " identical") : [],
            ]);
          }))])
  },
  switch_context: function(ctx) {
    this.model.set({
                     context: ctx,
                     path: ctx.paths[0]
                   });
    return false;
  }
});

var MatchSet = Backbone.Collection.extend({
  model: Match
});

var SearchState = Backbone.Model.extend({
  defaults: function() {
    return {
      displaying: null,
      error: null,
      time: null,
      why: null
    };
  },

  initialize: function() {
    this.search_map = {};
    this.matches = new MatchSet();
    this.search_id = 0;
    this.on('change:displaying', this.new_search, this);
  },

  new_search: function() {
    this.set({
        error: null,
        time: null,
        why: null
    });
    this.matches.reset();
    for (var k in this.search_map) {
      if (k < this.get('displaying'))
        delete this.search_map[k];
    }
  },

  next_id: function() {
    return ++this.search_id;
  },

  dispatch: function (q, file) {
    var id = this.next_id();
    this.search_map[id] = {q: q, file: file};
    if (!q.length)
      this.set('displaying', id);
    return id;
  },

  url: function() {
    var q = {};
    var current = this.search_map[this.get('displaying')];
    if (!current)
      return '/search';

    if (current.q)    q.q = current.q;
    if (current.file) q.file = current.file;
    return '/search?' + $.param(q);
  },

  handle_error: function (search, error) {
    if (search === this.search_id) {
      this.set('displaying', search);
      this.set('error', error);
    }
  },

  handle_match: function (search, match) {
    if (search < this.get('displaying'))
      return false;
    this.set('displaying', search);
    this.matches.add({
                       line: match.line,
                       bounds: match.bounds,
                       contexts: match.contexts
                     });
  },
  handle_done: function (search, time, why) {
    this.set('displaying', search);
    this.set({time: time, why: why});
  }
});

var MatchesView = Backbone.View.extend({
  el: $('#results'),
  initialize: function() {
    this.model.matches.bind('add', this.add_one, this);
    this.model.matches.bind('reset', this.add_all, this);
  },
  add_one: function(model) {
    var view = new MatchView({model: model});
    this.$el.append(view.render().el);
  },
  add_all: function() {
    this.$el.empty();
    this.model.matches.each(this.add_one);
  }
});

var ResultView = Backbone.View.extend({
  el: $('#resultarea'),
  initialize: function() {
    this.matches_view = new MatchesView({model: this.model});
    this.permalink = this.$('#permalink');
    this.results   = this.$('#numresults');
    this.errorbox  = $('#regex-error');
    this.time      = this.$('#searchtime');
    this.last_url  = null;

    this.model.on('all', this.render, this);
    this.model.matches.on('all', this.render, this);
  },

  render: function() {
    if (this.model.get('error')) {
      this.errorbox.find('#errortext').text(this.model.get('error'));
      this.errorbox.show();
    } else {
      this.errorbox.hide()
    }

    var url = this.model.url();
    if (this.last_url !== url ) {
      this.permalink.attr('href', url);
      if (history.replaceState) {
        history.replaceState(null, '', url);
      }
      this.last_url = url;
    }

    if (this.model.search_map[this.model.get('displaying')].q === '' ||
       this.model.get('error')) {
      this.$el.hide();
      return this;
    }

    this.$el.show();

    if (this.model.get('time')) {
      this.$('#searchtimebox').show();
      var time = this.model.get('time');
      this.time.text((time/1000) + "s")
    } else {
      this.$('#searchtimebox').hide();
    }

    var results = '' + this.model.matches.size();
    if (this.model.get('why') === 'limit')
      results = results + '+';
    this.results.text(results);

    return this;
  }
});

var CodesearchUI = function() {
  return {
    state: new SearchState(),
    view: null,
    onload: function() {
      if (CodesearchUI.input)
        return;

      CodesearchUI.view = new ResultView({model: CodesearchUI.state});

      CodesearchUI.input      = $('#searchbox');
      CodesearchUI.input_file = $('#filebox');
      var parms = CodesearchUI.parse_query_params();
      if (parms.q)
        CodesearchUI.input.val(parms.q);
      if (parms.file)
        CodesearchUI.input_file.val(parms.file);

      CodesearchUI.input.keydown(CodesearchUI.keypress);
      CodesearchUI.input_file.keydown(CodesearchUI.keypress);
      CodesearchUI.input.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input_file.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input.focus();

      Codesearch.connect(CodesearchUI);
    },
    parse_query_params: function() {
      var urlParams = {};
      var e,
          a = /\+/g,
          r = /([^&=]+)=?([^&]*)/g,
          d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
          q = window.location.search.substring(1);

      while (e = r.exec(q))
        urlParams[d(e[1])] = d(e[2]);
      return urlParams;
    },
    on_connect: function() {
      CodesearchUI.newsearch();
    },
    keypress: function() {
      setTimeout(CodesearchUI.newsearch, 0);
    },
    newsearch: function() {
      var id = CodesearchUI.state.dispatch(CodesearchUI.input.val(),
                                           CodesearchUI.input_file.val());
      Codesearch.new_search(
        CodesearchUI.input.val(),
        CodesearchUI.input_file.val(),
        id);
    },
    error: function(search, error) {
      CodesearchUI.state.handle_error(search, error);
    },
    match: function(search, match) {
      CodesearchUI.state.handle_match(search, match);
    },
    search_done: function(search, time, why) {
      CodesearchUI.state.handle_done(search, time, why);
    },
  };
}();
CodesearchUI.onload();
window.CodesearchUI = CodesearchUI;
});
<commit_msg>Shorten: Also truncate hashes.<commit_after>$(function() {
"use strict";

function vercmp(a, b) {
  var re = /^([0-9]*)([^0-9]*)(.*)$/;
  var abits, bbits;
  var anum, bnum;
  while (a.length && b.length) {
    abits = re.exec(a);
    bbits = re.exec(b);
    if ((abits[1] === '') != (bbits[1] === '')) {
      return abits[1] ? -1 : 1;
    }
    if (abits[1] !== '') {
      anum = parseInt(abits[1]);
      bnum = parseInt(bbits[1])
      if (anum !== bnum)
        return anum - bnum;
    }

    if (abits[2] !== bbits[2]) {
      return abits[2] < bbits[2] ? -1 : 1
    }

    a = abits[3];
    b = bbits[3];
  }

  return a.length - b.length;
}

function shorten(ref) {
  var match = /^refs\/(tags|branches)\/(.*)/.exec(ref);
  if (match)
    return match[2];
  match = /^([0-9a-f]{8})[0-9a-f]+$/.exec(ref);
  if (match)
    return match[1];
  return ref;
}

var Match = Backbone.Model.extend({
  initialize: function() {
    this.get('contexts').forEach(function (ctx) {
        ctx.paths.sort(function (a,b) {return vercmp(a.ref, b.ref);})
    });
    this.get('contexts').sort(function (a,b) {
        return vercmp(a.paths[0].ref, b.paths[0].ref);
    });
    this.set({
               context: this.get('contexts')[0],
               path: this.get('contexts')[0].paths[0]
             });
  },
  url: function() {
    return "https://github.com/torvalds/linux/blob/" + shorten(this.get('path').ref) +
      "/" + this.get('path').path + "#L" + this.get('context').lno;
  }
});

var MatchView = Backbone.View.extend({
  tagName: 'div',
  initialize: function() {
    this.model.on('change', this.render, this);
  },
  render: function() {
    var div = this._render();
    this.$el.empty();
    this.$el.append(div);
    return this;
  },
  _render: function() {
    var h = new HTMLFactory();
    var ctx = this.model.get('context');
    var i;
    var ctx_before = [], ctx_after = [];
    for (i = 0; i < ctx.context_before.length; i ++) {
      ctx_before.unshift(h.div([
                                 h.span({cls: 'lno'}, [ctx.lno - i - 1, ":"]),
                                 ctx.context_before[i]
                               ]));
    }
    for (i = 0; i < ctx.context_after.length; i ++) {
      ctx_after.push(h.div([
                             h.span({cls: 'lno'}, [ctx.lno + i + 1, ":"]),
                             ctx.context_after[i]
                           ]));
    }
    var line = this.model.get('line');
    var bounds = this.model.get('bounds');
    var pieces = [line.substring(0, bounds[0]),
                  line.substring(bounds[0], bounds[1]),
                  line.substring(bounds[1])];

    return h.div({cls: 'match'}, [
        h.div({}, [
          h.span({cls: 'label'}, [
            h.a({href: this.model.url()}, [
                  shorten(this.model.get('path').ref),
                  ":",
                  this.model.get('path').path])])]),
        h.div({cls: 'contents'}, [
                ctx_before,
                h.div({cls: 'matchline'}, [
                  h.span({cls: 'lno'}, [ctx.lno + ":"]),
                  pieces[0],
                  h.span({cls: 'matchstr'}, [pieces[1]]),
                  pieces[2]
                ]),
                ctx_after]),
        this.render_contexts(h)]);
  },
  render_contexts: function(h) {
    var self = this;
    return h.div({cls: 'contexts'}, [
          h.span({cls: 'label'}, ["Also in:"]),
          h.ul({},
          this.model.get('contexts').map(function (ctx) {
            return h.li(ctx === self.model.get('context') ? {cls: 'selected'} : {}, [
                h.a({href: "#",
                     click: _.bind(self.switch_context, self, ctx)}, [
                shorten(ctx.paths[0].ref)]),
                ctx.paths.length > 1 ? (" +" + (ctx.paths.length - 1) + " identical") : [],
            ]);
          }))])
  },
  switch_context: function(ctx) {
    this.model.set({
                     context: ctx,
                     path: ctx.paths[0]
                   });
    return false;
  }
});

var MatchSet = Backbone.Collection.extend({
  model: Match
});

var SearchState = Backbone.Model.extend({
  defaults: function() {
    return {
      displaying: null,
      error: null,
      time: null,
      why: null
    };
  },

  initialize: function() {
    this.search_map = {};
    this.matches = new MatchSet();
    this.search_id = 0;
    this.on('change:displaying', this.new_search, this);
  },

  new_search: function() {
    this.set({
        error: null,
        time: null,
        why: null
    });
    this.matches.reset();
    for (var k in this.search_map) {
      if (k < this.get('displaying'))
        delete this.search_map[k];
    }
  },

  next_id: function() {
    return ++this.search_id;
  },

  dispatch: function (q, file) {
    var id = this.next_id();
    this.search_map[id] = {q: q, file: file};
    if (!q.length)
      this.set('displaying', id);
    return id;
  },

  url: function() {
    var q = {};
    var current = this.search_map[this.get('displaying')];
    if (!current)
      return '/search';

    if (current.q)    q.q = current.q;
    if (current.file) q.file = current.file;
    return '/search?' + $.param(q);
  },

  handle_error: function (search, error) {
    if (search === this.search_id) {
      this.set('displaying', search);
      this.set('error', error);
    }
  },

  handle_match: function (search, match) {
    if (search < this.get('displaying'))
      return false;
    this.set('displaying', search);
    this.matches.add({
                       line: match.line,
                       bounds: match.bounds,
                       contexts: match.contexts
                     });
  },
  handle_done: function (search, time, why) {
    this.set('displaying', search);
    this.set({time: time, why: why});
  }
});

var MatchesView = Backbone.View.extend({
  el: $('#results'),
  initialize: function() {
    this.model.matches.bind('add', this.add_one, this);
    this.model.matches.bind('reset', this.add_all, this);
  },
  add_one: function(model) {
    var view = new MatchView({model: model});
    this.$el.append(view.render().el);
  },
  add_all: function() {
    this.$el.empty();
    this.model.matches.each(this.add_one);
  }
});

var ResultView = Backbone.View.extend({
  el: $('#resultarea'),
  initialize: function() {
    this.matches_view = new MatchesView({model: this.model});
    this.permalink = this.$('#permalink');
    this.results   = this.$('#numresults');
    this.errorbox  = $('#regex-error');
    this.time      = this.$('#searchtime');
    this.last_url  = null;

    this.model.on('all', this.render, this);
    this.model.matches.on('all', this.render, this);
  },

  render: function() {
    if (this.model.get('error')) {
      this.errorbox.find('#errortext').text(this.model.get('error'));
      this.errorbox.show();
    } else {
      this.errorbox.hide()
    }

    var url = this.model.url();
    if (this.last_url !== url ) {
      this.permalink.attr('href', url);
      if (history.replaceState) {
        history.replaceState(null, '', url);
      }
      this.last_url = url;
    }

    if (this.model.search_map[this.model.get('displaying')].q === '' ||
       this.model.get('error')) {
      this.$el.hide();
      return this;
    }

    this.$el.show();

    if (this.model.get('time')) {
      this.$('#searchtimebox').show();
      var time = this.model.get('time');
      this.time.text((time/1000) + "s")
    } else {
      this.$('#searchtimebox').hide();
    }

    var results = '' + this.model.matches.size();
    if (this.model.get('why') === 'limit')
      results = results + '+';
    this.results.text(results);

    return this;
  }
});

var CodesearchUI = function() {
  return {
    state: new SearchState(),
    view: null,
    onload: function() {
      if (CodesearchUI.input)
        return;

      CodesearchUI.view = new ResultView({model: CodesearchUI.state});

      CodesearchUI.input      = $('#searchbox');
      CodesearchUI.input_file = $('#filebox');
      var parms = CodesearchUI.parse_query_params();
      if (parms.q)
        CodesearchUI.input.val(parms.q);
      if (parms.file)
        CodesearchUI.input_file.val(parms.file);

      CodesearchUI.input.keydown(CodesearchUI.keypress);
      CodesearchUI.input_file.keydown(CodesearchUI.keypress);
      CodesearchUI.input.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input_file.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input.focus();

      Codesearch.connect(CodesearchUI);
    },
    parse_query_params: function() {
      var urlParams = {};
      var e,
          a = /\+/g,
          r = /([^&=]+)=?([^&]*)/g,
          d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
          q = window.location.search.substring(1);

      while (e = r.exec(q))
        urlParams[d(e[1])] = d(e[2]);
      return urlParams;
    },
    on_connect: function() {
      CodesearchUI.newsearch();
    },
    keypress: function() {
      setTimeout(CodesearchUI.newsearch, 0);
    },
    newsearch: function() {
      var id = CodesearchUI.state.dispatch(CodesearchUI.input.val(),
                                           CodesearchUI.input_file.val());
      Codesearch.new_search(
        CodesearchUI.input.val(),
        CodesearchUI.input_file.val(),
        id);
    },
    error: function(search, error) {
      CodesearchUI.state.handle_error(search, error);
    },
    match: function(search, match) {
      CodesearchUI.state.handle_match(search, match);
    },
    search_done: function(search, time, why) {
      CodesearchUI.state.handle_done(search, time, why);
    },
  };
}();
CodesearchUI.onload();
window.CodesearchUI = CodesearchUI;
});
<|endoftext|>


<commit_before>(function() {
  window.EventBus = new EventEmitter();

  var mapsKey = 'maps';

  window.fetchMaps = function() {
    return JSON.parse(localStorage.getItem(mapsKey) || '[]').sort();
  };

  var storeMap = function(name) {
    var maps = fetchMaps();
    if (maps.indexOf(name) === -1) {
      maps.push(name);
      localStorage.setItem(mapsKey, JSON.stringify(maps));
    }
  };

  var map = (window.location.hash || 'blank').replace('#', '');
  var notesKey = map + '-notes';
  storeMap(map);

  window.onhashchange = function(event) {
    event.preventDefault();
    map = (window.location.hash || 'blank').replace('#', '');
    notesKey = map + '-notes';
    storeMap(map);
    EventBus.emitEvent('reload');
  };

  window.fetchNotes = function() {
    var notes = JSON.parse(localStorage.getItem(notesKey)) || [];
    if (notes.length === 0 && notesKey === 'blank-notes') {
      return [{
        "matrix":"translate(5487px, 3211px)",
        "id":0,
        "key":0,
        "isEditing":false,
        "x":5487,
        "y":3211,
        "content":"# Atlas - Evernote + Google Maps\n\n- Click anywhere to add a new note\n- Click on a note to edit it\n- Enter __markdown__ text into a note\n- Click away or tab out to finish editing\n- Drag a note to move it\n- Create new boards using search\n- To remove a note delete its content",
        "className":"note"
      }];
    } else {
      return notes;
    }
  };

  EventBus.addListener('create-note', function(note) {
    var notes = fetchNotes();
    notes.push(note);
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });

  EventBus.addListener('update-note', function(changedNote) {
    var notes = fetchNotes();
    notes.forEach(function(note, index) {
      if (note.id === changedNote.id) {
        Object.keys(changedNote).forEach(function(key) {
          note[key] = changedNote[key];
        });
      }
    });
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });

  EventBus.addListener('delete-note', function(id) {
    var notes = fetchNotes().filter(function(note) {
      return note.id !== id;
    });
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });

  EventBus.addListener('stop-editing-note', function(id) {
    var notes = fetchNotes().map(function(note) {
      note.isEditing = false;
      return note;
    });
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });
})();
<commit_msg>feat: add tag about use in chrome<commit_after>(function() {
  window.EventBus = new EventEmitter();

  var mapsKey = 'maps';

  window.fetchMaps = function() {
    return JSON.parse(localStorage.getItem(mapsKey) || '[]').sort();
  };

  var storeMap = function(name) {
    var maps = fetchMaps();
    if (maps.indexOf(name) === -1) {
      maps.push(name);
      localStorage.setItem(mapsKey, JSON.stringify(maps));
    }
  };

  var map = (window.location.hash || 'blank').replace('#', '');
  var notesKey = map + '-notes';
  storeMap(map);

  window.onhashchange = function(event) {
    event.preventDefault();
    map = (window.location.hash || 'blank').replace('#', '');
    notesKey = map + '-notes';
    storeMap(map);
    EventBus.emitEvent('reload');
  };

  window.fetchNotes = function() {
    var notes = JSON.parse(localStorage.getItem(notesKey)) || [];
    if (notes.length === 0 && notesKey === 'blank-notes') {
      return [{
        "matrix":"translate(5487px, 3211px)",
        "id":0,
        "key":0,
        "isEditing":false,
        "x":5487,
        "y":3211,
        "content":"# Atlas - Evernote + Google Maps\n\n- Please use in Chrome\n- Click anywhere to add a new note\n- Click on a note to edit it\n- Enter __markdown__ text into a note\n- Click away or tab out to finish editing\n- Drag a note to move it\n- Create new boards using search\n- To remove a note delete its content",
        "className":"note"
      }];
    } else {
      return notes;
    }
  };

  EventBus.addListener('create-note', function(note) {
    var notes = fetchNotes();
    notes.push(note);
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });

  EventBus.addListener('update-note', function(changedNote) {
    var notes = fetchNotes();
    notes.forEach(function(note, index) {
      if (note.id === changedNote.id) {
        Object.keys(changedNote).forEach(function(key) {
          note[key] = changedNote[key];
        });
      }
    });
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });

  EventBus.addListener('delete-note', function(id) {
    var notes = fetchNotes().filter(function(note) {
      return note.id !== id;
    });
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });

  EventBus.addListener('stop-editing-note', function(id) {
    var notes = fetchNotes().map(function(note) {
      note.isEditing = false;
      return note;
    });
    localStorage.setItem(notesKey, JSON.stringify(notes));
    EventBus.emitEvent('reload-notes');
  });
})();
<|endoftext|>


<commit_before>/**
 * @since 2017-05-15 10:00:05
 * @author vivaxy
 * @see https://leetcode.com/problems/queue-reconstruction-by-height/

 Suppose you have a random list of people standing in a queue. Each person is described by a pair of integers (h, k), where h is the height of the person and k is the number of people in front of this person who have a height greater than or equal to h. Write an algorithm to reconstruct the queue.

 Note:
 The number of people is less than 1,100.

 Example

 Input:
 [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

 Output:
 [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

 */

/**
 * 前一个值表示权重，需要下降到 0 开始，中间差 1
 * 后一个值表示顺序
 * 前一个值 + 后一个值 的结果排序
 * 相同的结果以后一个值排序
 * @param {number[][]} people
 * @return {number[][]}
 */
var reconstructQueue = function(people) {
    var peopleHeight = [];
    people.forEach(function(p) {
        var height = p[0];
        if (peopleHeight.indexOf(height) === -1) {
            peopleHeight.push(height);
        }
    });
    var sortedPeopleHeight = peopleHeight.sort(function(p, n) {
        return p - n;
    });
    return people.sort(function(prev, next) {
        var prevHeight = prev[0];
        var prevBeforeCount = prev[1];
        var prevWeight = sortedPeopleHeight.indexOf(prevHeight);
        var prevOrder = prevWeight + prevBeforeCount;
        var nextHeight = next[0];
        var nextBeforeCount = next[1];
        var nextWeight = sortedPeopleHeight.indexOf(nextHeight);
        var nextOrder = nextWeight + nextBeforeCount;
        var diff = prevOrder - nextOrder;
        console.log(prev, next, diff, prevBeforeCount - nextBeforeCount);
        if (diff === 0) {
            return prevBeforeCount - nextBeforeCount;
        }
        return diff;
    });
};

var expect = require('../lib').expect;
var isSameArray = require('../lib').isSameArray;
expect(isSameArray(reconstructQueue([[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]]), [[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]]), true);
<commit_msg>feat(javascript): :sparkles:problems<commit_after>/**
 * @since 2017-05-15 10:00:05
 * @author vivaxy
 * @see https://leetcode.com/problems/queue-reconstruction-by-height/

 Suppose you have a random list of people standing in a queue. Each person is described by a pair of integers (h, k), where h is the height of the person and k is the number of people in front of this person who have a height greater than or equal to h. Write an algorithm to reconstruct the queue.

 Note:
 The number of people is less than 1,100.

 Example

 Input:
 [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

 Output:
 [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

 */

/**
 * @see https://leetcode.com/submissions/detail/103145734/
 * @param {number[][]} people
 * @return {number[][]}
 */
var reconstructQueue = function(people) {
    var sortedPeople = people.sort(function(prev, next) {
        var prevHeight = prev[0];
        var nextHeight = next[0];
        if (prevHeight === nextHeight) {
            return prev[1] - next[1];
        }
        return prevHeight - nextHeight;
    });
    var maxCursor = sortedPeople.length - 1;
    for (var cursor = maxCursor; cursor >= 0; cursor--) {
        var thisPerson = sortedPeople[cursor];
        var thisHeight = thisPerson[0];
        var thisOrder = thisPerson[1];
        var nowOrder = 0;
        for (var i = 0; i < cursor; i++) {
            if (sortedPeople[i][0] >= thisHeight) {
                nowOrder++;
            }
        }
        var orderDiff = thisOrder - nowOrder;
        if (orderDiff !== 0) {
            var targetPosition = cursor + orderDiff;
            sortedPeople.splice(cursor, 1);
            sortedPeople.splice(targetPosition, 0, thisPerson);
        }
    }
    return sortedPeople;
};

var expect = require('../lib').expect;
var isSameArray = require('../lib').isSameArray;
expect(isSameArray(
    reconstructQueue(
        [[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]]
    ),
    [[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]]
), true);
<|endoftext|>


<commit_before>// Description:
// Watch TV series/Movies for free
//
// Dependencies:
// Requires jQuery.
//
// Commands:
// watch NCIS - shows last 5 episodes and similar TV series
// watch Snatch - shows Movies related to name

function ddg_spice_guidebox_getid (api_result)
{
    "use strict";

    if (!api_result.results) return;

    // Prevent jQuery from appending "_={timestamp}" in our url.
    $.ajaxSetup({
        cache: true
    });

    var terms = ["full episodes of", "full free episodes of",
                   "free episodes of", "guidebox", "watch",
                   "full episodes", "watch free", "full free episodes",
                   "free episodes"];
    var i;

    var query = DDG.get_query();

    for (i in terms){
        query = query.replace(terms[i], "");
    }

    var metadata = {};
    metadata.res_type = api_result.results.result[0].type;
    metadata.more = api_result.results.result[0].url;
    metadata.query = query;
    ddg_spice_guidebox_getid.metadata = metadata;

    if (metadata.res_type === "series"){
        ddg_spice_guidebox_getid.metadata.searched = api_result.results.result;
        $.getScript("/js/spice/guidebox/lastshows/"+ api_result.results.result[0].type + "/" + api_result.results.result[0].id);
    } else {
        ddg_spice_guidebox_getid.render(api_result);
    }
}

function ddg_spice_guidebox_lastshows(api_result)
{
    ddg_spice_guidebox_getid.render(api_result);
}

function getSimilar(searched)
{
    var out = "";
    var i, j;

    for (i in searched){
        out += "
" + searched[i].title + "
";
    }
    return out;
}

ddg_spice_guidebox_getid.render = function(api_result) {
    "use strict";

    var metadata = ddg_spice_guidebox_getid.metadata;

    var options = {
            data : api_result,
            force_big_header : true,
            source_name : "Guidebox",
            source_url : metadata.more,
            template_frame : "carousel",
            carousel_css_id: "guidebox",
            carousel_items : api_result.results.result,
            force_no_fold : 1,
            template_options : {
                li_width : 120,
                li_height : 135
            }
    };

    if (metadata.res_type === "series"){
        options.header1 = "Watch full episodes of " + metadata.query + " (Guidebox)";
        options.template_normal = "guidebox_getid_series";
    } else if (metadata.res_type === "movie"){
        options.header1 = "Watch full movie: " + metadata.query + " (Guidebox)";
        options.template_normal = "guidebox_getid_movie";
        options.carousel_template_detail = "guidebox_getid_movie_details";
    }

    Spice.render(options);

    if (metadata.res_type === "series"){

        $("#ddgc_detail").html(
            "
"
           +     "Similar to" + metadata.query + ""
           +     "
"
           +         "
"
           +             getSimilar(metadata.searched)
           +         "
"
           +     "
"
           + "
"
        );

        $("#ddgc_detail").css("display", "block");
        $("a.GB_showHide").click(function(){
            if ($(this).data("target")){
                var target = $(this).data("target");
                $(target).toggle();
            }
        });
    }
};

Handlebars.registerHelper("getDate", function(first_aired) {
    "use strict";

    var datesplit;
    var months = {
            '01' : 'Jan.',
            '02' : 'Feb.',
            '03' : 'Mar.',
            '04' : 'Apr.',
            '05' : 'May',
            '06' : 'Jun.',
            '07' : 'Jul.',
            '08' : 'Aug.',
            '09' : 'Sep.',
            '10' : 'Oct.',
            '11' : 'Nov.',
            '12' : 'Dec.',
    };

    datesplit = first_aired.split('-');
    return months[datesplit[1]] + ' ' + datesplit[2] + ', ' + datesplit[0];

});
<commit_msg>reduced height<commit_after>// Description:
// Watch TV series/Movies for free
//
// Dependencies:
// Requires jQuery.
//
// Commands:
// watch NCIS - shows last 5 episodes and similar TV series
// watch Snatch - shows Movies related to name

function ddg_spice_guidebox_getid (api_result)
{
    "use strict";

    if (!api_result.results) return;

    // Prevent jQuery from appending "_={timestamp}" in our url.
    $.ajaxSetup({
        cache: true
    });

    var terms = ["full episodes of", "full free episodes of",
                   "free episodes of", "guidebox", "watch",
                   "full episodes", "watch free", "full free episodes",
                   "free episodes"];
    var i;

    var query = DDG.get_query();

    for (i in terms){
        query = query.replace(terms[i], "");
    }

    var metadata = {};
    metadata.res_type = api_result.results.result[0].type;
    metadata.more = api_result.results.result[0].url;
    metadata.query = query;
    ddg_spice_guidebox_getid.metadata = metadata;

    if (metadata.res_type === "series"){
        ddg_spice_guidebox_getid.metadata.searched = api_result.results.result;
        $.getScript("/js/spice/guidebox/lastshows/"+ api_result.results.result[0].type + "/" + api_result.results.result[0].id);
    } else {
        ddg_spice_guidebox_getid.render(api_result);
    }
}

function ddg_spice_guidebox_lastshows(api_result)
{
    ddg_spice_guidebox_getid.render(api_result);
}

function getSimilar(searched)
{
    var out = "";
    var i, j;

    for (i in searched){
        out += "
" + searched[i].title + "
";
    }
    return out;
}

ddg_spice_guidebox_getid.render = function(api_result) {
    "use strict";

    var metadata = ddg_spice_guidebox_getid.metadata;

    var options = {
            data : api_result,
            force_big_header : true,
            source_name : "Guidebox",
            source_url : metadata.more,
            template_frame : "carousel",
            carousel_css_id: "guidebox",
            carousel_items : api_result.results.result,
            force_no_fold : 1,
            template_options : {
                li_width : 120,
                li_height : 115
            }
    };

    if (metadata.res_type === "series"){
        options.header1 = "Watch full episodes of " + metadata.query + " (Guidebox)";
        options.template_normal = "guidebox_getid_series";
    } else if (metadata.res_type === "movie"){
        options.header1 = "Watch full movie: " + metadata.query + " (Guidebox)";
        options.template_normal = "guidebox_getid_movie";
        options.carousel_template_detail = "guidebox_getid_movie_details";
        options.template_options.li_height = 135;
    }

    Spice.render(options);

    if (metadata.res_type === "series"){

        $("#ddgc_detail").html(
            "
"
           +     "Similar to" + metadata.query + ""
           +     "
"
           +         "
"
           +             getSimilar(metadata.searched)
           +         "
"
           +     "
"
           + "
"
        );

        $("#ddgc_detail").css("display", "block");
        $("a.GB_showHide").click(function(){
            if ($(this).data("target")){
                var target = $(this).data("target");
                $(target).toggle();
            }
        });
    }
};

Handlebars.registerHelper("getDate", function(first_aired) {
    "use strict";

    var datesplit;
    var months = {
            '01' : 'Jan.',
            '02' : 'Feb.',
            '03' : 'Mar.',
            '04' : 'Apr.',
            '05' : 'May',
            '06' : 'Jun.',
            '07' : 'Jul.',
            '08' : 'Aug.',
            '09' : 'Sep.',
            '10' : 'Oct.',
            '11' : 'Nov.',
            '12' : 'Dec.',
    };

    datesplit = first_aired.split('-');
    return months[datesplit[1]] + ' ' + datesplit[2] + ', ' + datesplit[0];

});
<|endoftext|>


<commit_before>/* jshint strict: false, sub: true */
/* global */
'use strict';

// //////////////////////////////////////////////////////////////////////////////
// / DISCLAIMER
// /
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
// / Copyright 2014 triagens GmbH, Cologne, Germany
// /
// / Licensed under the Apache License, Version 2.0 (the "License")
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// /     http://www.apache.org/licenses/LICENSE-2.0
// /
// / Unless required by applicable law or agreed to in writing, software
// / distributed under the License is distributed on an "AS IS" BASIS,
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// / See the License for the specific language governing permissions and
// / limitations under the License.
// /
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
// /
// / @author Max Neunhoeffer
// //////////////////////////////////////////////////////////////////////////////

const functionsDocumentation = {
  'replication_ongoing': 'replication ongoing tests',
  'replication_static': 'replication static tests',
  'replication_sync': 'replication sync tests',
  'shell_replication': 'shell replication tests'
};
const optionsDocumentation = [
];

const _ = require('lodash');
const pu = require('@arangodb/process-utils');
const tu = require('@arangodb/test-utils');

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: shell_replication
// //////////////////////////////////////////////////////////////////////////////

function shellReplication (options) {
  let testCases = tu.scanTestPath('js/common/tests/replication');

  var opts = {
    'replication': true
  };
  _.defaults(opts, options);

  return tu.performTests(opts, testCases, 'shell_replication', tu.runThere);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_ongoing
// //////////////////////////////////////////////////////////////////////////////

function replicationOngoing (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-ongoing';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_static
// //////////////////////////////////////////////////////////////////////////////

function replicationStatic (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-static';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.grantDatabase("replicator-user", "_system");
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);
      return {};
    }
  };

  return tu.performTests(
    localOptions,
    testCases,
    'master_static',
    tu.runInArangosh,
    {
      'server.authentication': 'true'
    },
    startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_sync
// //////////////////////////////////////////////////////////////////////////////

function replicationSync (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = options;
  localOptions.replication = true;
  localOptions.test = 'replication-sync';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                           serverOptions,
                           instanceInfo,
                           customInstanceInfos,
                           startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

function setup (testFns, defaultFns, opts, fnDocs, optionsDoc) {
  testFns['shell_replication'] = shellReplication;
  testFns['replication_ongoing'] = replicationOngoing;
  testFns['replication_static'] = replicationStatic;
  testFns['replication_sync'] = replicationSync;
  for (var attrname in functionsDocumentation) { fnDocs[attrname] = functionsDocumentation[attrname]; }
  for (var i = 0; i < optionsDocumentation.length; i++) { optionsDoc.push(optionsDocumentation[i]); }
}

exports.setup = setup;
<commit_msg>fix invalid testing.js behavior<commit_after>/* jshint strict: false, sub: true */
/* global */
'use strict';

// //////////////////////////////////////////////////////////////////////////////
// / DISCLAIMER
// /
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
// / Copyright 2014 triagens GmbH, Cologne, Germany
// /
// / Licensed under the Apache License, Version 2.0 (the "License")
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// /     http://www.apache.org/licenses/LICENSE-2.0
// /
// / Unless required by applicable law or agreed to in writing, software
// / distributed under the License is distributed on an "AS IS" BASIS,
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// / See the License for the specific language governing permissions and
// / limitations under the License.
// /
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
// /
// / @author Max Neunhoeffer
// //////////////////////////////////////////////////////////////////////////////

const functionsDocumentation = {
  'replication_ongoing': 'replication ongoing tests',
  'replication_static': 'replication static tests',
  'replication_sync': 'replication sync tests',
  'shell_replication': 'shell replication tests'
};
const optionsDocumentation = [
];

const _ = require('lodash');
const pu = require('@arangodb/process-utils');
const tu = require('@arangodb/test-utils');

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: shell_replication
// //////////////////////////////////////////////////////////////////////////////

function shellReplication (options) {
  let testCases = tu.scanTestPath('js/common/tests/replication');

  var opts = {
    'replication': true
  };
  _.defaults(opts, options);

  return tu.performTests(opts, testCases, 'shell_replication', tu.runThere);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_ongoing
// //////////////////////////////////////////////////////////////////////////////

function replicationOngoing (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = _.clone(options);
  localOptions.replication = true;
  localOptions.test = 'replication-ongoing';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                       serverOptions,
                       instanceInfo,
                       customInstanceInfos,
                       startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_static
// //////////////////////////////////////////////////////////////////////////////

function replicationStatic (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = _.clone(options);
  localOptions.replication = true;
  localOptions.test = 'replication-static';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.grantDatabase("replicator-user", "_system");
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                       serverOptions,
                       instanceInfo,
                       customInstanceInfos,
                       startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);
      return {};
    }
  };

  return tu.performTests(
    localOptions,
    testCases,
    'master_static',
    tu.runInArangosh,
    {
      'server.authentication': 'true'
    },
    startStopHandlers);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief TEST: replication_sync
// //////////////////////////////////////////////////////////////////////////////

function replicationSync (options) {
  let testCases = tu.scanTestPath('js/server/tests/replication/');

  let localOptions = _.clone(options);
  localOptions.replication = true;
  localOptions.test = 'replication-sync';
  let startStopHandlers = {
    postStart: function (options,
                         serverOptions,
                         instanceInfo,
                         customInstanceInfos,
                         startStopHandlers) {
      let message;
      let res = true;
      let slave = pu.startInstance('tcp', options, {}, 'slave_sync');
      let state = (typeof slave === 'object');

      if (state) {
        res = pu.run.arangoshCmd(options, instanceInfo, {}, [
          '--javascript.execute-string',
          `
          var users = require("@arangodb/users");
          users.save("replicator-user", "replicator-password", true);
          users.reload();
          `
        ]);

        state = res.status;
        if (!state) {
          message = 'failed to setup slave connection' + res.message;
          pu.shutdownInstance(slave, options);
        }
      } else {
        message = 'failed to start slave instance!';
      }

      return {
        instanceInfo: slave,
        message: message,
        state: state,
        env: {
          'flatCommands': slave.endpoint
        }
      };
    },

    preStop: function (options,
                       serverOptions,
                       instanceInfo,
                       customInstanceInfos,
                       startStopHandlers) {
      pu.shutdownInstance(customInstanceInfos.postStart.instanceInfo, options);

      return {};
    }
  };

  return tu.performTests(localOptions, testCases, 'replication_sync', tu.runInArangosh, {}, startStopHandlers);
}

function setup (testFns, defaultFns, opts, fnDocs, optionsDoc) {
  testFns['shell_replication'] = shellReplication;
  testFns['replication_ongoing'] = replicationOngoing;
  testFns['replication_static'] = replicationStatic;
  testFns['replication_sync'] = replicationSync;
  for (var attrname in functionsDocumentation) { fnDocs[attrname] = functionsDocumentation[attrname]; }
  for (var i = 0; i < optionsDocumentation.length; i++) { optionsDoc.push(optionsDocumentation[i]); }
}

exports.setup = setup;
<|endoftext|>


<commit_before>(function (env) {
    "use strict";
    env.ddg_spice_twitch_streams = function(api_result){

        if (!api_result || !api_result.streams || api_result.streams.length === 0 ||api_result.error) {
            return Spice.failed('twitch_streams');
        }

        var streams = [];
        if (!($.isArray(api_result.streams))) {
            streams = [api_result.streams];
        } else {
            streams = api_result.streams;
        }

        //Get query, split into an array on space, append query to the end for moreat
        var script = $('[src*="/js/spice/twitch_streams/"]')[0],
            source = $(script).attr("src"),
            query = source.match(/twitch_streams\/([^\/]+)/)[1],
            decodedQuery = decodeURIComponent(query);
        var moreAt = decodedQuery.replace(/ /g,"+");

        Spice.add({
            minItemsForModeSwitch: 3,
            id: "twitch_streams",
            name: "Twitch Streams",
            data: api_result.streams,
            meta: {
                sourceName: "twitch.tv",
                sourceUrl: 'http://www.twitch.tv/search?query=+' + moreAt,
            },
            templates: {
                group: 'base',
                detail: false,
                moreAt: true,
                options: {
                    content: Spice.twitch_streams.content,
                },
                variants: {
                    tile: 'xwide'
                }
            },
            //Results could be returned as an array, so the data is normalized for the view.
            normalize: function(stream) {
                if(stream.channel.logo != null){
                    var logo = stream.channel.logo;
                } else {
                    var logo = 'http://static-cdn.jtvnw.net/jtv-static/404_preview-300x300.png';
                }
                return {
                    url: stream.channel.url,
                    displayName: stream.channel.display_name,
                    viewers: stream.viewers,
                    logo: logo
                };
            }
        });
    };
}(this));
<commit_msg>Proxied the image.<commit_after>(function (env) {
    "use strict";
    env.ddg_spice_twitch_streams = function(api_result){

        if (!api_result || !api_result.streams || api_result.streams.length === 0 ||api_result.error) {
            return Spice.failed('twitch_streams');
        }

        var streams = [];
        if (!($.isArray(api_result.streams))) {
            streams = [api_result.streams];
        } else {
            streams = api_result.streams;
        }

        //Get query, split into an array on space, append query to the end for moreat
        var script = $('[src*="/js/spice/twitch_streams/"]')[0],
            source = $(script).attr("src"),
            query = source.match(/twitch_streams\/([^\/]+)/)[1],
            decodedQuery = decodeURIComponent(query);
        var moreAt = decodedQuery.replace(/ /g,"+");

        Spice.add({
            minItemsForModeSwitch: 3,
            id: "twitch_streams",
            name: "Twitch Streams",
            data: api_result.streams,
            meta: {
                sourceName: "twitch.tv",
                sourceUrl: 'http://www.twitch.tv/search?query=+' + moreAt,
            },
            templates: {
                group: 'base',
                detail: false,
                moreAt: true,
                options: {
                    content: Spice.twitch_streams.content,
                },
                variants: {
                    tile: 'xwide'
                }
            },
            normalize: function(stream) {
                if(stream.channel.logo != null){
                    var logo = "https://images.duckduckgo.com/iu/?u=" + encodeURIComponent(stream.channel.logo) + "&f=1";
                } else {
                    var defaultIcon = "http://static-cdn.jtvnw.net/jtv-static/404_preview-300x300.png";
                    var logo = "https://images.duckduckgo.com/iu/?u=" + encodeURIComponent(defaultIcon) + "&f=1";
                }
                return {
                    url: stream.channel.url,
                    displayName: stream.channel.display_name,
                    viewers: stream.viewers,
                    logo: logo
                };
            }
        });
    };
}(this));
<|endoftext|>


<commit_before>import path from 'path'
import ws from 'nodejs-websocket'
import log from './lib/log'
import wport from './lib/wport'
import cache from './cache'

let wsServer
let connected = false
let connections = []
let queue = []
let curError = null

function broadcast(data) {
  connections.forEach(conn => {
    conn.sendText(data)
  })
}

function runQueue() {
  if (queue.length && wsServer) {
    queue.forEach(broadcast)
    queue = [];
  }
}

function sendInitialMessages(conn) {
  conn.sendText(makeMessage('flint:baseDir', { dir: cache.baseDir() }))

  if (curError)
    conn.sendText(curError)
}

function cleanError(obj) {
  if (obj.error.fileName)
    obj.error.file = path.relative(cache.baseDir(), obj.error.fileName)

  return obj
}

function makeMessage(type, obj) {
  obj = obj || {}
  obj._type = type
  obj.timestamp = Date.now()

  // formatting
  switch(type) {
    case 'compile:error':
      obj = cleanError(obj)
      break
  }

  let msg = JSON.stringify(obj)

  return msg
}

export function message(type, obj) {
  log('-[socket msg]-', type)
  let msg = makeMessage(type, obj)

  // store last error or success
  if (type == 'compile:error' || type == 'compile:success') {
    curError = msg
  }

  if (connected)
    broadcast(msg)
  else
    queue.push(msg)
}

let listeners = {}
export function on(event, cb) {
  listeners[event] = listeners[event] || []
  listeners[event].push(cb)
}

// TODO: put this somewhere nice
on('editor', (msg) => {
  // TODO: send to editor
})

function runListeners(data) {
  let obj

  try {
    obj = JSON.parse(data)
  }
  catch(e) {
    console.error(e.stack)
    return
  }

  const { ...args, _type } = obj
  const ls = listeners[_type]
  if (!ls || !ls.length) return
  ls.forEach(l => l(args))
}

export function start() {
  const port = wport()

  wsServer = ws.createServer(conn => {
    connections.push(conn)

    conn.on('text', runListeners)
    conn.on('error', err => {})

    sendInitialMessages(conn)

    if (connected) return
    connected = true
    runQueue()
  }).listen(port)
}

export default { start, message, on }<commit_msg>re-emit message<commit_after>import path from 'path'
import ws from 'nodejs-websocket'
import log from './lib/log'
import wport from './lib/wport'
import cache from './cache'

let wsServer
let connected = false
let connections = []
let queue = []
let curError = null

function broadcast(data) {
  connections.forEach(conn => {
    conn.sendText(data)
  })
}

function runQueue() {
  if (queue.length && wsServer) {
    queue.forEach(broadcast)
    queue = [];
  }
}

function sendInitialMessages(conn) {
  conn.sendText(makeMessage('flint:baseDir', { dir: cache.baseDir() }))

  if (curError)
    conn.sendText(curError)
}

function cleanError(obj) {
  if (obj.error.fileName)
    obj.error.file = path.relative(cache.baseDir(), obj.error.fileName)

  return obj
}

function makeMessage(type, obj) {
  obj = obj || {}
  obj._type = type
  obj.timestamp = Date.now()

  // formatting
  switch(type) {
    case 'compile:error':
      obj = cleanError(obj)
      break
  }

  let msg = JSON.stringify(obj)

  return msg
}

export function message(type, obj) {
  log('-[socket msg]-', type)
  let msg = makeMessage(type, obj)

  // store last error or success
  if (type == 'compile:error' || type == 'compile:success') {
    curError = msg
  }

  if (connected)
    broadcast(msg)
  else
    queue.push(msg)
}

let listeners = {}
export function on(event, cb) {
  listeners[event] = listeners[event] || []
  listeners[event].push(cb)
}

// TODO: put this somewhere nice
on('editor', (msg) => {
  message('editor', msg)
})

function runListeners(data) {
  let obj

  try {
    obj = JSON.parse(data)
  }
  catch(e) {
    console.error(e.stack)
    return
  }

  const { ...args, _type } = obj
  const ls = listeners[_type]
  if (!ls || !ls.length) return
  ls.forEach(l => l(args))
}

export function start() {
  const port = wport()

  wsServer = ws.createServer(conn => {
    connections.push(conn)

    conn.on('text', runListeners)
    conn.on('error', err => {})

    sendInitialMessages(conn)

    if (connected) return
    connected = true
    runQueue()
  }).listen(port)
}

export default { start, message, on }<|endoftext|>


<commit_before>/**
 * @license Copyright 2019 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

const path = require('path');
const createHttpServer = require('http').createServer;
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const ApiClient = require('@lhci/utils/src/api-client.js');
const createProjectsRouter = require('./api/routes/projects.js');
const StorageMethod = require('./api/storage/storage-method.js');
const {errorMiddleware} = require('./api/express-utils.js');

const DIST_FOLDER = path.join(__dirname, '../dist');

/**
 * @param {LHCI.ServerCommand.Options} options
 * @return {Promise<Parameters<typeof createHttpServer>[0]>}
 */
async function createApp(options) {
  const {storage} = options;

  const storageMethod = StorageMethod.from(storage);
  await storageMethod.initialize(storage);

  const app = express();
  if (options.logLevel !== 'silent') app.use(morgan('short'));

  // 1. Support large payloads because LHRs are big.
  // 2. Support JSON primitives because `PUT /builds/<id>/lifecycle "sealed"`
  app.use(bodyParser.json({limit: '10mb', strict: false}));

  app.use('/v1/projects', createProjectsRouter({storageMethod}));
  app.use('/app', express.static(DIST_FOLDER));
  app.get('/app/*', (_, res) => res.sendFile(path.join(DIST_FOLDER, 'index.html')));
  app.use(errorMiddleware);

  return app;
}

/**
 * @param {LHCI.ServerCommand.Options} options
 * @return {Promise<{port: number, close: () => void}>}
 */
async function createServer(options) {
  const app = await createApp(options);

  return new Promise(resolve => {
    const server = createHttpServer(app);
    server.listen(options.port, () => {
      const serverAddress = server.address();
      const listenPort =
        typeof serverAddress === 'string' || !serverAddress ? options.port : serverAddress.port;
      resolve({port: listenPort, close: () => server.close()});
    });
  });
}

module.exports = {createApp, createServer, ApiClient};
<commit_msg>tests: use unambiguous typedef ref<commit_after>/**
 * @license Copyright 2019 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
'use strict';

const path = require('path');
const createHttpServer = require('http').createServer;
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const ApiClient = require('@lhci/utils/src/api-client.js');
const createProjectsRouter = require('./api/routes/projects.js');
const StorageMethod = require('./api/storage/storage-method.js');
const {errorMiddleware} = require('./api/express-utils.js');

const DIST_FOLDER = path.join(__dirname, '../dist');

/**
 * @param {LHCI.ServerCommand.Options} options
 * @return {Promise<Parameters<typeof createHttpServer>[1]>}
 */
async function createApp(options) {
  const {storage} = options;

  const storageMethod = StorageMethod.from(storage);
  await storageMethod.initialize(storage);

  const app = express();
  if (options.logLevel !== 'silent') app.use(morgan('short'));

  // 1. Support large payloads because LHRs are big.
  // 2. Support JSON primitives because `PUT /builds/<id>/lifecycle "sealed"`
  app.use(bodyParser.json({limit: '10mb', strict: false}));

  app.use('/v1/projects', createProjectsRouter({storageMethod}));
  app.use('/app', express.static(DIST_FOLDER));
  app.get('/app/*', (_, res) => res.sendFile(path.join(DIST_FOLDER, 'index.html')));
  app.use(errorMiddleware);

  return app;
}

/**
 * @param {LHCI.ServerCommand.Options} options
 * @return {Promise<{port: number, close: () => void}>}
 */
async function createServer(options) {
  const app = await createApp(options);

  return new Promise(resolve => {
    const server = createHttpServer(app);
    server.listen(options.port, () => {
      const serverAddress = server.address();
      const listenPort =
        typeof serverAddress === 'string' || !serverAddress ? options.port : serverAddress.port;
      resolve({port: listenPort, close: () => server.close()});
    });
  });
}

module.exports = {createApp, createServer, ApiClient};
<|endoftext|>