<commit_before>(function() {
  'use strict';

  angular.module(HygieiaConfig.module).controller('featureViewController',
    featureViewController);

  featureViewController.$inject = ['$scope', '$q', '$interval', 'featureData'];

  function featureViewController($scope, $q, $interval, featureData) {
    /* jshint validthis:true */
    var ctrl = this;
    var today = new Date(_.now());
    var filterTeamId = $scope.widgetConfig.options.teamId;
    var estimateMetricType = $scope.widgetConfig.options.estimateMetricType;
    ctrl.teamName = $scope.widgetConfig.options.teamName;
    // Scrum
    ctrl.iterations = [];
    ctrl.totalStoryPoints = null;
    ctrl.wipStoryPoints = null;
    ctrl.doneStoryPoints = null;
    ctrl.epicStoryPoints = null;
    // Kanban
    ctrl.iterationsKanban = [];
    ctrl.totalStoryPointsKanban = null;
    ctrl.wipStoryPointsKanban = null;
    ctrl.doneStoryPointsKanban = null;
    ctrl.epicStoryPointsKanban = null;

    // Public Evaluators
    ctrl.setFeatureLimit = setFeatureLimit;
    ctrl.showStatus = $scope.widgetConfig.options.showStatus;
    ctrl.animateAgileView = animateAgileView;
    ctrl.intervalOff = $scope.widgetConfig.options.intervalOff;
    var timeoutPromise = null;
    ctrl.changeDetect = null;
    ctrl.pauseAgileView = pauseAgileView;
    ctrl.pausePlaySymbol = "||";

    /**
     * Every controller must have a load method. It will be called every 60
     * seconds and should be where any calls to the data factory are made.
     * To have a last updated date show at the top of the widget it must
     * return a promise and then resolve it passing the lastUpdated
     * timestamp.
     */
    ctrl.load = function() {
      var deferred = $q.all([
        // Scrum
        featureData.total($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processTotalResponse),
        featureData.wip($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processWipResponse),
        featureData.done($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processDoneResponse),
        featureData.featureWip($scope.widgetConfig.componentId,
          filterTeamId, estimateMetricType).then(processFeatureWipResponse),
        featureData.sprint($scope.widgetConfig.componentId, filterTeamId)
          .then(processSprintResponse),

        // Kanban
        featureData.totalKanban($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processTotalKanbanResponse),
        featureData.wipKanban($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processWipKanbanResponse),
        featureData.featureWipKanban($scope.widgetConfig.componentId,
          filterTeamId, estimateMetricType).then(processFeatureWipKanbanResponse),
        featureData.sprintKanban($scope.widgetConfig.componentId, filterTeamId)
          .then(processSprintKanbanResponse)
      ]);

      deferred.then(function(){
        detectIterationChange();
      });
    };

    /**
     * Processor for total feature estimate totals
     *
     * @param data
     */
    function processTotalResponse(data) {
      ctrl.totalStoryPoints = data.result[0].sEstimate;
    }

    /**
     * Processor for total feature estimate totals for kanban only
     *
     * @param data
     */
    function processTotalKanbanResponse(data) {
      ctrl.totalStoryPointsKanban = data.result[0].sEstimate;
    }

    /**
     * Processor for in progress feature estimate in-progress
     *
     * @param data
     */
    function processWipResponse(data) {
      ctrl.wipStoryPoints = data.result[0].sEstimate;
    }

    /**
     * Processor for in progress feature estimate in-progress for kanban only
     *
     * @param data
     */
    function processWipKanbanResponse(data) {
      ctrl.wipStoryPointsKanban = data.result[0].sEstimate;
    }

    /**
     * Processor for done feature estimate in-progress
     *
     * @param data
     */
    function processDoneResponse(data) {
      ctrl.doneStoryPoints = data.result[0].sEstimate;
    }

    /**
     * Processor for super feature estimates in-progress. Also sets the
     * feature expander value based on the size of the data result set.
     *
     * @param data
     */
    function processFeatureWipResponse(data) {
      var epicCollection = [];

      for (var i = 0; i < data.result.length; i++) {
        epicCollection.push(data.result[i]);
      }

      if (data.result.length <= 4) {
        ctrl.showFeatureLimitButton = false;
      } else {
        ctrl.showFeatureLimitButton = true;
      }

      ctrl.epicStoryPoints = epicCollection.sort(compare).reverse();
    }

    /**
     * Processor for super feature estimates in-progress. Also sets the
     * feature expander value based on the size of the data result set
     * for kanban only.
     *
     * @param data
     */
    function processFeatureWipKanbanResponse(data) {
      var epicCollection = [];

      for (var i = 0; i < data.result.length; i++) {
        epicCollection.push(data.result[i]);
      }

      if (data.result.length <= 4) {
        ctrl.showFeatureLimitButton = false;
      } else {
        ctrl.showFeatureLimitButton = true;
      }

      ctrl.epicStoryPointsKanban = epicCollection.sort(compare).reverse();
    }

    /**
     * Processor for sprint-based data
     *
     * @param data
     */
    function processSprintResponse(data) {
      /*
       * Sprint Name
       */
      var sprintID = null;
      var sprintName = null;
      var daysTilEnd = null;
      var iteration = null;
      var dupes = true;
      // Reset on every processing
      ctrl.showStatus = $scope.widgetConfig.options.showStatus;
      ctrl.intervalOff = $scope.widgetConfig.options.intervalOff;

      for (var i = 0; i < data.result.length; i++) {
        if (data.result[i].sSprintID === undefined) {
          sprintID = "[No Sprint Available]";
          sprintName = "[No Sprint Available]";
        } else {
          sprintID = data.result[i].sSprintID;
          sprintName = data.result[i].sSprintName;
        }

        /*
         * Days Until Sprint Expires
         */
        if (data.result[i].sSprintID === undefined) {
          daysTilEnd = moment(today).dash();
          daysTilEnd = "[N/A]";
        } else if (data.result[i].sSprintID === "KANBAN") {
          daysTilEnd = "[Unlimited]";
        } else {
          var nativeSprintEndDate = new Date(
            data.result[i].sSprintEndDate);
          if (nativeSprintEndDate < today) {
            daysTilEnd = "[Ended]";
          } else {
            var nativeDaysTilEnd = moment(nativeSprintEndDate)
              .fromNow();
            daysTilEnd = nativeDaysTilEnd.substr(3);
          }
        }

        // Fill one iteration object at a time, starting with the first
        if (ctrl.iterations.length <= 0) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterations.push(iteration);
        }

        // Add iterations only if there are no duplicates
        if (isInArray(sprintID, ctrl.iterations) === false) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterations.push(iteration);
        }

        // Clean-up
        sprintID = null;
        sprintName = null;
        daysTilEnd = null;
        iteration = null;

      }

      // Check if iteration switching is needed
      if (ctrl.iterations.length < 1) {
        ctrl.showStatus.scrum = false;
        ctrl.intervalOff --;
      }
    }

    /**
     * Processor for sprint-based data for kanban
     *
     * @param data
     */
    function processSprintKanbanResponse(data) {
      /*
       * Sprint Name
       */
      var sprintID = null;
      var sprintName = null;
      var daysTilEnd = null;
      var iteration = null;
      var dupes = true;

      for (var i = 0; i < data.result.length; i++) {
        if (data.result[i].sSprintID === undefined) {
          sprintID = "[No Sprint Available]";
          sprintName = "[No Sprint Available]";
        } else {
          sprintID = data.result[i].sSprintID;
          sprintName = data.result[i].sSprintName;
        }

        /*
         * Days Until Sprint Expires
         */
        if (data.result[i].sSprintID === undefined) {
          daysTilEnd = moment(today).dash();
          daysTilEnd = "[N/A]";
        } else if (data.result[i].sSprintID === "KANBAN") {
          daysTilEnd = "[Unlimited]";
        } else {
          var nativeSprintEndDate = new Date(
            data.result[i].sSprintEndDate);
          if (nativeSprintEndDate < today) {
            daysTilEnd = "[Ended]";
          } else {
            var nativeDaysTilEnd = moment(nativeSprintEndDate)
              .fromNow();
            daysTilEnd = nativeDaysTilEnd.substr(3);
          }
        }

        // Fill one iteration object at a time, starting with the first
        if (ctrl.iterationsKanban.length <= 0) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterationsKanban.push(iteration);
        }

        // Add iterations only if there are no duplicates
        if (isInArray(sprintID, ctrl.iterationsKanban) === false) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterationsKanban.push(iteration);
        }

        // Clean-up
        sprintID = null;
        sprintName = null;
        daysTilEnd = null;
        iteration = null;
      }

      // Check if iteration switching is needed
      if (ctrl.iterationsKanban.length < 1) {
        ctrl.showStatus.kanban = false;
        ctrl.intervalOff --;
      }
    }

    /*
     * Checks iterations array for existing elements
     */
    function isInArray(timebox, iterations) {
      var dupe = false;

      iterations.forEach(function(timebox) {
        if (timebox.id === sprintID) {
          dupe = true;
        }
      });

      return dupe;
    }

    /**
     * Custom object comparison used exclusively by the
     * processFeatureWipResponse method; returns the comparison results for
     * an array sort function based on integer values of estimates.
     *
     * @param a
     *            Object containing sEstimate string value
     * @param b
     *            Object containing sEstimate string value
     */
    function compare(a, b) {
      if (parseInt(a.sEstimate) < parseInt(b.sEstimate))
        return -1;
      if (parseInt(a.sEstimate) > parseInt(b.sEstimate))
        return 1;
      return 0;
    }

    /**
     * This method is used to help expand and contract the ever-growing
     * super feature section on the Feature Widget
     */
    function setFeatureLimit() {
      var featureMinLimit = 4;
      var featureMaxLimit = 99;

      if (ctrl.featureLimit > featureMinLimit) {
        ctrl.featureLimit = featureMinLimit;
      } else {
        ctrl.featureLimit = featureMaxLimit;
      }
    }

    /**
     * Changes timeout boolean based on agile iterations available,
     * turning off the agile view switching if only one or none are
     * available
     */
    ctrl.startTimeout = function() {
      ctrl.stopTimeout();

      timeoutPromise = $interval(function() {
        if (ctrl.intervalOff === 2) {
          animateAgileView(true);
        } else if (ctrl.intervalOff === 1) {
          animateAgileView(false);
        }
      }, 7000);
    };

    /**
     * Stops the current agile iteration cycler promise
     */
    ctrl.stopTimeout = function() {
      $interval.cancel(timeoutPromise);
    };

    /**
     * Starts timeout cycle function by default
     */
    ctrl.startTimeout();

    /**
     * Triggered by the resolution of the data factory promises, iterations
     * types are detected from their resolutions and then initialized based
     * on data results.  This is a one time action per promise resolution.
     */
    function detectIterationChange () {
      animateAgileView(false);
    }

    /**
     * Animates agile view switching
     */
    function animateAgileView(multipleDetects) {
      switch (multipleDetects) {
        case true:
          // Swap Kanban
          if (ctrl.showStatus.kanban === false) {
            ctrl.showStatus.kanban = true;
          } else if (ctrl.showStatus.kanban === true) {
            ctrl.showStatus.kanban = false;
          }

          // Swap Scrum
          if (ctrl.showStatus.scrum === false) {
            ctrl.showStatus.scrum = true;
          } else if (ctrl.showStatus.scrum === true) {
            ctrl.showStatus.scrum = false;
          }
          break;
        case false:
          // Use case for clean up and one time loads
          if (ctrl.iterationsKanban.length >= 1) {
            ctrl.showStatus.kanban = true;
            ctrl.showStatus.scrum = false;
          } else if (ctrl.iterations.length >= 1) {
            ctrl.showStatus.scrum = true;
            ctrl.showStatus.kanban = false;
          }
          break;
        default:
          ctrl.showStatus.scrum = false;
          ctrl.showStatus.kanban = false;
          console.log("This shouldn't happen!  Please raise an issue on GitHub.");
      }
    }

    /**
     * Pauses agile view switching via manual button from user interaction
     */
    function pauseAgileView() {
      if (timeoutPromise.$$state.value === "canceled") {
        ctrl.pausePlaySymbol = "||";
        ctrl.startTimeout();
      } else {
        ctrl.pausePlaySymbol = ">";
        ctrl.stopTimeout();
      }
    }
  }
})();
<commit_msg>need to define SprintID<commit_after>(function() {
  'use strict';

  angular.module(HygieiaConfig.module).controller('featureViewController',
    featureViewController);

  featureViewController.$inject = ['$scope', '$q', '$interval', 'featureData'];

  function featureViewController($scope, $q, $interval, featureData) {
    /* jshint validthis:true */
    var ctrl = this;
    var today = new Date(_.now());
    var filterTeamId = $scope.widgetConfig.options.teamId;
    var estimateMetricType = $scope.widgetConfig.options.estimateMetricType;
    ctrl.teamName = $scope.widgetConfig.options.teamName;
    // Scrum
    ctrl.iterations = [];
    ctrl.totalStoryPoints = null;
    ctrl.wipStoryPoints = null;
    ctrl.doneStoryPoints = null;
    ctrl.epicStoryPoints = null;
    // Kanban
    ctrl.iterationsKanban = [];
    ctrl.totalStoryPointsKanban = null;
    ctrl.wipStoryPointsKanban = null;
    ctrl.doneStoryPointsKanban = null;
    ctrl.epicStoryPointsKanban = null;

    // Public Evaluators
    ctrl.setFeatureLimit = setFeatureLimit;
    ctrl.showStatus = $scope.widgetConfig.options.showStatus;
    ctrl.animateAgileView = animateAgileView;
    ctrl.intervalOff = $scope.widgetConfig.options.intervalOff;
    var timeoutPromise = null;
    ctrl.changeDetect = null;
    ctrl.pauseAgileView = pauseAgileView;
    ctrl.pausePlaySymbol = "||";

    /**
     * Every controller must have a load method. It will be called every 60
     * seconds and should be where any calls to the data factory are made.
     * To have a last updated date show at the top of the widget it must
     * return a promise and then resolve it passing the lastUpdated
     * timestamp.
     */
    ctrl.load = function() {
      var deferred = $q.all([
        // Scrum
        featureData.total($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processTotalResponse),
        featureData.wip($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processWipResponse),
        featureData.done($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processDoneResponse),
        featureData.featureWip($scope.widgetConfig.componentId,
          filterTeamId, estimateMetricType).then(processFeatureWipResponse),
        featureData.sprint($scope.widgetConfig.componentId, filterTeamId)
          .then(processSprintResponse),

        // Kanban
        featureData.totalKanban($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processTotalKanbanResponse),
        featureData.wipKanban($scope.widgetConfig.componentId, filterTeamId, estimateMetricType)
          .then(processWipKanbanResponse),
        featureData.featureWipKanban($scope.widgetConfig.componentId,
          filterTeamId, estimateMetricType).then(processFeatureWipKanbanResponse),
        featureData.sprintKanban($scope.widgetConfig.componentId, filterTeamId)
          .then(processSprintKanbanResponse)
      ]);

      deferred.then(function(){
        detectIterationChange();
      });
    };

    /**
     * Processor for total feature estimate totals
     *
     * @param data
     */
    function processTotalResponse(data) {
      ctrl.totalStoryPoints = data.result[0].sEstimate;
    }

    /**
     * Processor for total feature estimate totals for kanban only
     *
     * @param data
     */
    function processTotalKanbanResponse(data) {
      ctrl.totalStoryPointsKanban = data.result[0].sEstimate;
    }

    /**
     * Processor for in progress feature estimate in-progress
     *
     * @param data
     */
    function processWipResponse(data) {
      ctrl.wipStoryPoints = data.result[0].sEstimate;
    }

    /**
     * Processor for in progress feature estimate in-progress for kanban only
     *
     * @param data
     */
    function processWipKanbanResponse(data) {
      ctrl.wipStoryPointsKanban = data.result[0].sEstimate;
    }

    /**
     * Processor for done feature estimate in-progress
     *
     * @param data
     */
    function processDoneResponse(data) {
      ctrl.doneStoryPoints = data.result[0].sEstimate;
    }

    /**
     * Processor for super feature estimates in-progress. Also sets the
     * feature expander value based on the size of the data result set.
     *
     * @param data
     */
    function processFeatureWipResponse(data) {
      var epicCollection = [];

      for (var i = 0; i < data.result.length; i++) {
        epicCollection.push(data.result[i]);
      }

      if (data.result.length <= 4) {
        ctrl.showFeatureLimitButton = false;
      } else {
        ctrl.showFeatureLimitButton = true;
      }

      ctrl.epicStoryPoints = epicCollection.sort(compare).reverse();
    }

    /**
     * Processor for super feature estimates in-progress. Also sets the
     * feature expander value based on the size of the data result set
     * for kanban only.
     *
     * @param data
     */
    function processFeatureWipKanbanResponse(data) {
      var epicCollection = [];

      for (var i = 0; i < data.result.length; i++) {
        epicCollection.push(data.result[i]);
      }

      if (data.result.length <= 4) {
        ctrl.showFeatureLimitButton = false;
      } else {
        ctrl.showFeatureLimitButton = true;
      }

      ctrl.epicStoryPointsKanban = epicCollection.sort(compare).reverse();
    }

    /**
     * Processor for sprint-based data
     *
     * @param data
     */
    function processSprintResponse(data) {
      /*
       * Sprint Name
       */
      var sprintID = null;
      var sprintName = null;
      var daysTilEnd = null;
      var iteration = null;
      var dupes = true;
      // Reset on every processing
      ctrl.showStatus = $scope.widgetConfig.options.showStatus;
      ctrl.intervalOff = $scope.widgetConfig.options.intervalOff;

      for (var i = 0; i < data.result.length; i++) {
        if (data.result[i].sSprintID === undefined) {
          sprintID = "[No Sprint Available]";
          sprintName = "[No Sprint Available]";
        } else {
          sprintID = data.result[i].sSprintID;
          sprintName = data.result[i].sSprintName;
        }

        /*
         * Days Until Sprint Expires
         */
        if (data.result[i].sSprintID === undefined) {
          daysTilEnd = moment(today).dash();
          daysTilEnd = "[N/A]";
        } else if (data.result[i].sSprintID === "KANBAN") {
          daysTilEnd = "[Unlimited]";
        } else {
          var nativeSprintEndDate = new Date(
            data.result[i].sSprintEndDate);
          if (nativeSprintEndDate < today) {
            daysTilEnd = "[Ended]";
          } else {
            var nativeDaysTilEnd = moment(nativeSprintEndDate)
              .fromNow();
            daysTilEnd = nativeDaysTilEnd.substr(3);
          }
        }

        // Fill one iteration object at a time, starting with the first
        if (ctrl.iterations.length <= 0) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterations.push(iteration);
        }

        // Add iterations only if there are no duplicates
        if (isInArray(sprintID, ctrl.iterations) === false) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterations.push(iteration);
        }

        // Clean-up
        sprintID = null;
        sprintName = null;
        daysTilEnd = null;
        iteration = null;

      }

      // Check if iteration switching is needed
      if (ctrl.iterations.length < 1) {
        ctrl.showStatus.scrum = false;
        ctrl.intervalOff --;
      }
    }

    /**
     * Processor for sprint-based data for kanban
     *
     * @param data
     */
    function processSprintKanbanResponse(data) {
      /*
       * Sprint Name
       */
      var sprintID = null;
      var sprintName = null;
      var daysTilEnd = null;
      var iteration = null;
      var dupes = true;

      for (var i = 0; i < data.result.length; i++) {
        if (data.result[i].sSprintID === undefined) {
          sprintID = "[No Sprint Available]";
          sprintName = "[No Sprint Available]";
        } else {
          sprintID = data.result[i].sSprintID;
          sprintName = data.result[i].sSprintName;
        }

        /*
         * Days Until Sprint Expires
         */
        if (data.result[i].sSprintID === undefined) {
          daysTilEnd = moment(today).dash();
          daysTilEnd = "[N/A]";
        } else if (data.result[i].sSprintID === "KANBAN") {
          daysTilEnd = "[Unlimited]";
        } else {
          var nativeSprintEndDate = new Date(
            data.result[i].sSprintEndDate);
          if (nativeSprintEndDate < today) {
            daysTilEnd = "[Ended]";
          } else {
            var nativeDaysTilEnd = moment(nativeSprintEndDate)
              .fromNow();
            daysTilEnd = nativeDaysTilEnd.substr(3);
          }
        }

        // Fill one iteration object at a time, starting with the first
        if (ctrl.iterationsKanban.length <= 0) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterationsKanban.push(iteration);
        }

        // Add iterations only if there are no duplicates
        if (isInArray(sprintID, ctrl.iterationsKanban) === false) {
          iteration = {
            id: sprintID,
            name: sprintName,
            tilEnd: daysTilEnd
          };
          ctrl.iterationsKanban.push(iteration);
        }

        // Clean-up
        sprintID = null;
        sprintName = null;
        daysTilEnd = null;
        iteration = null;
      }

      // Check if iteration switching is needed
      if (ctrl.iterationsKanban.length < 1) {
        ctrl.showStatus.kanban = false;
        ctrl.intervalOff --;
      }
    }

    /*
     * Checks iterations array for existing elements
     */
    function isInArray(timebox, iterations) {
      var dupe = false;
      var sprintID = timebox;

      iterations.forEach(function(timebox) {
        if (timebox.id === sprintID) {
          dupe = true;
        }
      });

      return dupe;
    }

    /**
     * Custom object comparison used exclusively by the
     * processFeatureWipResponse method; returns the comparison results for
     * an array sort function based on integer values of estimates.
     *
     * @param a
     *            Object containing sEstimate string value
     * @param b
     *            Object containing sEstimate string value
     */
    function compare(a, b) {
      if (parseInt(a.sEstimate) < parseInt(b.sEstimate))
        return -1;
      if (parseInt(a.sEstimate) > parseInt(b.sEstimate))
        return 1;
      return 0;
    }

    /**
     * This method is used to help expand and contract the ever-growing
     * super feature section on the Feature Widget
     */
    function setFeatureLimit() {
      var featureMinLimit = 4;
      var featureMaxLimit = 99;

      if (ctrl.featureLimit > featureMinLimit) {
        ctrl.featureLimit = featureMinLimit;
      } else {
        ctrl.featureLimit = featureMaxLimit;
      }
    }

    /**
     * Changes timeout boolean based on agile iterations available,
     * turning off the agile view switching if only one or none are
     * available
     */
    ctrl.startTimeout = function() {
      ctrl.stopTimeout();

      timeoutPromise = $interval(function() {
        if (ctrl.intervalOff === 2) {
          animateAgileView(true);
        } else if (ctrl.intervalOff === 1) {
          animateAgileView(false);
        }
      }, 7000);
    };

    /**
     * Stops the current agile iteration cycler promise
     */
    ctrl.stopTimeout = function() {
      $interval.cancel(timeoutPromise);
    };

    /**
     * Starts timeout cycle function by default
     */
    ctrl.startTimeout();

    /**
     * Triggered by the resolution of the data factory promises, iterations
     * types are detected from their resolutions and then initialized based
     * on data results.  This is a one time action per promise resolution.
     */
    function detectIterationChange () {
      animateAgileView(false);
    }

    /**
     * Animates agile view switching
     */
    function animateAgileView(multipleDetects) {
      switch (multipleDetects) {
        case true:
          // Swap Kanban
          if (ctrl.showStatus.kanban === false) {
            ctrl.showStatus.kanban = true;
          } else if (ctrl.showStatus.kanban === true) {
            ctrl.showStatus.kanban = false;
          }

          // Swap Scrum
          if (ctrl.showStatus.scrum === false) {
            ctrl.showStatus.scrum = true;
          } else if (ctrl.showStatus.scrum === true) {
            ctrl.showStatus.scrum = false;
          }
          break;
        case false:
          // Use case for clean up and one time loads
          if (ctrl.iterationsKanban.length >= 1) {
            ctrl.showStatus.kanban = true;
            ctrl.showStatus.scrum = false;
          } else if (ctrl.iterations.length >= 1) {
            ctrl.showStatus.scrum = true;
            ctrl.showStatus.kanban = false;
          }
          break;
        default:
          ctrl.showStatus.scrum = false;
          ctrl.showStatus.kanban = false;
          console.log("This shouldn't happen!  Please raise an issue on GitHub.");
      }
    }

    /**
     * Pauses agile view switching via manual button from user interaction
     */
    function pauseAgileView() {
      if (timeoutPromise.$$state.value === "canceled") {
        ctrl.pausePlaySymbol = "||";
        ctrl.startTimeout();
      } else {
        ctrl.pausePlaySymbol = ">";
        ctrl.stopTimeout();
      }
    }
  }
})();
<|endoftext|>


<commit_before>(function () {
  'use strict';

  function ready () {
    $.removeNodes('iframe, #adblock_detect');

    var node = $.$('#continuetoimage > form input');
    if (node) {
      // first pass
      setTimeout(function () {
        node.removeAttribute('disabled');
        node.click();
      }, 1000);
      return;
    }

    // second pass
    var i = $('img[class^=centred]');
    $.openImage(i.src);
  }

  $.register({
    rule: [
      {
        host: [
          // com
          // starts with image
          /^image(ontime|corn|picsa)\.com$/,
          // ends with image
          /^(zonezeed|zelje|croft|myhot|bok|hostur|greasy)image\.com$/,
          // starts with img
          /^img(next|savvy|\.spicyzilla|twyti|xyz|devil|tzar|ban|pu|beer|wet|tornado|kicks|-pay|nimz|binbou|2share|22)\.com$/,
          // starts with img-
          /^img-(zone|planet)\.com$/,
          // starts with www
          /^www\.(imagefolks|img(blow|lemon))\.com$/,
          // starts with xxx
          /^xxx(\.pornprimehd|imagenow|screens)\.com$/,
          // else
          /^(picstwist|ericsony|wpc8|uplimg|lexiit|thumbnailus|newimagepost|fapingpics)\.com$/,
          /^((i|hentai)\.)?imgslip\.com$/,
          /^(i|xxx)\.hentaiyoutube\.com$/,
          /^(go|er)imge\.com$/,
          /^(like\.)?08lkk\.com$/,
          /^(www\.)?\.imgult\.com$/,
          /^nim(plus|zshare)\.com$/,
          /^nudeximg\.com$/,
          /imgseeds\.com$/,
          /damimage\.com$/,
          /imagedecode\.com$/,
          // net
          /^img(serve|coin|fap|candy|master|-view|run|boom|project|python)\.net$/,
          /^(gallerycloud|imagelaser|project-photo|pix-link|funimg|golfpit)\.net$/,
          // org
          /^shotimg\.org$/,
          /^img(studio|spot)\.org$/,
          /^image(\.adlock|on|team)\.org$/,
          /^(drag|teen|mega)image\.org$/,
          /^teenshot\.org$/,
          // else
          /^(hotimages|55888)\.eu$/,
          /^img(cloud|mag)\.co$/,
          /^pixup\.us$/,
          /^(bulkimg|photo-up)\.info$/,
          /^img\.yt$/,
          /^vava\.in$/,
          /^(pixxx|picspornfree|imgload)\.me$/,
          /^(porno-pirat|24avarii|loftlm)\.ru$/,
          /^hotimage\.uk$/,
          /^imgease\.re$/,
          /^goimg\.xyz$/,
          /^pic2pic\.site$/,
          /^darpix\.ga$/,
          /^sxpics\.nl$/,
        ],
        path: /^\/img-.*\.html$/,
      },
      {
        host: [
          /^img(run|twyti)\.net$/,
          /^imgtwyti\.com$/,
        ],
        path: /^\/t\/img-.*\.html$/,
      },
      {
        host: /^imgking\.co$/,
        path: /^\/img-.*\.htmls?$/,
      },
      {
        host: /^imgbb\.net$/,
        path: /^\/.-.+$/,
      },
    ],
    ready: ready,
  });

  // TODO need to refactor the cookie rule
  $.register({
    rule: {
      host: [
        /^www\.img(taxi|adult)\.com$/,
        /^www\.imgdrive\.net$/,
      ],
      path: /^\/img-.*\.html$/,
    },
    start: function () {
      var c = $.getCookie('img_c_d') || $.getCookie('img_p_d');
      if (c) {
        return;
      }
      $.post(window.location.href.toString(), {
        cti: 1,
        ref: '',
        rc: 1,
      }).then(function (data) {
        window.location.reload();
      });
    },
    ready: function () {
      $.removeNodes('iframe');

      var node = $.$('#continuetoimage > form input');
      if (node) {
        // first pass
        node.click();
        // somehow imgrun.net need to click twice
        node.click();
        return;
      }

      // the cookies are shared in the whole domain
      // we have to reset cookies to prevent wrong state
      $.resetCookies();

      // second pass
      var i = $('img[class^=centred]');
      $.openImage(i.src);
    },
  });

  function helper () {
    // crack the shitty qqc.co visitScript 5440
    $.window.setTimeout = _.nop;

    // this site checks cookie that caculate from session
    // do an AJAX to skip checking
    return $.get(window.location.toString()).then(function (data) {
      return $.toDOM(data);
    });
  }

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/Photo\/img-.+\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var i = $('img[class^=centred]', page);
        $.openImage(i.src);
      });
    },
  });

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/\d+\/img-.*\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var bbcode = $.$('#imagecodes input', page);
        bbcode = bbcode.value.match(/.+\[IMG\]([^\[]+)\[\/IMG\].+/);
        bbcode = bbcode[1];
        bbcode = bbcode.replace('small', 'big');

        $.openImage(bbcode);
      });
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<commit_msg>xximg.net<commit_after>(function () {
  'use strict';

  function ready () {
    $.removeNodes('iframe, #adblock_detect');

    var node = $.$('#continuetoimage > form input');
    if (node) {
      // first pass
      setTimeout(function () {
        node.removeAttribute('disabled');
        node.click();
      }, 1000);
      return;
    }

    // second pass
    var i = $('img[class^=centred]');
    $.openImage(i.src);
  }

  $.register({
    rule: [
      {
        host: [
          // com
          // starts with image
          /^image(ontime|corn|picsa)\.com$/,
          // ends with image
          /^(zonezeed|zelje|croft|myhot|bok|hostur|greasy)image\.com$/,
          // starts with img
          /^img(next|savvy|\.spicyzilla|twyti|xyz|devil|tzar|ban|pu|beer|wet|tornado|kicks|-pay|nimz|binbou|2share|22)\.com$/,
          // starts with img-
          /^img-(zone|planet)\.com$/,
          // starts with www
          /^www\.(imagefolks|img(blow|lemon))\.com$/,
          // starts with xxx
          /^xxx(\.pornprimehd|imagenow|screens)\.com$/,
          // else
          /^(picstwist|ericsony|wpc8|uplimg|lexiit|thumbnailus|newimagepost|fapingpics)\.com$/,
          /^((i|hentai)\.)?imgslip\.com$/,
          /^(i|xxx)\.hentaiyoutube\.com$/,
          /^(go|er)imge\.com$/,
          /^(like\.)?08lkk\.com$/,
          /^(www\.)?\.imgult\.com$/,
          /^nim(plus|zshare)\.com$/,
          /^nudeximg\.com$/,
          /imgseeds\.com$/,
          /damimage\.com$/,
          /imagedecode\.com$/,
          // net
          /^img(serve|coin|fap|candy|master|-view|run|boom|project|python)\.net$/,
          /^(gallerycloud|imagelaser|project-photo|pix-link|funimg|golfpit|xximg)\.net$/,
          // org
          /^shotimg\.org$/,
          /^img(studio|spot)\.org$/,
          /^image(\.adlock|on|team)\.org$/,
          /^(drag|teen|mega)image\.org$/,
          /^teenshot\.org$/,
          // else
          /^(hotimages|55888)\.eu$/,
          /^img(cloud|mag)\.co$/,
          /^pixup\.us$/,
          /^(bulkimg|photo-up)\.info$/,
          /^img\.yt$/,
          /^vava\.in$/,
          /^(pixxx|picspornfree|imgload)\.me$/,
          /^(porno-pirat|24avarii|loftlm)\.ru$/,
          /^hotimage\.uk$/,
          /^imgease\.re$/,
          /^goimg\.xyz$/,
          /^pic2pic\.site$/,
          /^darpix\.ga$/,
          /^sxpics\.nl$/,
        ],
        path: /^\/img-.*\.html$/,
      },
      {
        host: [
          /^img(run|twyti)\.net$/,
          /^imgtwyti\.com$/,
        ],
        path: /^\/t\/img-.*\.html$/,
      },
      {
        host: /^imgking\.co$/,
        path: /^\/img-.*\.htmls?$/,
      },
      {
        host: /^imgbb\.net$/,
        path: /^\/.-.+$/,
      },
    ],
    ready: ready,
  });

  // TODO need to refactor the cookie rule
  $.register({
    rule: {
      host: [
        /^www\.img(taxi|adult)\.com$/,
        /^www\.imgdrive\.net$/,
      ],
      path: /^\/img-.*\.html$/,
    },
    start: function () {
      var c = $.getCookie('img_c_d') || $.getCookie('img_p_d');
      if (c) {
        return;
      }
      $.post(window.location.href.toString(), {
        cti: 1,
        ref: '',
        rc: 1,
      }).then(function (data) {
        window.location.reload();
      });
    },
    ready: function () {
      $.removeNodes('iframe');

      var node = $.$('#continuetoimage > form input');
      if (node) {
        // first pass
        node.click();
        // somehow imgrun.net need to click twice
        node.click();
        return;
      }

      // the cookies are shared in the whole domain
      // we have to reset cookies to prevent wrong state
      $.resetCookies();

      // second pass
      var i = $('img[class^=centred]');
      $.openImage(i.src);
    },
  });

  function helper () {
    // crack the shitty qqc.co visitScript 5440
    $.window.setTimeout = _.nop;

    // this site checks cookie that caculate from session
    // do an AJAX to skip checking
    return $.get(window.location.toString()).then(function (data) {
      return $.toDOM(data);
    });
  }

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/Photo\/img-.+\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var i = $('img[class^=centred]', page);
        $.openImage(i.src);
      });
    },
  });

  $.register({
    rule: {
      host: /^08lkk\.com$/,
      path: /^\/\d+\/img-.*\.html$/,
    },
    start: function () {
      helper().then(function (page) {
        var bbcode = $.$('#imagecodes input', page);
        bbcode = bbcode.value.match(/.+\[IMG\]([^\[]+)\[\/IMG\].+/);
        bbcode = bbcode[1];
        bbcode = bbcode.replace('small', 'big');

        $.openImage(bbcode);
      });
    },
  });

})();

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<|endoftext|>


<commit_before>'use babel'
/* eslint-env jasmine */

let temp = require('temp').track()
import path from 'path'
import fs from 'fs-plus'
import { conditionPromise, waitForAutocomplete } from './spec-helper'

describe('Autocomplete Manager', () => {
  let [directory, filePath, completionDelay, editorView, editor, mainModule, autocompleteManager] = []

  beforeEach(async () => {
    jasmine.useRealClock()

    directory = temp.mkdirSync()
    let sample = `var quicksort = function () {
var sort = function(items) {
  if (items.length <= 1) return items;
  var pivot = items.shift(), current, left = [], right = [];
  while(items.length > 0) {
    current = items.shift();
    current < pivot ? left.push(current) : right.push(current);
  }
  return sort(left).concat(pivot).concat(sort(right));
};

return sort(Array.apply(this, arguments));
};
`
    filePath = path.join(directory, 'sample.js')
    fs.writeFileSync(filePath, sample)

    // Enable autosave
    atom.config.set('autosave.enabled', true)

    // Set to live completion
    atom.config.set('autocomplete-plus.enableAutoActivation', true)
    atom.config.set('editor.fontSize', '16')

    // Set the completion delay
    completionDelay = 100
    atom.config.set('autocomplete-plus.autoActivationDelay', completionDelay)
    completionDelay += 100 // Rendering

    let workspaceElement = atom.views.getView(atom.workspace)
    jasmine.attachToDOM(workspaceElement)

    await atom.packages.activatePackage('autosave')

    editor = await atom.workspace.open(filePath)
    editorView = atom.views.getView(editor)

    await atom.packages.activatePackage('language-javascript')

    // Activate the package
    mainModule = (await atom.packages.activatePackage('autocomplete-plus')).mainModule

    await conditionPromise(() => {
      if (!mainModule || !mainModule.autocompleteManager) {
        return false
      }
      return mainModule.autocompleteManager.ready
    })

    autocompleteManager = mainModule.autocompleteManager
    let { displaySuggestions } = autocompleteManager
    spyOn(autocompleteManager, 'displaySuggestions').andCallFake((suggestions, options) => {
      displaySuggestions(suggestions, options)
    })
  })

  describe('autosave compatibility', () =>
    it('keeps the suggestion list open while saving', async () => {
      expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
      // Trigger an autocompletion
      editor.moveToBottom()
      editor.moveToBeginningOfLine()
      editor.insertText('f')
      await waitForAutocomplete(editor)

      editor.save()
      expect(editorView.querySelector('.autocomplete-plus')).toExist()
      editor.insertText('u')
      await waitForAutocomplete(editor)

      editor.save()
      expect(editorView.querySelector('.autocomplete-plus')).toExist()
      // Accept suggestion
      let suggestionListView = autocompleteManager.suggestionList.suggestionListElement
      atom.commands.dispatch(suggestionListView.element, 'autocomplete-plus:confirm')
      expect(editor.getBuffer().getLastLine()).toEqual('function')
    })
  )
})
<commit_msg>Make autocomplete test more reliable<commit_after>'use babel'
/* eslint-env jasmine */

let temp = require('temp').track()
import path from 'path'
import fs from 'fs-plus'
import { conditionPromise } from './spec-helper'

describe('Autocomplete Manager', () => {
  let directory
  let filePath
  let editorView
  let editor
  let mainModule
  let autocompleteManager
  let createSuggestionsPromise

  beforeEach(async () => {
    jasmine.useRealClock()

    directory = temp.mkdirSync()
    let sample = `var quicksort = function () {
var sort = function(items) {
  if (items.length <= 1) return items;
  var pivot = items.shift(), current, left = [], right = [];
  while(items.length > 0) {
    current = items.shift();
    current < pivot ? left.push(current) : right.push(current);
  }
  return sort(left).concat(pivot).concat(sort(right));
};

return sort(Array.apply(this, arguments));
};
`
    filePath = path.join(directory, 'sample.js')
    fs.writeFileSync(filePath, sample)

    // Enable autosave
    atom.config.set('autosave.enabled', true)

    // Set to live completion
    atom.config.set('autocomplete-plus.enableAutoActivation', true)
    atom.config.set('editor.fontSize', '16')

    let workspaceElement = atom.views.getView(atom.workspace)
    jasmine.attachToDOM(workspaceElement)

    await atom.packages.activatePackage('autosave')

    editor = await atom.workspace.open(filePath)
    editorView = atom.views.getView(editor)

    await atom.packages.activatePackage('language-javascript')

    // Activate the package
    mainModule = (await atom.packages.activatePackage('autocomplete-plus')).mainModule

    await conditionPromise(() =>
      mainModule && mainModule.autocompleteManager && mainModule.autocompleteManager.ready
    )

    autocompleteManager = mainModule.autocompleteManager
    let { displaySuggestions } = autocompleteManager

    const suggestionsPromises = new Set()

    createSuggestionsPromise = function () {
      return new Promise(resolve => {
        suggestionsPromises.add(resolve)
      })
    }

    spyOn(autocompleteManager, 'displaySuggestions').andCallFake((suggestions, options) => {
      displaySuggestions(suggestions, options)
      for (const resolve of suggestionsPromises) {
        resolve()
      }
      suggestionsPromises.clear()
    })
  })

  describe('autosave compatibility', () =>
    it('keeps the suggestion list open while saving', async () => {
      expect(editorView.querySelector('.autocomplete-plus')).not.toExist()
      // Trigger an autocompletion
      const firstEventPromise = createSuggestionsPromise()

      editor.moveToBottom()
      editor.moveToBeginningOfLine()
      editor.insertText('f')
      await firstEventPromise

      const secondEventPromise = createSuggestionsPromise()
      editor.save()
      expect(editorView.querySelector('.autocomplete-plus')).toExist()
      editor.insertText('u')
      await secondEventPromise

      editor.save()
      expect(editorView.querySelector('.autocomplete-plus')).toExist()
      // Accept suggestion
      let suggestionListView = autocompleteManager.suggestionList.suggestionListElement
      atom.commands.dispatch(suggestionListView.element, 'autocomplete-plus:confirm')
      expect(editor.getBuffer().getLastLine()).toEqual('function')
    })
  )
})
<|endoftext|>


<commit_before>/**
 * Gookie
 * Lightweight & configurable GitHub Webhook Server
 * Created by Liam Gray (@hoxxep)
 * Released under the MIT License.
 */

var exec = require('child_process').exec,
    path = require('path'),
    winston = require('winston'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    express = require('express'),
    app = express(),
    logger;

var repos;

function main() {
    /**
     * Load config and start serving
     * TODO: add support for multiple events
     * TODO: add special thing for ping event
     */

    var config = loadConfig();

    process.argv.forEach(function (val, index, array) {
        if (val === '-q' || val === '--quiet') {
            logger.transports.console.level = 'warn';
        } else if (val === '-v' || val === '--verbose') {
            logger.transports.console.level = 'verbose';
            logger.transports.file.level = 'verbose';
        }
    });

    if (config.repositories) server(config.port);
}

function loadConfig() {
    /**
     * Load users settings and add defaults
     * @type {{port: number, repositories: Array}}
     * @return config merged with defaults
     */

    var defaults = {
        "port": 8001,
        "log": {
            "directory": ""
        },
        "repositories": [
            {
                "url": "https://github.com/user/repo",
                "path": "/home/user/local/git/repo",
                "deploy": "git pull",
                "secret": ""
            }
        ]
    };

    var config = require('./config.json');
    config = merge(defaults, config);

    repos = {};
    for (var repo in config['repositories']) {
        var r = config['repositories'][repo];
        r.url = formatUrl(r.url);
        repos[r.url] = merge(defaults.repositories[0], r);
    }

    logger = newLogger(config.log.directory, 'gookie.log');

    return defaults;
}

function newLogger(folder, logfile) {
    /**
     * Create Winston logger for specified filename and console
     * @param filename: name of log to write to (excluding .log)
     * @return a winston logger
     */
    var filename = folder ? path.join(folder, logfile) : logfile;

    return new (winston.Logger)({
        transports: [
            new (winston.transports.Console)({
                name: 'console',
                json: false,
                colorize: true,
                level: 'info'
            }),
            new (winston.transports.File)({
                name: 'file',
                //datePattern: '.yyyy-MM-dd',
                filename: filename,
                timestamp: false,
                json: false,
                level: 'info'
            })
        ]
    })
}

function merge(object1, object2) {
    /**
     * Merge attributes of two objects
     * @param object1: default object
     * @param object2: customised object
     * @type {{}}
     * @return object1 overwritten by object2
     */
    var attr,
        object3 = {};

    for (attr in object1) {object3[attr] = object1[attr]}
    for (attr in object2) {
        if (object1.hasOwnProperty(attr) && object2[attr] instanceof Array) {
            object3[attr] = arrayMerge(object1[attr], object2[attr]);
        } else if (object1.hasOwnProperty(attr) && object2[attr] instanceof Object) {
            object3[attr] = merge(object1[attr], object2[attr]);
        } else {
            object3[attr] = object2[attr];
        }
    }
    return object3;
}

function arrayMerge(array1, array2) {
    /**
     * For merging repository array
     * @param array1: default array
     * @param array1: customised array
     * @type {Array}
     * @return array2 after its sub-Arrays/Objects overwritten by array1[0]
     */
    var array3 = [];
    for (var i=0; i<array2.length; i++) {
        if (array2[i] instanceof Array) {
            array3 = array3.concat(arrayMerge(array1[0], array2[i]));
        } else if (array2[i] instanceof Object) {
            array3 = array3.concat(merge(array1[0], array2[i]));
        } else {
            array3 = array3.concat(array2[i]);
        }
    }
    return array3;
}

function server(port) {
    /**
     * Start listening for hook
     * @param port: port to start Gookie server on
     */
    app.use(bodyParser.json({extended: false}));
    app.use(methodOverride());

    // error function
    app.use(function(err, req, res, next) {
        logger.error(timePrefix() + 'Error occured: ' + err);
        res.status(500).send('Bad request').end();
    });

    app.route('/')
        .get(function(req, res) {
            logger.info(timePrefix() + 'GET request sent from ' + req.connection.remoteAddress);
            res.status(200).send('<title>Gookie</title>
Your Gookie server is running.
').end();
        })
        .post(function(req, res) {
            try {
                req.body.repository.url = formatUrl(req.body.repository.url);
                validateRequest(req.body);

                if (req.body.hasOwnProperty('zen')) {
                    logger.info('Ping event from ' + req.body.repository.url + ' with zen: ' + req.body['zen']);
                    res.status(204).end();
                    return;
                }

                logger.verbose(timePrefix() + 'user ' + req.body.pusher.name + ' pushed to ' + req.body.repository.url);

                res.status(204).end();
            } catch (e) {
                logger.error(timePrefix() + e);
                res.status(400).send('Bad request.').end();
                return;
            }

            matchRepo(req.body);
        });

    app.listen(port);
    logger.info(timePrefix() + 'Server started on port ' + port);
}

function validateRequest(json) {
    /**
     * Validate GitHub JSON by checking repo matches etc.
     * TODO: support for secret
     * @param json: decoded JSON request body
     * @throws error when invalid JSON
     */

    if (!json.hasOwnProperty('repository') || !json.repository.hasOwnProperty('url')) {
        throw 'ERROR: invalid JSON!';
    }
    if (!repos.hasOwnProperty(json.repository.url)) {
        throw 'ERROR: ' + json.repository.url + ' just sent a webhook but is not configured?';
    }
}

function matchRepo(json) {
    /**
     * Match repo in JSON to deploy command
     * @param json: decoded JSON request body
     */
    var repo = repos[json.repository.url];
    deploy(repo.path, repo.deploy);
}

function deploy(directory, command) {
    /**
     * Run deploy command in git repo
     * Execs: cd "directory" && command
     * @param directory: directory of git repo
     * @param command: deploy command to run
     */
    command = 'cd "' + directory + '" && ' + command;
    logger.verbose(timePrefix() + command);
    exec(command, function(error, stdout, stderr) {
        if (stdout) logger.info(timePrefix() + 'Deploying in ' + directory + '\n' + stdout);
        if (stderr) logger.error(timePrefix() + 'Deploy error in ' + directory + '\n' + stderr);
        if (error) logger.error(timePrefix() + 'Deploy error in ' + directory + '\n' + error);
        logger.verbose(timePrefix() + 'end of deploy output');
    });
}

function timePrefix() {
    /**
     * Time prefix for console.log
     * @return prefix in the form of ' [hh:mm:ss] '
     */
    var date = new Date();

    var hour = date.getHours();
    hour = (hour < 10 ? "0" : "") + hour;

    var min  = date.getMinutes();
    min = (min < 10 ? "0" : "") + min;

    var sec  = date.getSeconds();
    sec = (sec < 10 ? "0" : "") + sec;

    return ' [' + hour + ':' + min + ':' + sec + '] ';
}

function formatUrl(url) {
    /**
     * Get user and repo name from url
     * @param url: url in string format
     * @type String
     * @return formatted github url like: https://github.com/user/repo
     */
    var match = url.match(/https?:\/\/(?:(?:www|api)\.)?github\.com\/(?:repos\/)?([a-zA-Z0-9\-]+)\/([a-zA-Z0-9\-]+)\/?/);
    try {
        return 'https://github.com/' + match[1].toLowerCase() + '/' + match[2].toLowerCase();
    } catch (e) {
        throw 'Invalid repo URL ' + url;
    }
}

main();<commit_msg>Added support for GitHub secret<commit_after>/**
 * Gookie
 * Lightweight & configurable GitHub Webhook Server
 * Created by Liam Gray (@hoxxep)
 * Released under the MIT License.
 */

var exec = require('child_process').exec,
    path = require('path'),
    winston = require('winston'),
    crypto = require('crypto'),
    bodyParser = require('body-parser'),
    methodOverride = require('method-override'),
    express = require('express'),
    app = express(),
    logger;

var repos;

function main() {
    /**
     * Load config and start serving
     * TODO: add support for multiple events
     * TODO: add special thing for ping event
     */

    var config = loadConfig();

    process.argv.forEach(function (val, index, array) {
        if (val === '-q' || val === '--quiet') {
            logger.transports.console.level = 'warn';
        } else if (val === '-v' || val === '--verbose') {
            logger.transports.console.level = 'verbose';
            logger.transports.file.level = 'verbose';
        }
    });

    if (config.repositories) server(config.port);
}

function loadConfig() {
    /**
     * Load users settings and add defaults
     * @type {{port: number, repositories: Array}}
     * @return config merged with defaults
     */

    var defaults = {
        "port": 8001,
        "log": {
            "directory": ""
        },
        "repositories": [
            {
                "url": "https://github.com/user/repo",
                "path": "/home/user/local/git/repo",
                "deploy": "git pull",
                "secret": ""
            }
        ]
    };

    var config = require('./config.json');
    config = merge(defaults, config);

    repos = {};
    for (var repo in config['repositories']) {
        var r = config['repositories'][repo];
        r.url = formatUrl(r.url);
        repos[r.url] = merge(defaults.repositories[0], r);
    }

    logger = newLogger(config.log.directory, 'gookie.log');

    return defaults;
}

function newLogger(folder, logfile) {
    /**
     * Create Winston logger for specified filename and console
     * @param filename: name of log to write to (excluding .log)
     * @return a winston logger
     */
    var filename = folder ? path.join(folder, logfile) : logfile;

    return new (winston.Logger)({
        transports: [
            new (winston.transports.Console)({
                name: 'console',
                json: false,
                colorize: true,
                level: 'info'
            }),
            new (winston.transports.File)({
                name: 'file',
                //datePattern: '.yyyy-MM-dd',
                filename: filename,
                timestamp: false,
                json: false,
                level: 'info'
            })
        ]
    })
}

function merge(object1, object2) {
    /**
     * Merge attributes of two objects
     * @param object1: default object
     * @param object2: customised object
     * @type {{}}
     * @return object1 overwritten by object2
     */
    var attr,
        object3 = {};

    for (attr in object1) {object3[attr] = object1[attr]}
    for (attr in object2) {
        if (object1.hasOwnProperty(attr) && object2[attr] instanceof Array) {
            object3[attr] = arrayMerge(object1[attr], object2[attr]);
        } else if (object1.hasOwnProperty(attr) && object2[attr] instanceof Object) {
            object3[attr] = merge(object1[attr], object2[attr]);
        } else {
            object3[attr] = object2[attr];
        }
    }
    return object3;
}

function arrayMerge(array1, array2) {
    /**
     * For merging repository array
     * @param array1: default array
     * @param array1: customised array
     * @type {Array}
     * @return array2 after its sub-Arrays/Objects overwritten by array1[0]
     */
    var array3 = [];
    for (var i=0; i<array2.length; i++) {
        if (array2[i] instanceof Array) {
            array3 = array3.concat(arrayMerge(array1[0], array2[i]));
        } else if (array2[i] instanceof Object) {
            array3 = array3.concat(merge(array1[0], array2[i]));
        } else {
            array3 = array3.concat(array2[i]);
        }
    }
    return array3;
}

function server(port) {
    /**
     * Start listening for hook
     * @param port: port to start Gookie server on
     */

    app.use(methodOverride());

    // error function
    app.use(function(err, req, res, next) {
        logger.error(timePrefix() + 'Error occured: ' + err);
        res.status(500).send('Bad request').end();
    });

    // Parse body as text (so we get raw JSON string for hashing)
    app.use(bodyParser.text({type: 'application/json'})); //{extended: false}

    // Secret + Parse/Validate JSON
    app.use(function(req, res, next) {
        var signature = req.headers.hasOwnProperty('x-hub-signature') ? req.headers['x-hub-signature'] : undefined;

        req.rawBody = req.body;
        var json = req.body = JSON.parse(req.body);
        var repo = json.repository.url = formatUrl(json.repository.url);

        try {
            validateRequest(json);
        } catch (e) {
            throw 'Invalid JSON in request: ' + e;
        }

        var secret = repos[repo].secret;

        if (secret) {
            var signature_error = '';

            if (signature && signature.length < 100) {
                var shasum = crypto.createHmac('sha1', secret);
                shasum.update(req.rawBody);
                if ('sha1=' + shasum.digest('hex') === signature) {
                    logger.verbose(timePrefix() + 'Signature valid on request for ' + repo);
                    next();
                } else {
                    signature_error += 'Signature "' + signature + '" does not match digest "sha1=' + shasum.digest('hex') + '"! Double check secret on repo ' + repo + '. ';
                }
            } else {
                signature_error += 'Signature invalid/missing. Secret most likely not configured on GitHub. ';
            }

            if (signature_error) {
                logger.error(signature_error);
                res.status('500').send('Bad Request').end();
            }
        } else {
            logger.verbose(timePrefix() + 'No signature configured on repo ' + repo);
            next();
        }
    });

    app.route('/')
        .get(function(req, res) {
            logger.info(timePrefix() + 'GET request sent from ' + req.connection.remoteAddress);
            res.status(200).send('<title>Gookie</title>
Your Gookie server is running.
').end();
        })
        .post(function(req, res) {
            try {
                if (req.body.hasOwnProperty('zen')) {
                    logger.info('Ping event from ' + req.body.repository.url + ' with zen: ' + req.body['zen']);
                    res.status(204).end();
                    return;
                }

                logger.verbose(timePrefix() + 'User ' + req.body.pusher.name + ' pushed to ' + req.body.repository.url);

                res.status(204).end();
            } catch (e) {
                logger.error(timePrefix() + e);
                res.status(400).send('Bad request.').end();
                return;
            }

            matchRepo(req.body);
        });

    app.listen(port);
    logger.info(timePrefix() + 'Server started on port ' + port);
}

function validateRequest(json) {
    /**
     * Validate GitHub JSON by checking repo matches etc.
     * TODO: support for secret
     * @param json: decoded JSON request body
     * @throws error when invalid JSON
     */

    if (!json.hasOwnProperty('repository') || !json.repository.hasOwnProperty('url')) {
        throw 'ERROR: invalid JSON!';
    }
    if (!repos.hasOwnProperty(json.repository.url)) {
        throw 'ERROR: ' + json.repository.url + ' just sent a webhook but is not configured?';
    }
}

function matchRepo(json) {
    /**
     * Match repo in JSON to deploy command
     * @param json: decoded JSON request body
     */
    var repo = repos[json.repository.url];
    deploy(repo.path, repo.deploy);
}

function deploy(directory, command) {
    /**
     * Run deploy command in git repo
     * Execs: cd "directory" && command
     * @param directory: directory of git repo
     * @param command: deploy command to run
     */
    command = 'cd "' + directory + '" && ' + command;
    logger.verbose(timePrefix() + 'Running: ' + command);
    exec(command, function(error, stdout, stderr) {
        if (stdout) logger.info(timePrefix() + 'Deploying in ' + directory + '\n' + stdout);
        if (stderr) logger.error(timePrefix() + 'Deploy error in ' + directory + '\n' + stderr);
        if (error) logger.error(timePrefix() + 'Deploy error in ' + directory + '\n' + error);
        logger.verbose(timePrefix() + 'End of deploy output');
    });
}

function timePrefix() {
    /**
     * Time prefix for console.log
     * @return prefix in the form of ' [hh:mm:ss] '
     */
    var date = new Date();

    var hour = date.getHours();
    hour = (hour < 10 ? "0" : "") + hour;

    var min  = date.getMinutes();
    min = (min < 10 ? "0" : "") + min;

    var sec  = date.getSeconds();
    sec = (sec < 10 ? "0" : "") + sec;

    return ' [' + hour + ':' + min + ':' + sec + '] ';
}

function formatUrl(url) {
    /**
     * Get user and repo name from url
     * @param url: url in string format
     * @type String
     * @return formatted github url like: https://github.com/user/repo
     */
    var match = url.match(/https?:\/\/(?:(?:www|api)\.)?github\.com\/(?:repos\/)?([a-zA-Z0-9\-]+)\/([a-zA-Z0-9\-]+)\/?/);
    try {
        return 'https://github.com/' + match[1].toLowerCase() + '/' + match[2].toLowerCase();
    } catch (e) {
        throw 'Invalid repo URL ' + url;
    }
}

main();<|endoftext|>


<commit_before>'use strict';

function weekInDotNet(d, apiKey, serviceUrl, baseUrl) {
    var url = d.URL;
    var title = getTitle();
    var author = getAuthor();

    displayForm();

    function getTitle() {
        return find({ tag: 'h1' });
    }

    function getAuthor() {
        return findAny([
            '.author-name',
            '.author .profile-display-name',
            '.author a',
            { class: 'author' },
            'a[rel~="author"]'
            ]);
    }

    function find(query) {
        var els =
            (typeof query === 'string') ? [d.querySelector(query)] :
            (query.id) ? [d.getElementById(query.id)] :
            (query.class) ? d.getElementsByClassName(query.class) :
            (query.tag) ? d.getElementsByTagName(query.tag) : null;
        if (els.length == 0 || !els[0]) return '';
        return els[0].innerText.trim();
    }

    function findAny(queries) {
        for (var query of queries) {
            var result = find(query);
            if (result) return result;
        }
        return '';
    }

    function displayForm() {
        var formWidth = 500;
        var margin = 8;

        var dialog = create('div', {}, {
            border: '1px solid black',
            position: 'fixed',
            top: 0,
            left: 0,
            right: 0,
            width: formWidth + 'px',
            margin: '2px auto',
            backgroundColor: 'white',
            zIndex: 999999,
            fontSize: 'small',
            fontFamily: 'sans-serif'
        });
        var titleBar = append(dialog, 'div', {}, {
            left: 0,
            right: 0,
            backgroundColor: 'black',
            color: 'white',
            fontWeight: 'bold',
            padding: margin + 'px'
        }, 'Add this post to The Week in .NET');

        append(titleBar, 'button', {}, {
            float: 'right',
            border: 'none',
            background: 'transparent'
        }, 'X')
            .addEventListener('click', function () {
                d.body.removeChild(dialog);
            });

        var form = append(dialog, 'form', {
            method: 'POST',
            action: serviceUrl
        }, {
            width: formWidth + 'px',
            padding: margin + 'px'
        });

        append(form, 'input', {type: 'hidden', name: 'apikey', value: apiKey})
        append(form, 'div', {}, {}, 'Title:');
        appendInput(form, title, 'title');
        append(form, 'div', {}, {}, 'URL:');
        appendInput(form, url, 'url');
        append(form, 'div', {}, {}, 'Author:');
        appendInput(form, author, 'author');
        // TODO: replace this with dynamically obtained categories
        var categories = append(form, 'fieldset', {}, {});
        append(categories, 'legend', {}, {}, 'Category:');
        append(categories, 'input', { id: 'widn-category-dotnet', type: 'radio', name: 'category', value: '.NET', checked: 'checked' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-dotnet' }, { marginRight: '8px' }, '.NET');
        append(categories, 'input', { id: 'widn-category-aspnet', type: 'radio', name: 'category', value: 'ASP.NET' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-aspnet' }, { marginRight: '8px' }, 'ASP.NET');
        append(categories, 'input', { id: 'widn-category-csharp', type: 'radio', name: 'category', value: 'C#' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-csharp' }, { marginRight: '8px' }, 'C#');
        append(categories, 'input', { id: 'widn-category-fsharp', type: 'radio', name: 'category', value: 'F#' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-fsharp' }, { marginRight: '8px' }, 'F#');
        append(categories, 'input', { id: 'widn-category-vb', type: 'radio', name: 'category', value: 'VB' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-vb' }, { marginRight: '8px' }, 'VB');
        append(categories, 'input', { id: 'widn-category-xamarin', type: 'radio', name: 'category', value: 'Xamarin' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-xamarin' }, { marginRight: '8px' }, 'Xamarin');
        append(categories, 'input', { id: 'widn-category-azure', type: 'radio', name: 'category', value: 'Azure' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-azure' }, { marginRight: '8px' }, 'Azure');
        append(categories, 'input', { id: 'widn-category-uwp', type: 'radio', name: 'category', value: 'UWP' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-uwp' }, { marginRight: '8px' }, 'UWP');
        append(categories, 'input', { id: 'widn-category-data', type: 'radio', name: 'category', value: 'Data' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-data' }, { marginRight: '8px' }, 'Data');
        append(categories, 'input', { id: 'widn-category-game', type: 'radio', name: 'category', value: 'Game development' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-game' }, { marginRight: '8px' }, 'Game development');
        append(form, 'button', {
            type: 'submit'
        }, {
            marginTop: '8px'
        }, 'Submit');
        d.body.insertBefore(dialog, d.body.firstChild);

        function create(tag, properties, css, contents) {
            var el = d.createElement(tag || 'div');
            if (properties) {
                for (var prop in properties) {
                    el[prop] = properties[prop];
                }
            }
            if (css) {
                for (var prop in css) {
                    el.style[prop] = css[prop];
                }
            }
            if (contents) el.innerHTML = contents;
            return el;
        }

        function append(parent, tag, properties, css, contents) {
            var el = create(tag, properties, css, contents);
            parent.appendChild(el);
            return el;
        }

        function appendInput(parent, val, name) {
            return append(parent, 'input',
                { value: val, name: name, type: 'text' },
                {
                    width: (formWidth - 5 * margin) + 'px',
                    marginBottom: '8px'
                }
            );
        }
    }
}<commit_msg>Add more author formats<commit_after>'use strict';

function weekInDotNet(d, apiKey, serviceUrl, baseUrl) {
    var url = d.URL;
    var title = getTitle();
    var author = getAuthor();

    displayForm();

    function getTitle() {
        return find({ tag: 'h1' });
    }

    function getAuthor() {
        return findAny([
            '.author-name',
            '.author .profile-display-name',
            '.author a',
            { class: 'author' },
            'a[rel~="author"]',
            '.footer-text a[href="/about"]',
            '.copyright'
            ]);
    }

    function find(query) {
        var els =
            (typeof query === 'string') ? [d.querySelector(query)] :
            (query.id) ? [d.getElementById(query.id)] :
            (query.class) ? d.getElementsByClassName(query.class) :
            (query.tag) ? d.getElementsByTagName(query.tag) : null;
        if (els.length == 0 || !els[0]) return '';
        return els[0].innerText.trim();
    }

    function findAny(queries) {
        for (var query of queries) {
            var result = find(query);
            if (result) return result;
        }
        return '';
    }

    function displayForm() {
        var formWidth = 500;
        var margin = 8;

        var dialog = create('div', {}, {
            border: '1px solid black',
            position: 'fixed',
            top: 0,
            left: 0,
            right: 0,
            width: formWidth + 'px',
            margin: '2px auto',
            backgroundColor: 'white',
            zIndex: 999999,
            fontSize: 'small',
            fontFamily: 'sans-serif'
        });
        var titleBar = append(dialog, 'div', {}, {
            left: 0,
            right: 0,
            backgroundColor: 'black',
            color: 'white',
            fontWeight: 'bold',
            padding: margin + 'px'
        }, 'Add this post to The Week in .NET');

        append(titleBar, 'button', {}, {
            float: 'right',
            border: 'none',
            background: 'transparent'
        }, 'X')
            .addEventListener('click', function () {
                d.body.removeChild(dialog);
            });

        var form = append(dialog, 'form', {
            method: 'POST',
            action: serviceUrl
        }, {
            width: formWidth + 'px',
            padding: margin + 'px'
        });

        append(form, 'input', {type: 'hidden', name: 'apikey', value: apiKey})
        append(form, 'div', {}, {}, 'Title:');
        appendInput(form, title, 'title');
        append(form, 'div', {}, {}, 'URL:');
        appendInput(form, url, 'url');
        append(form, 'div', {}, {}, 'Author:');
        appendInput(form, author, 'author');
        // TODO: replace this with dynamically obtained categories
        var categories = append(form, 'fieldset', {}, {});
        append(categories, 'legend', {}, {}, 'Category:');
        append(categories, 'input', { id: 'widn-category-dotnet', type: 'radio', name: 'category', value: '.NET', checked: 'checked' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-dotnet' }, { marginRight: '8px' }, '.NET');
        append(categories, 'input', { id: 'widn-category-aspnet', type: 'radio', name: 'category', value: 'ASP.NET' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-aspnet' }, { marginRight: '8px' }, 'ASP.NET');
        append(categories, 'input', { id: 'widn-category-csharp', type: 'radio', name: 'category', value: 'C#' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-csharp' }, { marginRight: '8px' }, 'C#');
        append(categories, 'input', { id: 'widn-category-fsharp', type: 'radio', name: 'category', value: 'F#' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-fsharp' }, { marginRight: '8px' }, 'F#');
        append(categories, 'input', { id: 'widn-category-vb', type: 'radio', name: 'category', value: 'VB' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-vb' }, { marginRight: '8px' }, 'VB');
        append(categories, 'input', { id: 'widn-category-xamarin', type: 'radio', name: 'category', value: 'Xamarin' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-xamarin' }, { marginRight: '8px' }, 'Xamarin');
        append(categories, 'input', { id: 'widn-category-azure', type: 'radio', name: 'category', value: 'Azure' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-azure' }, { marginRight: '8px' }, 'Azure');
        append(categories, 'input', { id: 'widn-category-uwp', type: 'radio', name: 'category', value: 'UWP' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-uwp' }, { marginRight: '8px' }, 'UWP');
        append(categories, 'input', { id: 'widn-category-data', type: 'radio', name: 'category', value: 'Data' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-data' }, { marginRight: '8px' }, 'Data');
        append(categories, 'input', { id: 'widn-category-game', type: 'radio', name: 'category', value: 'Game development' }, { marginRight: '4px' });
        append(categories, 'label', { for: 'widn-category-game' }, { marginRight: '8px' }, 'Game development');
        append(form, 'button', {
            type: 'submit'
        }, {
            marginTop: '8px'
        }, 'Submit');
        d.body.insertBefore(dialog, d.body.firstChild);

        function create(tag, properties, css, contents) {
            var el = d.createElement(tag || 'div');
            if (properties) {
                for (var prop in properties) {
                    el[prop] = properties[prop];
                }
            }
            if (css) {
                for (var prop in css) {
                    el.style[prop] = css[prop];
                }
            }
            if (contents) el.innerHTML = contents;
            return el;
        }

        function append(parent, tag, properties, css, contents) {
            var el = create(tag, properties, css, contents);
            parent.appendChild(el);
            return el;
        }

        function appendInput(parent, val, name) {
            return append(parent, 'input',
                { value: val, name: name, type: 'text' },
                {
                    width: (formWidth - 5 * margin) + 'px',
                    marginBottom: '8px'
                }
            );
        }
    }
}<|endoftext|>


<commit_before>var challenges;
var templates = {};

Handlebars.registerHelper('splitSlash', function(filename) {
    var filename = filename.split("/");
    filename = filename[filename.length - 1];
    return filename;
});

function loadchal(id) {
    var obj = $.grep(challenges['game'], function (e) {
        return e.id == id;
    })[0];

    updateChalWindow(obj);
}

function loadchalbyname(chalname) {
    var obj = $.grep(challenges['game'], function (e) {
      return e.name == chalname;
    })[0];

    updateChalWindow(obj);
}

function updateChalWindow(obj) {
    $.get(script_root + '/themes/original/static/js/templates/challenges/'+obj.type+'/'+obj.type+'-challenge-modal.hbs', function(template_data){
        $('#chal-window').empty();
        templates[obj.type] = template_data;
        var template_data = templates[obj.type];
        template_data['script_root'] = script_root;
        var template = Handlebars.compile(template_data);
        var solves = obj.solves == 1 ? " Solve" : " Solves";
        var solves = obj.solves + solves;

        var nonce = $('#nonce').val();
        var wrapper  = {
            id: obj.id,
            name: obj.name,
            value: obj.value,
            tags: obj.tags,
            desc: obj.description,
            solves: solves,
            files: obj.files,
            hints: obj.hints
        };

        $('#chal-window').append(template(wrapper));
        $.getScript(script_root + '/themes/original/static/js/templates/challenges/'+obj.type+'/'+obj.type+'-challenge-script.js',
            function() {
                // Handle Solves tab
                $('.chal-solves').click(function (e) {
                    getsolves($('#chal-id').val())
                });
                $('.nav-tabs a').click(function (e) {
                    e.preventDefault();
                    $(this).tab('show')
                });

                // Handle modal toggling
                $('#chal-window').on('hide.bs.modal', function (event) {
                    $("#answer-input").removeClass("wrong");
                    $("#answer-input").removeClass("correct");
                    $("#incorrect-key").slideUp();
                    $("#correct-key").slideUp();
                    $("#already-solved").slideUp();
                    $("#too-fast").slideUp();
                });

                // $('pre code').each(function(i, block) {
                //     hljs.highlightBlock(block);
                // });

                window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
                $('#chal-window').modal();
        });
    });
}

$("#answer-input").keyup(function(event){
    if(event.keyCode == 13){
        $("#submit-key").click();
    }
});

function submitkey(chal, key, nonce) {
    $('#submit-key').addClass("disabled-button");
    $('#submit-key').prop('disabled', true);
    $.post(script_root + "/chal/" + chal, {
        key: key,
        nonce: nonce
    }, function (data) {
        var result = $.parseJSON(JSON.stringify(data));

        var result_message = $('#result-message');
        var result_notification = $('#result-notification');
        var answer_input = $("#answer-input");
        result_notification.removeClass();
        result_message.text(result.message);

        if (result.status == -1){
          window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash
          return
        }
        else if (result.status == 0){ // Incorrect key
            result_notification.addClass('alert alert-danger alert-dismissable text-center');
            result_notification.slideDown();

            answer_input.removeClass("correct");
            answer_input.addClass("wrong");
            setTimeout(function () {
                answer_input.removeClass("wrong");
            }, 3000);
        }
        else if (result.status == 1){ // Challenge Solved
            result_notification.addClass('alert alert-success alert-dismissable text-center');
            result_notification.slideDown();

            $('.chal-solves').text((parseInt($('.chal-solves').text().split(" ")[0]) + 1 +  " Solves") );

            answer_input.val("");
            answer_input.removeClass("wrong");
            answer_input.addClass("correct");
        }
        else if (result.status == 2){ // Challenge already solved
            result_notification.addClass('alert alert-info alert-dismissable text-center');
            result_notification.slideDown();

            answer_input.addClass("correct");
        }
        else if (result.status == 3){ // Keys per minute too high
            result_notification.addClass('alert alert-warning alert-dismissable text-center');
            result_notification.slideDown();

            answer_input.addClass("too-fast");
            setTimeout(function() {
                answer_input.removeClass("too-fast");
            }, 3000);
        }
        marksolves();
        updatesolves();
        setTimeout(function(){
          $('.alert').slideUp();
          $('#submit-key').removeClass("disabled-button");
          $('#submit-key').prop('disabled', false);
        }, 3000);
    })
}

function marksolves(cb) {
    $.get(script_root + '/solves', function (data) {
        var solves = $.parseJSON(JSON.stringify(data));
        for (var i = solves['solves'].length - 1; i >= 0; i--) {
            var id = solves['solves'][i].chalid;
            $('button[value="' + id + '"]').removeClass('theme-background');
            $('button[value="' + id + '"]').addClass('solved-challenge');
        };
        if (cb) {
            cb();
        }
    });
}

function updatesolves(cb){
    $.get(script_root + '/chals/solves', function (data) {
        var solves = $.parseJSON(JSON.stringify(data));
        var chalids = Object.keys(solves);

        for (var i = 0; i < chalids.length; i++) {
            for (var i = 0; i < challenges['game'].length; i++) {
                var obj = challenges['game'][i];
                var solve_cnt = solves[chalids[i]];
                if (solve_cnt) {
                    obj.solves = solve_cnt;
                } else {
                    obj.solves = 0;
                }
            }
        };
        if (cb) {
            cb();
        }
    });
}

function getsolves(id){
  $.get(script_root + '/chal/'+id+'/solves', function (data) {
    var teams = data['teams'];
    var box = $('#chal-solves-names');
    box.empty();
    for (var i = 0; i < teams.length; i++) {
      var id = teams[i].id;
      var name = teams[i].name;
      var date = moment(teams[i].date).local().format('LLL');
      box.append('
{1}	{2}
'.format(id, htmlentities(name), date));
    };
  });
}

function loadchals(cb) {
    $.get(script_root + "/chals", function (data) {
        var categories = [];
        challenges = $.parseJSON(JSON.stringify(data));

        challenges['game'].sort(function(a, b) {
            return a.id - b.id  ||  a.name.localeCompare(b.name);
        });

        $('#challenges-board').html("");

        for (var i = challenges['game'].length - 1; i >= 0; i--) {
            challenges['game'][i].solves = 0
            if ($.inArray(challenges['game'][i].category, categories) == -1) {
                var category = challenges['game'][i].category;
                categories.push(category);

                var categoryid = category.replace(/ /g,"-").hashCode();
                var categoryrow = $('' +
                    '
'.format(categoryid) +
                        '
' +
                        '
' +
                        '
' +
                            '

' +
                        '
' +
                    '
');
                categoryrow.find(".category-header").append($("
"+ category +"
"));

                $('#challenges-board').append(categoryrow);
            }
        };

        for (var i = 0; i <= challenges['game'].length - 1; i++) {
            var chalinfo = challenges['game'][i];
            var challenge = chalinfo.category.replace(/ /g,"-").hashCode();
            var chalid = chalinfo.name.replace(/ /g,"-").hashCode();
            var catid = chalinfo.category.replace(/ /g,"-").hashCode();
            var chalwrap = $("

".format(chalid));
            var chalbutton = $("<button class='challenge-button trigger theme-background hide-text' value='{0}'>
".format(chalinfo.id));
            var chalheader = $("
{0}
".format(chalinfo.name));
            var chalscore = $("{0}".format(chalinfo.value));
            for (var j = 0; j < chalinfo.tags.length; j++) {
                var tag = 'tag-' + chalinfo.tags[j].replace(/ /g, '-');
                chalwrap.addClass(tag);
            }

            chalbutton.append(chalheader);
            chalbutton.append(chalscore);
            chalwrap.append(chalbutton);

            $("#"+ catid +"-row").find(".category-challenges > .chal-row").append(chalwrap);
        };

        marksolves();

        $('.challenge-button').click(function (e) {
            loadchal(this.value);
        });

        if (cb){
            cb();
        }
    });
}

function loadhint(hintid){
    if (confirm("Are you sure you want to open this hint?")){
        $.post(script_root + "/hints/" + hintid, {'nonce': $('#nonce').val()}, function(data){
            if (data.errors){
                alert(data.errors);
            } else {
                $('#hint-modal-body').html(marked(data.hint, {'gfm':true, 'breaks':true}));
                $('#hint-modal').modal();
            }
        });
    }
}

$('#submit-key').click(function (e) {
    submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val())
});

$('.chal-solves').click(function (e) {
    getsolves($('#chal-id').val())
});

$('#chal-window').on('hide.bs.modal', function (event) {
    $("#answer-input").removeClass("wrong");
    $("#answer-input").removeClass("correct");
    $("#incorrect-key").slideUp();
    $("#correct-key").slideUp();
    $("#already-solved").slideUp();
    $("#too-fast").slideUp();
});

// $.distint(array)
// Unique elements in array
$.extend({
    distinct : function(anArray) {
       var result = [];
       $.each(anArray, function(i,v){
           if ($.inArray(v, result) == -1) result.push(v);
       });
       return result;
    }
});

function colorhash (x) {
    color = "";
    for (var i = 20; i <= 60; i+=20){
        x += i;
        x *= i;
        color += x.toString(16)
    };
    return "#" + color.substring(0, 6);
}

var load_location_hash = function () {
    if (window.location.hash.length > 0) {
        loadchalbyname(window.location.hash.substring(1));
        $("#chal-window").modal("show");
    }
};

function update(){
    loadchals(function(){
        updatesolves();
    });
}

$(function() {
    loadchals(function(){
        updatesolves(load_location_hash);
    });
});

$('.nav-tabs a').click(function (e) {
    e.preventDefault();
    $(this).tab('show')
})

$('#chal-window').on('hidden.bs.modal', function() {
    $('.nav-tabs a:first').tab('show');
    history.replaceState('', document.title, window.location.pathname);
});

setInterval(update, 300000);
<commit_msg>Fixing challenge ordering bug introduced (#385)<commit_after>var challenges;
var templates = {};

Handlebars.registerHelper('splitSlash', function(filename) {
    var filename = filename.split("/");
    filename = filename[filename.length - 1];
    return filename;
});

function loadchal(id) {
    var obj = $.grep(challenges['game'], function (e) {
        return e.id == id;
    })[0];

    updateChalWindow(obj);
}

function loadchalbyname(chalname) {
    var obj = $.grep(challenges['game'], function (e) {
      return e.name == chalname;
    })[0];

    updateChalWindow(obj);
}

function updateChalWindow(obj) {
    $.get(script_root + '/themes/original/static/js/templates/challenges/'+obj.type+'/'+obj.type+'-challenge-modal.hbs', function(template_data){
        $('#chal-window').empty();
        templates[obj.type] = template_data;
        var template_data = templates[obj.type];
        template_data['script_root'] = script_root;
        var template = Handlebars.compile(template_data);
        var solves = obj.solves == 1 ? " Solve" : " Solves";
        var solves = obj.solves + solves;

        var nonce = $('#nonce').val();
        var wrapper  = {
            id: obj.id,
            name: obj.name,
            value: obj.value,
            tags: obj.tags,
            desc: obj.description,
            solves: solves,
            files: obj.files,
            hints: obj.hints
        };

        $('#chal-window').append(template(wrapper));
        $.getScript(script_root + '/themes/original/static/js/templates/challenges/'+obj.type+'/'+obj.type+'-challenge-script.js',
            function() {
                // Handle Solves tab
                $('.chal-solves').click(function (e) {
                    getsolves($('#chal-id').val())
                });
                $('.nav-tabs a').click(function (e) {
                    e.preventDefault();
                    $(this).tab('show')
                });

                // Handle modal toggling
                $('#chal-window').on('hide.bs.modal', function (event) {
                    $("#answer-input").removeClass("wrong");
                    $("#answer-input").removeClass("correct");
                    $("#incorrect-key").slideUp();
                    $("#correct-key").slideUp();
                    $("#already-solved").slideUp();
                    $("#too-fast").slideUp();
                });

                // $('pre code').each(function(i, block) {
                //     hljs.highlightBlock(block);
                // });

                window.location.replace(window.location.href.split('#')[0] + '#' + obj.name);
                $('#chal-window').modal();
        });
    });
}

$("#answer-input").keyup(function(event){
    if(event.keyCode == 13){
        $("#submit-key").click();
    }
});

function submitkey(chal, key, nonce) {
    $('#submit-key').addClass("disabled-button");
    $('#submit-key').prop('disabled', true);
    $.post(script_root + "/chal/" + chal, {
        key: key,
        nonce: nonce
    }, function (data) {
        var result = $.parseJSON(JSON.stringify(data));

        var result_message = $('#result-message');
        var result_notification = $('#result-notification');
        var answer_input = $("#answer-input");
        result_notification.removeClass();
        result_message.text(result.message);

        if (result.status == -1){
          window.location = script_root + "/login?next=" + script_root + window.location.pathname + window.location.hash
          return
        }
        else if (result.status == 0){ // Incorrect key
            result_notification.addClass('alert alert-danger alert-dismissable text-center');
            result_notification.slideDown();

            answer_input.removeClass("correct");
            answer_input.addClass("wrong");
            setTimeout(function () {
                answer_input.removeClass("wrong");
            }, 3000);
        }
        else if (result.status == 1){ // Challenge Solved
            result_notification.addClass('alert alert-success alert-dismissable text-center');
            result_notification.slideDown();

            $('.chal-solves').text((parseInt($('.chal-solves').text().split(" ")[0]) + 1 +  " Solves") );

            answer_input.val("");
            answer_input.removeClass("wrong");
            answer_input.addClass("correct");
        }
        else if (result.status == 2){ // Challenge already solved
            result_notification.addClass('alert alert-info alert-dismissable text-center');
            result_notification.slideDown();

            answer_input.addClass("correct");
        }
        else if (result.status == 3){ // Keys per minute too high
            result_notification.addClass('alert alert-warning alert-dismissable text-center');
            result_notification.slideDown();

            answer_input.addClass("too-fast");
            setTimeout(function() {
                answer_input.removeClass("too-fast");
            }, 3000);
        }
        marksolves();
        updatesolves();
        setTimeout(function(){
          $('.alert').slideUp();
          $('#submit-key').removeClass("disabled-button");
          $('#submit-key').prop('disabled', false);
        }, 3000);
    })
}

function marksolves(cb) {
    $.get(script_root + '/solves', function (data) {
        var solves = $.parseJSON(JSON.stringify(data));
        for (var i = solves['solves'].length - 1; i >= 0; i--) {
            var id = solves['solves'][i].chalid;
            $('button[value="' + id + '"]').removeClass('theme-background');
            $('button[value="' + id + '"]').addClass('solved-challenge');
        };
        if (cb) {
            cb();
        }
    });
}

function updatesolves(cb){
    $.get(script_root + '/chals/solves', function (data) {
        var solves = $.parseJSON(JSON.stringify(data));
        var chalids = Object.keys(solves);

        for (var i = 0; i < chalids.length; i++) {
            for (var z = 0; z < challenges['game'].length; z++) {
                var obj = challenges['game'][z];
                var solve_cnt = solves[chalids[i]];
                if (obj.id == chalids[i]){
                    if (solve_cnt) {
                        obj.solves = solve_cnt;
                    } else {
                        obj.solves = 0;
                    }
                }
            }
        };
        if (cb) {
            cb();
        }
    });
}

function getsolves(id){
  $.get(script_root + '/chal/'+id+'/solves', function (data) {
    var teams = data['teams'];
    var box = $('#chal-solves-names');
    box.empty();
    for (var i = 0; i < teams.length; i++) {
      var id = teams[i].id;
      var name = teams[i].name;
      var date = moment(teams[i].date).local().format('LLL');
      box.append('
{1}	{2}
'.format(id, htmlentities(name), date));
    };
  });
}

function loadchals(cb) {
    $.get(script_root + "/chals", function (data) {
        var categories = [];
        challenges = $.parseJSON(JSON.stringify(data));

        $('#challenges-board').html("");

        for (var i = challenges['game'].length - 1; i >= 0; i--) {
            challenges['game'][i].solves = 0
            if ($.inArray(challenges['game'][i].category, categories) == -1) {
                var category = challenges['game'][i].category;
                categories.push(category);

                var categoryid = category.replace(/ /g,"-").hashCode();
                var categoryrow = $('' +
                    '
'.format(categoryid) +
                        '
' +
                        '
' +
                        '
' +
                            '

' +
                        '
' +
                    '
');
                categoryrow.find(".category-header").append($("
"+ category +"
"));

                $('#challenges-board').append(categoryrow);
            }
        };

        for (var i = 0; i <= challenges['game'].length - 1; i++) {
            var chalinfo = challenges['game'][i];
            var challenge = chalinfo.category.replace(/ /g,"-").hashCode();
            var chalid = chalinfo.name.replace(/ /g,"-").hashCode();
            var catid = chalinfo.category.replace(/ /g,"-").hashCode();
            var chalwrap = $("

".format(chalid));
            var chalbutton = $("<button class='challenge-button trigger theme-background hide-text' value='{0}'>
".format(chalinfo.id));
            var chalheader = $("
{0}
".format(chalinfo.name));
            var chalscore = $("{0}".format(chalinfo.value));
            for (var j = 0; j < chalinfo.tags.length; j++) {
                var tag = 'tag-' + chalinfo.tags[j].replace(/ /g, '-');
                chalwrap.addClass(tag);
            }

            chalbutton.append(chalheader);
            chalbutton.append(chalscore);
            chalwrap.append(chalbutton);

            $("#"+ catid +"-row").find(".category-challenges > .chal-row").append(chalwrap);
        };

        marksolves();

        $('.challenge-button').click(function (e) {
            loadchal(this.value);
        });

        if (cb){
            cb();
        }
    });
}

function loadhint(hintid){
    if (confirm("Are you sure you want to open this hint?")){
        $.post(script_root + "/hints/" + hintid, {'nonce': $('#nonce').val()}, function(data){
            if (data.errors){
                alert(data.errors);
            } else {
                $('#hint-modal-body').html(marked(data.hint, {'gfm':true, 'breaks':true}));
                $('#hint-modal').modal();
            }
        });
    }
}

$('#submit-key').click(function (e) {
    submitkey($('#chal-id').val(), $('#answer-input').val(), $('#nonce').val())
});

$('.chal-solves').click(function (e) {
    getsolves($('#chal-id').val())
});

$('#chal-window').on('hide.bs.modal', function (event) {
    $("#answer-input").removeClass("wrong");
    $("#answer-input").removeClass("correct");
    $("#incorrect-key").slideUp();
    $("#correct-key").slideUp();
    $("#already-solved").slideUp();
    $("#too-fast").slideUp();
});

// $.distint(array)
// Unique elements in array
$.extend({
    distinct : function(anArray) {
       var result = [];
       $.each(anArray, function(i,v){
           if ($.inArray(v, result) == -1) result.push(v);
       });
       return result;
    }
});

function colorhash (x) {
    color = "";
    for (var i = 20; i <= 60; i+=20){
        x += i;
        x *= i;
        color += x.toString(16)
    };
    return "#" + color.substring(0, 6);
}

var load_location_hash = function () {
    if (window.location.hash.length > 0) {
        loadchalbyname(window.location.hash.substring(1));
        $("#chal-window").modal("show");
    }
};

function update(){
    loadchals(function(){
        updatesolves();
    });
}

$(function() {
    loadchals(function(){
        updatesolves(load_location_hash);
    });
});

$('.nav-tabs a').click(function (e) {
    e.preventDefault();
    $(this).tab('show')
})

$('#chal-window').on('hidden.bs.modal', function() {
    $('.nav-tabs a:first').tab('show');
    history.replaceState('', document.title, window.location.pathname);
});

setInterval(update, 300000);
<|endoftext|>