<commit_before>'use strict';
/*global angular: false, Highcharts: false */

angular.module('highcharts-ng', [])
  .directive('highchart', function () {

    //IE8 support
    var indexOf = function(arr, find, i /*opt*/) {
      if (i===undefined) i= 0;
      if (i<0) i+= arr.length;
      if (i<0) i= 0;
      for (var n= arr.length; i<n; i++)
        if (i in arr && arr[i]===find)
          return i;
      return -1;
    };

    function prependMethod(obj, method, func) {
      var original = obj[method];
      obj[method] = function () {
        var args = Array.prototype.slice.call(arguments);
        func.apply(this, args);
        if(original) {
          return original.apply(this, args);
        }  else {
          return;
        }

      };
    }

    function deepExtend(destination, source) {
      for (var property in source) {
        if (source[property] && source[property].constructor &&
          source[property].constructor === Object) {
          destination[property] = destination[property] || {};
          deepExtend(destination[property], source[property]);
        } else {
          destination[property] = source[property];
        }
      }
      return destination;
    }

    // acceptable shared state
    var seriesId = 0;
    var ensureIds = function (series) {
      var changed = false;
      angular.forEach(series, function(s) {
        if (!angular.isDefined(s.id)) {
          s.id = 'series-' + seriesId++;
          changed = true;
        }
      });
      return changed;
    };

    // immutable
    var axisNames = [ 'xAxis', 'yAxis' ];

    var getMergedOptions = function (scope, element, config) {
      var mergedOptions = {};

      var defaultOptions = {
        chart: {
          events: {}
        },
        title: {},
        subtitle: {},
        series: [],
        credits: {},
        plotOptions: {},
        navigator: {enabled: false}
      };

      if (config.options) {
        mergedOptions = deepExtend(defaultOptions, config.options);
      } else {
        mergedOptions = defaultOptions;
      }
      mergedOptions.chart.renderTo = element[0];
      angular.forEach(axisNames, function(axisName) {
        if (config[axisName]) {
          prependMethod(mergedOptions.chart.events, 'selection', function(e){
            var thisChart = this;
            if (e[axisName]) {
              scope.$apply(function () {
                scope.config[axisName].currentMin = e[axisName][0].min;
                scope.config[axisName].currentMax = e[axisName][0].max;
              });
            } else {
              //handle reset button - zoom out to all
              scope.$apply(function () {
                scope.config[axisName].currentMin = thisChart[axisName][0].dataMin;
                scope.config[axisName].currentMax = thisChart[axisName][0].dataMax;
              });
            }
          });

          prependMethod(mergedOptions.chart.events, 'addSeries', function(e){
            scope.config[axisName].currentMin = this[axisName][0].min || scope.config[axisName].currentMin;
            scope.config[axisName].currentMax = this[axisName][0].max || scope.config[axisName].currentMax;
          });

          mergedOptions[axisName] = angular.copy(config[axisName]);
        }
      });

      if(config.title) {
        mergedOptions.title = config.title;
      }
      if (config.subtitle) {
        mergedOptions.subtitle = config.subtitle;
      }
      if (config.credits) {
        mergedOptions.credits = config.credits;
      }
      if(config.size) {
        if (config.size.width) {
          mergedOptions.chart.width = config.size.width;
        }
        if (config.size.height) {
          mergedOptions.chart.height = config.size.height;
        }
	  }
      return mergedOptions;
    };

    var updateZoom = function (axis, modelAxis) {
      var extremes = axis.getExtremes();
      if(modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) {
        axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
      }
    };

    var processExtremes = function(chart, axis, axisName) {
      if(axis.currentMin || axis.currentMax) {
        chart[axisName][0].setExtremes(axis.currentMin, axis.currentMax, true);
      }
    };

    var chartOptionsWithoutEasyOptions = function (options) {
      return angular.extend({}, options, {data: null, visible: null});
    };

    return {
      restrict: 'EAC',
      replace: true,
      template: '

',
      scope: {
        config: '='
      },
      link: function (scope, element, attrs) {
        // We keep some chart-specific variables here as a closure
        // instead of storing them on 'scope'.

        // prevSeriesOptions is maintained by processSeries
        var prevSeriesOptions = {};

        var processSeries = function(series) {
          var ids = [];
          if(series) {
            var setIds = ensureIds(series);
            if(setIds) {
              //If we have set some ids this will trigger another digest cycle.
              //In this scenario just return early and let the next cycle take care of changes
              return false;
            }

            //Find series to add or update
            angular.forEach(series, function(s) {
              ids.push(s.id);
              var chartSeries = chart.get(s.id);
              if (chartSeries) {
                if (!angular.equals(prevSeriesOptions[s.id], chartOptionsWithoutEasyOptions(s))) {
                  chartSeries.update(angular.copy(s), false);
                } else {
                  if (s.visible !== undefined && chartSeries.visible !== s.visible) {
                    chartSeries.setVisible(s.visible, false);
                  }
                  chartSeries.setData(angular.copy(s.data), false);
                }
              } else {
                chart.addSeries(angular.copy(s), false);
              }
              prevSeriesOptions[s.id] = chartOptionsWithoutEasyOptions(s);
            });
          }

          //Now remove any missing series
          for(var i = chart.series.length - 1; i >= 0; i--) {
            var s = chart.series[i];
            if (indexOf(ids, s.options.id) < 0) {
              s.remove(false);
            }
          }
          return true;
        };

        // chart is maintained by initChart
        var chart = false;
        var initChart = function() {
          if (chart) chart.destroy();
          prevSeriesOptions = {};
          var config = scope.config || {};
          var mergedOptions = getMergedOptions(scope, element, config);
          chart = config.useHighStocks ? new Highcharts.StockChart(mergedOptions) : new Highcharts.Chart(mergedOptions);
          for (var i = 0; i < axisNames.length; i++) {
            if (config[axisNames[i]]) {
              processExtremes(chart, config[axisNames[i]], axisNames[i]);
            }
          }
          if(config.loading) {
            chart.showLoading();
          }

        };
        initChart();

        scope.$watch('config.series', function (newSeries, oldSeries) {
          var needsRedraw = processSeries(newSeries);
          if(needsRedraw) {
            chart.redraw();
          }
        }, true);

        scope.$watch('config.title', function (newTitle) {
          chart.setTitle(newTitle, true);
        }, true);

        scope.$watch('config.subtitle', function (newSubtitle) {
          chart.setTitle(true, newSubtitle);
        }, true);

        scope.$watch('config.loading', function (loading) {
          if(loading) {
            chart.showLoading();
          } else {
            chart.hideLoading();
          }
        });

        scope.$watch('config.credits.enabled', function (enabled) {
          if (enabled) {
            chart.credits.show();
          } else if (chart.credits) {
            chart.credits.hide();
          }
        });

        scope.$watch('config.useHighStocks', function (useHighStocks) {
          initChart();
        });

        angular.forEach(axisNames, function(axisName) {
          scope.$watch('config.' + axisName, function (newAxes, oldAxes) {
            if (newAxes === oldAxes) return;
            if(newAxes) {
              chart[axisName][0].update(newAxes, false);
              updateZoom(chart[axisName][0], angular.copy(newAxes));
              chart.redraw();
            }
          }, true);
        });
        scope.$watch('config.options', function (newOptions, oldOptions, scope) {
          //do nothing when called on registration
          if (newOptions === oldOptions) return;
          initChart();
          processSeries(scope.config.series);
          chart.redraw();
        }, true);

        scope.$watch('config.size', function (newSize, oldSize) {
            if (newSize === oldSize) return;
            chart.setSize(newSize.width, newSize.height);
        }, true);

        scope.$on('$destroy', function() {
          if (chart) chart.destroy();
          element.remove();
        });

      }
    };
  });
<commit_msg>updated size  for initial size set<commit_after>'use strict';
/*global angular: false, Highcharts: false */

angular.module('highcharts-ng', [])
  .directive('highchart', function () {

    //IE8 support
    var indexOf = function(arr, find, i /*opt*/) {
      if (i===undefined) i= 0;
      if (i<0) i+= arr.length;
      if (i<0) i= 0;
      for (var n= arr.length; i<n; i++)
        if (i in arr && arr[i]===find)
          return i;
      return -1;
    };

    function prependMethod(obj, method, func) {
      var original = obj[method];
      obj[method] = function () {
        var args = Array.prototype.slice.call(arguments);
        func.apply(this, args);
        if(original) {
          return original.apply(this, args);
        }  else {
          return;
        }

      };
    }

    function deepExtend(destination, source) {
      for (var property in source) {
        if (source[property] && source[property].constructor &&
          source[property].constructor === Object) {
          destination[property] = destination[property] || {};
          deepExtend(destination[property], source[property]);
        } else {
          destination[property] = source[property];
        }
      }
      return destination;
    }

    // acceptable shared state
    var seriesId = 0;
    var ensureIds = function (series) {
      var changed = false;
      angular.forEach(series, function(s) {
        if (!angular.isDefined(s.id)) {
          s.id = 'series-' + seriesId++;
          changed = true;
        }
      });
      return changed;
    };

    // immutable
    var axisNames = [ 'xAxis', 'yAxis' ];

    var getMergedOptions = function (scope, element, config) {
      var mergedOptions = {};

      var defaultOptions = {
        chart: {
          events: {}
        },
        title: {},
        subtitle: {},
        series: [],
        credits: {},
        plotOptions: {},
        navigator: {enabled: false}
      };

      if (config.options) {
        mergedOptions = deepExtend(defaultOptions, config.options);
      } else {
        mergedOptions = defaultOptions;
      }
      mergedOptions.chart.renderTo = element[0];
      angular.forEach(axisNames, function(axisName) {
        if (config[axisName]) {
          prependMethod(mergedOptions.chart.events, 'selection', function(e){
            var thisChart = this;
            if (e[axisName]) {
              scope.$apply(function () {
                scope.config[axisName].currentMin = e[axisName][0].min;
                scope.config[axisName].currentMax = e[axisName][0].max;
              });
            } else {
              //handle reset button - zoom out to all
              scope.$apply(function () {
                scope.config[axisName].currentMin = thisChart[axisName][0].dataMin;
                scope.config[axisName].currentMax = thisChart[axisName][0].dataMax;
              });
            }
          });

          prependMethod(mergedOptions.chart.events, 'addSeries', function(e){
            scope.config[axisName].currentMin = this[axisName][0].min || scope.config[axisName].currentMin;
            scope.config[axisName].currentMax = this[axisName][0].max || scope.config[axisName].currentMax;
          });

          mergedOptions[axisName] = angular.copy(config[axisName]);
        }
      });

      if(config.title) {
        mergedOptions.title = config.title;
      }
      if (config.subtitle) {
        mergedOptions.subtitle = config.subtitle;
      }
      if (config.credits) {
        mergedOptions.credits = config.credits;
      }
      if(config.size) {
        if (config.size.width) {
          mergedOptions.chart.width = config.size.width;
        }
        if (config.size.height) {
          mergedOptions.chart.height = config.size.height;
        }
	  }
      return mergedOptions;
    };

    var updateZoom = function (axis, modelAxis) {
      var extremes = axis.getExtremes();
      if(modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) {
        axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
      }
    };

    var processExtremes = function(chart, axis, axisName) {
      if(axis.currentMin || axis.currentMax) {
        chart[axisName][0].setExtremes(axis.currentMin, axis.currentMax, true);
      }
    };

    var chartOptionsWithoutEasyOptions = function (options) {
      return angular.extend({}, options, {data: null, visible: null});
    };

    return {
      restrict: 'EAC',
      replace: true,
      template: '

',
      scope: {
        config: '='
      },
      link: function (scope, element, attrs) {
        // We keep some chart-specific variables here as a closure
        // instead of storing them on 'scope'.

        // prevSeriesOptions is maintained by processSeries
        var prevSeriesOptions = {};

        var processSeries = function(series) {
          var ids = [];
          if(series) {
            var setIds = ensureIds(series);
            if(setIds) {
              //If we have set some ids this will trigger another digest cycle.
              //In this scenario just return early and let the next cycle take care of changes
              return false;
            }

            //Find series to add or update
            angular.forEach(series, function(s) {
              ids.push(s.id);
              var chartSeries = chart.get(s.id);
              if (chartSeries) {
                if (!angular.equals(prevSeriesOptions[s.id], chartOptionsWithoutEasyOptions(s))) {
                  chartSeries.update(angular.copy(s), false);
                } else {
                  if (s.visible !== undefined && chartSeries.visible !== s.visible) {
                    chartSeries.setVisible(s.visible, false);
                  }
                  chartSeries.setData(angular.copy(s.data), false);
                }
              } else {
                chart.addSeries(angular.copy(s), false);
              }
              prevSeriesOptions[s.id] = chartOptionsWithoutEasyOptions(s);
            });
          }

          //Now remove any missing series
          for(var i = chart.series.length - 1; i >= 0; i--) {
            var s = chart.series[i];
            if (indexOf(ids, s.options.id) < 0) {
              s.remove(false);
            }
          }
          return true;
        };

        // chart is maintained by initChart
        var chart = false;
        var initChart = function() {
          if (chart) chart.destroy();
          prevSeriesOptions = {};
          var config = scope.config || {};
          var mergedOptions = getMergedOptions(scope, element, config);
          chart = config.useHighStocks ? new Highcharts.StockChart(mergedOptions) : new Highcharts.Chart(mergedOptions);
          for (var i = 0; i < axisNames.length; i++) {
            if (config[axisNames[i]]) {
              processExtremes(chart, config[axisNames[i]], axisNames[i]);
            }
          }
          if(config.loading) {
            chart.showLoading();
          }

        };
        initChart();

        scope.$watch('config.series', function (newSeries, oldSeries) {
          var needsRedraw = processSeries(newSeries);
          if(needsRedraw) {
            chart.redraw();
          }
        }, true);

        scope.$watch('config.title', function (newTitle) {
          chart.setTitle(newTitle, true);
        }, true);

        scope.$watch('config.subtitle', function (newSubtitle) {
          chart.setTitle(true, newSubtitle);
        }, true);

        scope.$watch('config.loading', function (loading) {
          if(loading) {
            chart.showLoading();
          } else {
            chart.hideLoading();
          }
        });

        scope.$watch('config.credits.enabled', function (enabled) {
          if (enabled) {
            chart.credits.show();
          } else if (chart.credits) {
            chart.credits.hide();
          }
        });

        scope.$watch('config.useHighStocks', function (useHighStocks) {
          initChart();
        });

        angular.forEach(axisNames, function(axisName) {
          scope.$watch('config.' + axisName, function (newAxes, oldAxes) {
            if (newAxes === oldAxes) return;
            if(newAxes) {
              chart[axisName][0].update(newAxes, false);
              updateZoom(chart[axisName][0], angular.copy(newAxes));
              chart.redraw();
            }
          }, true);
        });
        scope.$watch('config.options', function (newOptions, oldOptions, scope) {
          //do nothing when called on registration
          if (newOptions === oldOptions) return;
          initChart();
          processSeries(scope.config.series);
          chart.redraw();
        }, true);

        scope.$watch('config.size', function (newSize, oldSize) {
            chart.setSize(newSize.width, newSize.height);
        }, true);

        scope.$on('$destroy', function() {
          if (chart) chart.destroy();
          element.remove();
        });

      }
    };
  });
<|endoftext|>


<commit_before>if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports){
  module.exports = 'highcharts-ng';
}
(function () {
  'use strict';
  /*global angular: false, Highcharts: false */

  angular.module('highcharts-ng', [])
    .factory('highchartsNGUtils', highchartsNGUtils)
    .directive('highchart', ['highchartsNGUtils', '$timeout', highchart]);

  function highchartsNGUtils() {

    return {

      //IE8 support
      indexOf: function (arr, find, i /*opt*/) {
        if (i === undefined) i = 0;
        if (i < 0) i += arr.length;
        if (i < 0) i = 0;
        for (var n = arr.length; i < n; i++)
          if (i in arr && arr[i] === find)
            return i;
        return -1;
      },

      prependMethod: function (obj, method, func) {
        var original = obj[method];
        obj[method] = function () {
          var args = Array.prototype.slice.call(arguments);
          func.apply(this, args);
          if (original) {
            return original.apply(this, args);
          } else {
            return;
          }

        };
      },

      deepExtend: function deepExtend(destination, source) {
        //Slightly strange behaviour in edge cases (e.g. passing in non objects)
        //But does the job for current use cases.
        if (angular.isArray(source)) {
          destination = angular.isArray(destination) ? destination : [];
          for (var i = 0; i < source.length; i++) {
            destination[i] = deepExtend(destination[i] || {}, source[i]);
          }
        } else if (angular.isObject(source)) {
          for (var property in source) {
            destination[property] = deepExtend(destination[property] || {}, source[property]);
          }
        } else {
          destination = source;
        }
        return destination;
      }
    };
  }

  function highchart(highchartsNGUtils, $timeout) {

    // acceptable shared state
    var seriesId = 0;
    var ensureIds = function (series) {
      var changed = false;
      angular.forEach(series, function(s) {
        if (!angular.isDefined(s.id)) {
          s.id = 'series-' + seriesId++;
          changed = true;
        }
      });
      return changed;
    };

    // immutable
    var axisNames = [ 'xAxis', 'yAxis' ];
    var chartTypeMap = {
      'stock': 'StockChart',
      'map':   'Map',
      'chart': 'Chart'
    };

    var getMergedOptions = function (scope, element, config) {
      var mergedOptions = {};

      var defaultOptions = {
        chart: {
          events: {}
        },
        title: {},
        subtitle: {},
        series: [],
        credits: {},
        plotOptions: {},
        navigator: {enabled: false}
      };

      if (config.options) {
        mergedOptions = highchartsNGUtils.deepExtend(defaultOptions, config.options);
      } else {
        mergedOptions = defaultOptions;
      }
      mergedOptions.chart.renderTo = element[0];

      angular.forEach(axisNames, function(axisName) {
        if(angular.isDefined(config[axisName])) {
          mergedOptions[axisName] = angular.copy(config[axisName]);

          if(angular.isDefined(config[axisName].currentMin) ||
              angular.isDefined(config[axisName].currentMax)) {

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'selection', function(e){
              var thisChart = this;
              if (e[axisName]) {
                scope.$apply(function () {
                  scope.config[axisName].currentMin = e[axisName][0].min;
                  scope.config[axisName].currentMax = e[axisName][0].max;
                });
              } else {
                //handle reset button - zoom out to all
                scope.$apply(function () {
                  scope.config[axisName].currentMin = thisChart[axisName][0].dataMin;
                  scope.config[axisName].currentMax = thisChart[axisName][0].dataMax;
                });
              }
            });

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'addSeries', function(e){
              scope.config[axisName].currentMin = this[axisName][0].min || scope.config[axisName].currentMin;
              scope.config[axisName].currentMax = this[axisName][0].max || scope.config[axisName].currentMax;
            });
          }
        }
      });

      if(config.title) {
        mergedOptions.title = config.title;
      }
      if (config.subtitle) {
        mergedOptions.subtitle = config.subtitle;
      }
      if (config.credits) {
        mergedOptions.credits = config.credits;
      }
      if(config.size) {
        if (config.size.width) {
          mergedOptions.chart.width = config.size.width;
        }
        if (config.size.height) {
          mergedOptions.chart.height = config.size.height;
        }
      }
      return mergedOptions;
    };

    var updateZoom = function (axis, modelAxis) {
      var extremes = axis.getExtremes();
      if(modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) {
        axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
      }
    };

    var processExtremes = function(chart, axis, axisName) {
      if(axis.currentMin || axis.currentMax) {
        chart[axisName][0].setExtremes(axis.currentMin, axis.currentMax, true);
      }
    };

    var chartOptionsWithoutEasyOptions = function (options) {
        return
            angular.extend(
                highchartsNGUtils.deepExtend({}, options),
                { data: null, visible: null }
            );
    };

    var getChartType = function(scope) {
      if (scope.config === undefined) return 'Chart';
      return chartTypeMap[('' + scope.config.chartType).toLowerCase()] ||
             (scope.config.useHighStocks ? 'StockChart' : 'Chart');
    };

    return {
      restrict: 'EAC',
      replace: true,
      template: '

',
      scope: {
        config: '=',
        disableDataWatch: '='
      },
      link: function (scope, element, attrs) {
        // We keep some chart-specific variables here as a closure
        // instead of storing them on 'scope'.

        // prevSeriesOptions is maintained by processSeries
        var prevSeriesOptions = {};

        var processSeries = function(series) {
          var i;
          var ids = [];

          if(series) {
            var setIds = ensureIds(series);
            if(setIds) {
              //If we have set some ids this will trigger another digest cycle.
              //In this scenario just return early and let the next cycle take care of changes
              return false;
            }

            //Find series to add or update
            angular.forEach(series, function(s) {
              ids.push(s.id);
              var chartSeries = chart.get(s.id);
              if (chartSeries) {
                if (!angular.equals(prevSeriesOptions[s.id], chartOptionsWithoutEasyOptions(s))) {
                  chartSeries.update(angular.copy(s), false);
                } else {
                  if (s.visible !== undefined && chartSeries.visible !== s.visible) {
                    chartSeries.setVisible(s.visible, false);
                  }
                  chartSeries.setData(angular.copy(s.data), false);
                }
              } else {
                chart.addSeries(angular.copy(s), false);
              }
              prevSeriesOptions[s.id] = chartOptionsWithoutEasyOptions(s);
            });

            //  Shows no data text if all series are empty
            if(scope.config.noData) {
              var chartContainsData = false;

              for(i = 0; i < series.length; i++) {
                if (series[i].data && series[i].data.length > 0) {
                  chartContainsData = true;

                  break;
                }
              }

              if (!chartContainsData) {
                chart.showLoading(scope.config.noData);
              } else {
                chart.hideLoading();
              }
            }
          }

          //Now remove any missing series
          for(i = chart.series.length - 1; i >= 0; i--) {
            var s = chart.series[i];
            if (s.options.id !== 'highcharts-navigator-series' && highchartsNGUtils.indexOf(ids, s.options.id) < 0) {
              s.remove(false);
            }
          }

          return true;
        };

        // chart is maintained by initChart
        var chart = false;
        var initChart = function() {
          if (chart) chart.destroy();
          prevSeriesOptions = {};
          var config = scope.config || {};
          var mergedOptions = getMergedOptions(scope, element, config);
          var func = config.func || undefined;
          var chartType = getChartType(scope);

          chart = new Highcharts[chartType](mergedOptions, func);

          for (var i = 0; i < axisNames.length; i++) {
            if (config[axisNames[i]]) {
              processExtremes(chart, config[axisNames[i]], axisNames[i]);
            }
          }
          if(config.loading) {
            chart.showLoading();
          }
          config.getHighcharts = function() {
            return chart;
          };

        };
        initChart();

        if(scope.disableDataWatch){
          scope.$watchCollection('config.series', function (newSeries, oldSeries) {
            processSeries(newSeries);
            chart.redraw();
          });
        } else {
          scope.$watch('config.series', function (newSeries, oldSeries) {
            var needsRedraw = processSeries(newSeries);
            if(needsRedraw) {
              chart.redraw();
            }
          }, true);
        }

        scope.$watch('config.title', function (newTitle) {
          chart.setTitle(newTitle, true);
        }, true);

        scope.$watch('config.subtitle', function (newSubtitle) {
          chart.setTitle(true, newSubtitle);
        }, true);

        scope.$watch('config.loading', function (loading) {
          if(loading) {
            chart.showLoading(loading === true ? null : loading);
          } else {
            chart.hideLoading();
          }
        });
        scope.$watch('config.noData', function (noData) {
          if(scope.config && scope.config.loading) {
            chart.showLoading(noData);
          }
        }, true);

        scope.$watch('config.credits.enabled', function (enabled) {
          if (enabled) {
            chart.credits.show();
          } else if (chart.credits) {
            chart.credits.hide();
          }
        });

        scope.$watch(getChartType, function (chartType, oldChartType) {
          if (chartType === oldChartType) return;
          initChart();
        });

        angular.forEach(axisNames, function(axisName) {
          scope.$watch('config.' + axisName, function(newAxes, oldAxes) {
            if (newAxes === oldAxes || !newAxes) {
              return;
            }

            if (angular.isArray(newAxes)) {

              for (var axisIndex = 0; axisIndex < newAxes.length; axisIndex++) {
                var axis = newAxes[axisIndex];

                if (axisIndex < chart[axisName].length) {
                  chart[axisName][axisIndex].update(axis, false);
                  updateZoom(chart[axisName][axisIndex], angular.copy(axis));
                }

              }

            } else {
              // update single axis
              chart[axisName][0].update(newAxes, false);
              updateZoom(chart[axisName][0], angular.copy(newAxes));
            }

            chart.redraw();
          }, true);
        });
        scope.$watch('config.options', function (newOptions, oldOptions, scope) {
          //do nothing when called on registration
          if (newOptions === oldOptions) return;
          initChart();
          processSeries(scope.config.series);
          chart.redraw();
        }, true);

        scope.$watch('config.size', function (newSize, oldSize) {
          if(newSize === oldSize) return;
          if(newSize) {
            chart.setSize(newSize.width || chart.chartWidth, newSize.height || chart.chartHeight);
          }
        }, true);

        scope.$on('highchartsng.reflow', function () {
          chart.reflow();
        });

        scope.$on('$destroy', function() {
          if (chart) {
            try{
              chart.destroy();
            }catch(ex){
              // fail silently as highcharts will throw exception if element doesn't exist
            }

            $timeout(function(){
              element.remove();
            }, 0);
          }
        });

      }
    };
  }
}());
<commit_msg>Update highcharts-ng.js<commit_after>if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports){
  module.exports = 'highcharts-ng';
}
(function () {
  'use strict';
  /*global angular: false, Highcharts: false */

  angular.module('highcharts-ng', [])
    .factory('highchartsNGUtils', highchartsNGUtils)
    .directive('highchart', ['highchartsNGUtils', '$timeout', highchart]);

  function highchartsNGUtils() {

    return {

      //IE8 support
      indexOf: function (arr, find, i /*opt*/) {
        if (i === undefined) i = 0;
        if (i < 0) i += arr.length;
        if (i < 0) i = 0;
        for (var n = arr.length; i < n; i++)
          if (i in arr && arr[i] === find)
            return i;
        return -1;
      },

      prependMethod: function (obj, method, func) {
        var original = obj[method];
        obj[method] = function () {
          var args = Array.prototype.slice.call(arguments);
          func.apply(this, args);
          if (original) {
            return original.apply(this, args);
          } else {
            return;
          }

        };
      },

      deepExtend: function deepExtend(destination, source) {
        //Slightly strange behaviour in edge cases (e.g. passing in non objects)
        //But does the job for current use cases.
        if (angular.isArray(source)) {
          destination = angular.isArray(destination) ? destination : [];
          for (var i = 0; i < source.length; i++) {
            destination[i] = deepExtend(destination[i] || {}, source[i]);
          }
        } else if (angular.isObject(source)) {
          for (var property in source) {
            destination[property] = deepExtend(destination[property] || {}, source[property]);
          }
        } else {
          destination = source;
        }
        return destination;
      }
    };
  }

  function highchart(highchartsNGUtils, $timeout) {

    // acceptable shared state
    var seriesId = 0;
    var ensureIds = function (series) {
      var changed = false;
      angular.forEach(series, function(s) {
        if (!angular.isDefined(s.id)) {
          s.id = 'series-' + seriesId++;
          changed = true;
        }
      });
      return changed;
    };

    // immutable
    var axisNames = [ 'xAxis', 'yAxis' ];
    var chartTypeMap = {
      'stock': 'StockChart',
      'map':   'Map',
      'chart': 'Chart'
    };

    var getMergedOptions = function (scope, element, config) {
      var mergedOptions = {};

      var defaultOptions = {
        chart: {
          events: {}
        },
        title: {},
        subtitle: {},
        series: [],
        credits: {},
        plotOptions: {},
        navigator: {enabled: false}
      };

      if (config.options) {
        mergedOptions = highchartsNGUtils.deepExtend(defaultOptions, config.options);
      } else {
        mergedOptions = defaultOptions;
      }
      mergedOptions.chart.renderTo = element[0];

      angular.forEach(axisNames, function(axisName) {
        if(angular.isDefined(config[axisName])) {
          mergedOptions[axisName] = angular.copy(config[axisName]);

          if(angular.isDefined(config[axisName].currentMin) ||
              angular.isDefined(config[axisName].currentMax)) {

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'selection', function(e){
              var thisChart = this;
              if (e[axisName]) {
                scope.$apply(function () {
                  scope.config[axisName].currentMin = e[axisName][0].min;
                  scope.config[axisName].currentMax = e[axisName][0].max;
                });
              } else {
                //handle reset button - zoom out to all
                scope.$apply(function () {
                  scope.config[axisName].currentMin = thisChart[axisName][0].dataMin;
                  scope.config[axisName].currentMax = thisChart[axisName][0].dataMax;
                });
              }
            });

            highchartsNGUtils.prependMethod(mergedOptions.chart.events, 'addSeries', function(e){
              scope.config[axisName].currentMin = this[axisName][0].min || scope.config[axisName].currentMin;
              scope.config[axisName].currentMax = this[axisName][0].max || scope.config[axisName].currentMax;
            });
          }
        }
      });

      if(config.title) {
        mergedOptions.title = config.title;
      }
      if (config.subtitle) {
        mergedOptions.subtitle = config.subtitle;
      }
      if (config.credits) {
        mergedOptions.credits = config.credits;
      }
      if(config.size) {
        if (config.size.width) {
          mergedOptions.chart.width = config.size.width;
        }
        if (config.size.height) {
          mergedOptions.chart.height = config.size.height;
        }
      }
      return mergedOptions;
    };

    var updateZoom = function (axis, modelAxis) {
      var extremes = axis.getExtremes();
      if(modelAxis.currentMin !== extremes.dataMin || modelAxis.currentMax !== extremes.dataMax) {
        axis.setExtremes(modelAxis.currentMin, modelAxis.currentMax, false);
      }
    };

    var processExtremes = function(chart, axis, axisName) {
      if(axis.currentMin || axis.currentMax) {
        chart[axisName][0].setExtremes(axis.currentMin, axis.currentMax, true);
      }
    };

    var chartOptionsWithoutEasyOptions = function (options) {
        return angular.extend(
          highchartsNGUtils.deepExtend({}, options),
          { data: null, visible: null }
        );
    };

    var getChartType = function(scope) {
      if (scope.config === undefined) return 'Chart';
      return chartTypeMap[('' + scope.config.chartType).toLowerCase()] ||
             (scope.config.useHighStocks ? 'StockChart' : 'Chart');
    };

    return {
      restrict: 'EAC',
      replace: true,
      template: '

',
      scope: {
        config: '=',
        disableDataWatch: '='
      },
      link: function (scope, element, attrs) {
        // We keep some chart-specific variables here as a closure
        // instead of storing them on 'scope'.

        // prevSeriesOptions is maintained by processSeries
        var prevSeriesOptions = {};

        var processSeries = function(series) {
          var i;
          var ids = [];

          if(series) {
            var setIds = ensureIds(series);
            if(setIds) {
              //If we have set some ids this will trigger another digest cycle.
              //In this scenario just return early and let the next cycle take care of changes
              return false;
            }

            //Find series to add or update
            angular.forEach(series, function(s) {
              ids.push(s.id);
              var chartSeries = chart.get(s.id);
              if (chartSeries) {
                if (!angular.equals(prevSeriesOptions[s.id], chartOptionsWithoutEasyOptions(s))) {
                  chartSeries.update(angular.copy(s), false);
                } else {
                  if (s.visible !== undefined && chartSeries.visible !== s.visible) {
                    chartSeries.setVisible(s.visible, false);
                  }
                  chartSeries.setData(angular.copy(s.data), false);
                }
              } else {
                chart.addSeries(angular.copy(s), false);
              }
              prevSeriesOptions[s.id] = chartOptionsWithoutEasyOptions(s);
            });

            //  Shows no data text if all series are empty
            if(scope.config.noData) {
              var chartContainsData = false;

              for(i = 0; i < series.length; i++) {
                if (series[i].data && series[i].data.length > 0) {
                  chartContainsData = true;

                  break;
                }
              }

              if (!chartContainsData) {
                chart.showLoading(scope.config.noData);
              } else {
                chart.hideLoading();
              }
            }
          }

          //Now remove any missing series
          for(i = chart.series.length - 1; i >= 0; i--) {
            var s = chart.series[i];
            if (s.options.id !== 'highcharts-navigator-series' && highchartsNGUtils.indexOf(ids, s.options.id) < 0) {
              s.remove(false);
            }
          }

          return true;
        };

        // chart is maintained by initChart
        var chart = false;
        var initChart = function() {
          if (chart) chart.destroy();
          prevSeriesOptions = {};
          var config = scope.config || {};
          var mergedOptions = getMergedOptions(scope, element, config);
          var func = config.func || undefined;
          var chartType = getChartType(scope);

          chart = new Highcharts[chartType](mergedOptions, func);

          for (var i = 0; i < axisNames.length; i++) {
            if (config[axisNames[i]]) {
              processExtremes(chart, config[axisNames[i]], axisNames[i]);
            }
          }
          if(config.loading) {
            chart.showLoading();
          }
          config.getHighcharts = function() {
            return chart;
          };

        };
        initChart();

        if(scope.disableDataWatch){
          scope.$watchCollection('config.series', function (newSeries, oldSeries) {
            processSeries(newSeries);
            chart.redraw();
          });
        } else {
          scope.$watch('config.series', function (newSeries, oldSeries) {
            var needsRedraw = processSeries(newSeries);
            if(needsRedraw) {
              chart.redraw();
            }
          }, true);
        }

        scope.$watch('config.title', function (newTitle) {
          chart.setTitle(newTitle, true);
        }, true);

        scope.$watch('config.subtitle', function (newSubtitle) {
          chart.setTitle(true, newSubtitle);
        }, true);

        scope.$watch('config.loading', function (loading) {
          if(loading) {
            chart.showLoading(loading === true ? null : loading);
          } else {
            chart.hideLoading();
          }
        });
        scope.$watch('config.noData', function (noData) {
          if(scope.config && scope.config.loading) {
            chart.showLoading(noData);
          }
        }, true);

        scope.$watch('config.credits.enabled', function (enabled) {
          if (enabled) {
            chart.credits.show();
          } else if (chart.credits) {
            chart.credits.hide();
          }
        });

        scope.$watch(getChartType, function (chartType, oldChartType) {
          if (chartType === oldChartType) return;
          initChart();
        });

        angular.forEach(axisNames, function(axisName) {
          scope.$watch('config.' + axisName, function(newAxes, oldAxes) {
            if (newAxes === oldAxes || !newAxes) {
              return;
            }

            if (angular.isArray(newAxes)) {

              for (var axisIndex = 0; axisIndex < newAxes.length; axisIndex++) {
                var axis = newAxes[axisIndex];

                if (axisIndex < chart[axisName].length) {
                  chart[axisName][axisIndex].update(axis, false);
                  updateZoom(chart[axisName][axisIndex], angular.copy(axis));
                }

              }

            } else {
              // update single axis
              chart[axisName][0].update(newAxes, false);
              updateZoom(chart[axisName][0], angular.copy(newAxes));
            }

            chart.redraw();
          }, true);
        });
        scope.$watch('config.options', function (newOptions, oldOptions, scope) {
          //do nothing when called on registration
          if (newOptions === oldOptions) return;
          initChart();
          processSeries(scope.config.series);
          chart.redraw();
        }, true);

        scope.$watch('config.size', function (newSize, oldSize) {
          if(newSize === oldSize) return;
          if(newSize) {
            chart.setSize(newSize.width || chart.chartWidth, newSize.height || chart.chartHeight);
          }
        }, true);

        scope.$on('highchartsng.reflow', function () {
          chart.reflow();
        });

        scope.$on('$destroy', function() {
          if (chart) {
            try{
              chart.destroy();
            }catch(ex){
              // fail silently as highcharts will throw exception if element doesn't exist
            }

            $timeout(function(){
              element.remove();
            }, 0);
          }
        });

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


<commit_before>/* @flow */

import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState } from '../util/push-state'

export class HTML5History extends History {
  constructor (router: Router, base: ?string) {
    super(router, base)

    const expectScroll = router.options.scrollBehavior

    if (expectScroll) {
      setupScroll()
    }

    window.addEventListener('popstate', e => {
      const current = this.current
      this.transitionTo(getLocation(this.base), route => {
        if (expectScroll) {
          handleScroll(router, route, current, true)
        }
      })
    })
  }

  go (n: number) {
    window.history.go(n)
  }

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      replaceState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  ensureURL (push?: boolean) {
    if (getLocation(this.base) !== this.current.fullPath) {
      const current = cleanPath(this.base + this.current.fullPath)
      push ? pushState(current) : replaceState(current)
    }
  }

  getCurrentLocation (): string {
    return getLocation(this.base)
  }
}

export function getLocation (base: string): string {
  let path = window.location.pathname
  if (base && path.indexOf(base) === 0) {
    path = path.slice(base.length)
  }
  return (path || '/') + window.location.search + window.location.hash
}
<commit_msg>Allow using `base` option with hash under history mode (#1349)<commit_after>/* @flow */

import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState } from '../util/push-state'

export class HTML5History extends History {
  constructor (router: Router, base: ?string) {
    super(router, base)

    const expectScroll = router.options.scrollBehavior

    if (expectScroll) {
      setupScroll()
    }

    window.addEventListener('popstate', e => {
      const current = this.current
      this.transitionTo(getLocation(this.base), route => {
        if (expectScroll) {
          handleScroll(router, route, current, true)
        }
      })
    })
  }

  go (n: number) {
    window.history.go(n)
  }

  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      pushState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const { current: fromRoute } = this
    this.transitionTo(location, route => {
      replaceState(cleanPath(this.base + route.fullPath))
      handleScroll(this.router, route, fromRoute, false)
      onComplete && onComplete(route)
    }, onAbort)
  }

  ensureURL (push?: boolean) {
    if (getLocation(this.base) !== this.current.fullPath) {
      const current = cleanPath(this.base + this.current.fullPath)
      push ? pushState(current) : replaceState(current)
    }
  }

  getCurrentLocation (): string {
    return getLocation(this.base)
  }
}

export function getLocation (base: string): string {
  let path = window.location.pathname

  // Special handling for cases where base contains hash ('#')
  if (base && base.indexOf('#') > -1) {
    path = window.location.href.replace(window.location.origin, '')
    if (path.indexOf(base) === 0) {
      // Leave the rest of the url as-is
      return (path.slice(base.length) || '/')
    }
  }

  if (base && path.indexOf(base) === 0) {
    path = path.slice(base.length)
  }
  return (path || '/') + window.location.search + window.location.hash
}
<|endoftext|>


<commit_before>import { shuffle_colors } from './actions';

let in_winning_state =
  function() {
    for (let i = 1; i < this.board.length; i++) {
      if (this.board[i].current_color !== this.board[i - 1].current_color) {
        return false;
      }
    }
    return true;
  }

let current_level =
  function() {
    return this.levels[this.current_level_index];
  }

let highest_unlocked_level =
  function() {
    let highest_level = 0;
    for (let [index, level] of this.levels.entries()) {
      if (level.best_score !== 'N/A') {
        highest_level = index + 1;
      } else {
        break;
      }
    }
    return highest_level;
  }

let persisted_state = localStorage.getItem('chromattis_saved_state');

// Rebuild the redux state by parsing the JSON string in localStorage
if (persisted_state) {
  persisted_state = JSON.parse(localStorage.getItem('chromattis_saved_state'));
  persisted_state.current_level = current_level;
  persisted_state.highest_unlocked_level = highest_unlocked_level;

  for (let level of persisted_state.levels) {
    level.in_winning_state = in_winning_state;
  }
}

// If there is no persisted_state in localStorage, initialize a new state with shuffled colors.
export const INITIAL_STATE = persisted_state ? persisted_state : shuffle_colors({
  levels: [{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 2]},
            {id: 2, will_change: false, current_color: null, target_tiles: [2, 1]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 2]},
            {id: 2, will_change: false, current_color: null, target_tiles: [2, 0]},
            {id: 3, will_change: false, current_color: null, target_tiles: [3, 2]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1, 3]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 3, 5]},
            {id: 2, will_change: false, current_color: null, target_tiles: [1, 2, 5]},
            {id: 3, will_change: false, current_color: null, target_tiles: [1, 3, 7]},
            {id: 4, will_change: false, current_color: null, target_tiles: [1, 3, 5, 7]},
            {id: 5, will_change: false, current_color: null, target_tiles: [2, 4, 4, 5, 8]},
            {id: 6, will_change: false, current_color: null, target_tiles: [3, 6, 7]},
            {id: 7, will_change: false, current_color: null, target_tiles: [6, 7, 8, 4]},
            {id: 8, will_change: false, current_color: null, target_tiles: [5, 7, 8]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },
  {
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1, 3, 4]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 5]},
            {id: 2, will_change: false, current_color: null, target_tiles: [1, 2, 4, 5]},
            {id: 3, will_change: false, current_color: null, target_tiles: [3, 1]},
            {id: 4, will_change: false, current_color: null, target_tiles: [4, 5]},
            {id: 5, will_change: false, current_color: null, target_tiles: [5, 7]},
            {id: 6, will_change: false, current_color: null, target_tiles: [3, 4, 6, 7]},
            {id: 7, will_change: false, current_color: null, target_tiles: [3, 7]},
            {id: 8, will_change: false, current_color: null, target_tiles: [4, 5, 7, 8]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },
  {
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1, 2, 3]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 4]},
            {id: 2, will_change: false, current_color: null, target_tiles: [2, 5, 8]},
            {id: 3, will_change: false, current_color: null, target_tiles: [3, 6, 9, 12]},
            {id: 4, will_change: false, current_color: null, target_tiles: [4, 5, 6, 7]},
            {id: 5, will_change: false, current_color: null, target_tiles: [5, 2, 8]},
            {id: 6, will_change: false, current_color: null, target_tiles: [6, 3, 9, 12]},
            {id: 7, will_change: false, current_color: null, target_tiles: [7, 10, 13]},
            {id: 8, will_change: false, current_color: null, target_tiles: [8, 9, 10, 11]},
            {id: 9, will_change: false, current_color: null, target_tiles: [9, 3, 6, 12]},
            {id: 10, will_change: false, current_color: null, target_tiles: [10, 7, 13]},
            {id: 11, will_change: false, current_color: null, target_tiles: [11, 14]},
            {id: 12, will_change: false, current_color: null, target_tiles: [12, 13, 14, 15]},
            {id: 13, will_change: false, current_color: null, target_tiles: [13, 9, 5, 1]},
            {id: 14, will_change: false, current_color: null, target_tiles: [14, 10, 6, 2]},
            {id: 15, will_change: false, current_color: null, target_tiles: [15, 11, 7, 3]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  }],
  current_level_index: 0,
  current_level: current_level,
  highest_unlocked_level: highest_unlocked_level
});
<commit_msg>Lightly modify existing levels.<commit_after>import { shuffle_colors } from './actions';

let in_winning_state =
  function() {
    for (let i = 1; i < this.board.length; i++) {
      if (this.board[i].current_color !== this.board[i - 1].current_color) {
        return false;
      }
    }
    return true;
  }

let current_level =
  function() {
    return this.levels[this.current_level_index];
  }

let highest_unlocked_level =
  function() {
    let highest_level = 0;
    for (let [index, level] of this.levels.entries()) {
      if (level.best_score !== 'N/A') {
        highest_level = index + 1;
      } else {
        break;
      }
    }
    return highest_level;
  }

let persisted_state = localStorage.getItem('chromattis_saved_state');

// Rebuild the redux state by parsing the JSON string in localStorage
if (persisted_state) {
  persisted_state = JSON.parse(localStorage.getItem('chromattis_saved_state'));
  persisted_state.current_level = current_level;
  persisted_state.highest_unlocked_level = highest_unlocked_level;

  for (let level of persisted_state.levels) {
    level.in_winning_state = in_winning_state;
  }
}

// If there is no persisted_state in localStorage, initialize a new state with shuffled colors.
export const INITIAL_STATE = persisted_state ? persisted_state : shuffle_colors({
  levels: [{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1]},
            {id: 1, will_change: false, current_color: null, target_tiles: [0, 2]},
            {id: 2, will_change: false, current_color: null, target_tiles: [2, 1]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 2]},
            {id: 2, will_change: false, current_color: null, target_tiles: [2, 0]},
            {id: 3, will_change: false, current_color: null, target_tiles: [3, 2]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1, 3, 4]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 5]},
            {id: 2, will_change: false, current_color: null, target_tiles: [1, 2, 4, 5]},
            {id: 3, will_change: false, current_color: null, target_tiles: [3, 1]},
            {id: 4, will_change: false, current_color: null, target_tiles: [4, 5]},
            {id: 5, will_change: false, current_color: null, target_tiles: [5, 7]},
            {id: 6, will_change: false, current_color: null, target_tiles: [3, 4, 6, 7]},
            {id: 7, will_change: false, current_color: null, target_tiles: [3, 7]},
            {id: 8, will_change: false, current_color: null, target_tiles: [4, 5, 7, 8]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1, 3]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 3, 5]},
            {id: 2, will_change: false, current_color: null, target_tiles: [1, 2, 5]},
            {id: 3, will_change: false, current_color: null, target_tiles: [1, 3, 7]},
            {id: 4, will_change: false, current_color: null, target_tiles: [1, 3, 5, 7]},
            {id: 5, will_change: false, current_color: null, target_tiles: [2, 4, 4, 5, 8]},
            {id: 6, will_change: false, current_color: null, target_tiles: [3, 6, 7]},
            {id: 7, will_change: false, current_color: null, target_tiles: [6, 7, 8, 4]},
            {id: 8, will_change: false, current_color: null, target_tiles: [5, 7, 8]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  },{
    board: [{id: 0, will_change: false, current_color: null, target_tiles: [0, 1, 2, 3]},
            {id: 1, will_change: false, current_color: null, target_tiles: [1, 4]},
            {id: 2, will_change: false, current_color: null, target_tiles: [2, 5, 8]},
            {id: 3, will_change: false, current_color: null, target_tiles: [3, 6, 9, 12]},
            {id: 4, will_change: false, current_color: null, target_tiles: [4, 5, 6, 7]},
            {id: 5, will_change: false, current_color: null, target_tiles: [5, 2, 8]},
            {id: 6, will_change: false, current_color: null, target_tiles: [6, 3, 9, 12]},
            {id: 7, will_change: false, current_color: null, target_tiles: [7, 10, 13]},
            {id: 8, will_change: false, current_color: null, target_tiles: [8, 9, 10, 11]},
            {id: 9, will_change: false, current_color: null, target_tiles: [9, 3, 6, 12]},
            {id: 10, will_change: false, current_color: null, target_tiles: [10, 7, 13]},
            {id: 11, will_change: false, current_color: null, target_tiles: [11, 14]},
            {id: 12, will_change: false, current_color: null, target_tiles: [12, 13, 14, 15]},
            {id: 13, will_change: false, current_color: null, target_tiles: [13, 9, 5, 1]},
            {id: 14, will_change: false, current_color: null, target_tiles: [14, 10, 6, 2]},
            {id: 15, will_change: false, current_color: null, target_tiles: [15, 11, 7, 3]}],
    moves: 0,
    best_score: 'N/A',
    currently_selected: null,
    in_winning_state: in_winning_state
  }],
  current_level_index: 0,
  current_level: current_level,
  highest_unlocked_level: highest_unlocked_level
});
<|endoftext|>


<commit_before>var log = debug('horacat'),
    logv = debug('horacat:verbose');

// TODO: make settable in options
var verbosity = 3;
switch (verbosity) {
  case 0:
    debug.enable('horacat');
    break;
  case 1:
    debug.enable('horacat,horacat:*,-horacat:verbose,-horacat:*:verbose');
    break;
  case 2:
    debug.enable('horacat,horacat:*,-horacat:*:verbose');
    break;
  case 3:
    debug.enable('horacat,horacat:*');
    break;
}

function stripUrl() {
  return window.location.href.replace(/\#.*?$/, '');
}

function getMetaData(name) {
  return $('meta[name="octolytics-' + name + '"]').attr('content');
}

function getIssue() {
  var issue = $('input[name="thread_id"]').val();
  if (!issue) {
    return;
  } else {
    var shaObj = new jsSHA(issue, "TEXT");
    var hash = shaObj.getHash("SHA-1", "HEX");
    logv('getIssue hashing:', issue, '->', hash);
    return hash;
  }
}

function getProject() {
  var project = getMetaData('dimension-repository_id');
  if (!project) {
    return;
  } else {
    var shaObj = new jsSHA(project, "TEXT");
    var hash = shaObj.getHash("SHA-1", "HEX");
    logv('getProject hashing:', project, '->', hash);
    return hash;
  }
}

function getUser() {
  var user = getMetaData('actor-hash'); // FIXME: too insecure?
  if (!user) {
    return;
  } else {
    var shaObj = new jsSHA(user, "TEXT");
    var hash = shaObj.getHash("SHA-1", "HEX");
    logv('getUser hashing:', user, '->', hash);
    return hash;
  }
}

function formatMs(s) {

  function addZ(n) {
    return (n<10? '0':'') + n;
  }

  var ms = s % 1000;
  s = (s - ms) / 1000;
  var secs = s % 60;
  s = (s - secs) / 60;
  var mins = s % 60;
  var hrs = (s - mins) / 60;

  return addZ(hrs) + ':' + addZ(mins) + ':' + addZ(secs);
}

function HoracatButton(user, project, issue) {
  this.log = debug('horacat:button');
  this.logv = debug('horacat:button:verbose');

  $('.gh-header-actions').prepend('
Start Timer00:00:00
');
  this.timer = document.getElementById('horacat-timer'); // timer object
  this.$ = $('.gh-header-horacat'); // jQuery object

  this.started = false;

  var _this = this;
  this.$.click(function() {
    _this.logv('clicked');
    if (_this.started) {
      _this.stop();
      _this.$.find('#horacat-toggle').text('Start Timer');
    } else {
      _this.start();
      _this.$.find('#horacat-toggle').text('Stop Timer');
    }
  });
  this.log('initialized');
}

HoracatButton.prototype.update = function update() {
  var time = formatMs(new Date() - this.startTime);
  this.timer.innerHTML = time;
};

HoracatButton.prototype.start = function start() {
  this.started = true;

  if (!this.loggedTime) {
    this.startTime = new Date();
  } else {
    this.startTime = new Date() - this.loggedTime;
  }

  var _this = this;
  this.interval = setInterval(function () {
    _this.update();
  }, 1000);
  this.log('started:', this.startTime);
  this.update();
};

HoracatButton.prototype.stop = function stop() {
  this.started = false;

  this.loggedTime = new Date() - this.startTime;
  clearInterval(this.interval);
  this.log('stopped:', this.loggedTime);
};

function init(user, project, issue) {
  log('user:', user);
  log('project:', project);
  log('issue:', issue);
  var button = new HoracatButton(user, project, issue);
}

chrome.extension.sendMessage({}, function(response) {
	var readyStateCheckInterval = setInterval(function() {
	if (document.readyState === "complete") {
		clearInterval(readyStateCheckInterval);

    var user = getUser();
    var project = getProject();
    var issue = getIssue();

    if (!user || !project || !issue) {
      log('not logged in or no issue available, disabling time tracking');
    } else {
      init(user, project, issue);
    }
	}
	}, 10);
});<commit_msg>store logged time to localStorage (closes #3)<commit_after>var log = debug('horacat'),
    logv = debug('horacat:verbose');

// TODO: make settable in options
var verbosity = 3;
switch (verbosity) {
  case 0:
    debug.enable('horacat');
    break;
  case 1:
    debug.enable('horacat,horacat:*,-horacat:verbose,-horacat:*:verbose');
    break;
  case 2:
    debug.enable('horacat,horacat:*,-horacat:*:verbose');
    break;
  case 3:
    debug.enable('horacat,horacat:*');
    break;
}

function stripUrl() {
  return window.location.href.replace(/\#.*?$/, '');
}

function getMetaData(name) {
  return $('meta[name="octolytics-' + name + '"]').attr('content');
}

function getIssue() {
  var issue = $('input[name="thread_id"]').val();
  if (!issue) {
    return;
  } else {
    var shaObj = new jsSHA(issue, "TEXT");
    var hash = shaObj.getHash("SHA-1", "HEX");
    logv('getIssue hashing:', issue, '->', hash);
    return hash;
  }
}

function getProject() {
  var project = getMetaData('dimension-repository_id');
  if (!project) {
    return;
  } else {
    var shaObj = new jsSHA(project, "TEXT");
    var hash = shaObj.getHash("SHA-1", "HEX");
    logv('getProject hashing:', project, '->', hash);
    return hash;
  }
}

function getUser() {
  var user = getMetaData('actor-hash'); // FIXME: too insecure?
  if (!user) {
    return;
  } else {
    var shaObj = new jsSHA(user, "TEXT");
    var hash = shaObj.getHash("SHA-1", "HEX");
    logv('getUser hashing:', user, '->', hash);
    return hash;
  }
}

function getUniqueId(user, project, issue) {
  if (!user) user = getUser();
  if (!project) project = getProject();
  if (!issue) issue = getIssue();

  var uniqueId = user + project + issue;
  var shaObj = new jsSHA(uniqueId, "TEXT");
  var hash = shaObj.getHash("SHA-1", "HEX");
  logv('getUniqueId hashing:', uniqueId, '->', hash);

  return hash;
}

function formatMs(s) {

  function addZ(n) {
    return (n<10? '0':'') + n;
  }

  var ms = s % 1000;
  s = (s - ms) / 1000;
  var secs = s % 60;
  s = (s - secs) / 60;
  var mins = s % 60;
  var hrs = (s - mins) / 60;

  return addZ(hrs) + ':' + addZ(mins) + ':' + addZ(secs);
}

function HoracatButton(user, project, issue) {
  this.log = debug('horacat:button');
  this.logv = debug('horacat:button:verbose');

  $('.gh-header-actions').prepend('
Start Timer00:00:00
');
  this.timer = document.getElementById('horacat-timer'); // timer object
  this.$ = $('.gh-header-horacat'); // jQuery object

  this.started = false;

  this.user = user;
  this.project = project;
  this.issue = issue;
  this.uniqueId = getUniqueId(user, project, issue);

  var lsData = localStorage.getItem('horacat.timer.' + this.uniqueId);
  try {
    lsData = JSON.parse(lsData);
  } catch (err) {
    lsData = null;
  }
  if (lsData && lsData.loggedTime) {
    this.loggedTime = lsData.loggedTime;
    this.startTime = (new Date() - this.loggedTime);
    this.update();
  }

  var _this = this;
  this.$.click(function() {
    _this.logv('clicked');
    if (_this.started) {
      _this.stop();
    } else {
      _this.start();
    }
  });

  $(window).unload(function(){
    _this.stop();
  });

  this.log('initialized');
}

HoracatButton.prototype.update = function update() {
  var time = formatMs(new Date() - this.startTime);
  this.timer.innerHTML = time;

  if (this.started) this.$.find('#horacat-toggle').text('Stop Timer');
  else this.$.find('#horacat-toggle').text('Start Timer');
};

HoracatButton.prototype.store = function store() {
  var name = "horacat.timer." + this.uniqueId;
  var data = {
    startTime: this.startTime,
    loggedTime: this.loggedTime
  };
  localStorage.setItem(name, JSON.stringify(data));
  this.log('stored:', name, '->', data);
};

HoracatButton.prototype.start = function start() {
  this.started = true;

  if (!this.loggedTime) {
    this.startTime = new Date();
  } else {
    this.startTime = new Date() - this.loggedTime;
  }

  this.store();

  var _this = this;
  this.interval = setInterval(function () {
    _this.update();
  }, 1000);
  this.log('started:', this.startTime);
  this.update();
};

HoracatButton.prototype.stop = function stop() {
  this.started = false;

  this.loggedTime = new Date() - this.startTime;

  this.store();

  clearInterval(this.interval);
  this.logv('stopped:', this.loggedTime);
};

function init(user, project, issue) {
  log('user:', user);
  log('project:', project);
  log('issue:', issue);
  var button = new HoracatButton(user, project, issue);
}

chrome.extension.sendMessage({}, function(response) {
	var readyStateCheckInterval = setInterval(function() {
	if (document.readyState === "complete") {
		clearInterval(readyStateCheckInterval);

    var user = getUser();
    var project = getProject();
    var issue = getIssue();

    if (!user || !project || !issue) {
      log('not logged in or no issue available, disabling time tracking');
    } else {
      init(user, project, issue);
    }
	}
	}, 10);
});<|endoftext|>


<commit_before>chrome.extension.sendMessage({}, function(response) {
  var readyStateCheckInterval = setInterval(function() {
  if (document.readyState === "complete") {
    clearInterval(readyStateCheckInterval);

    // ----------------------------------------------------------
    // This part of the script triggers when page is done loading
    console.log("Hello. This message was sent from scripts/inject.js");
    // ----------------------------------------------------------

  }
  }, 10);
});

var $weeklyLectures = $('.course-item-list-header');

$weeklyLectures.find('h3')
.append('<button class="alfred">download this week</button>');

$(document).on("click", "button.alfred", function (e) {
  var $header = $(this).closest('.course-item-list-header')
  e.stopPropagation();
  var $courses = $($header).next();
  $courses.find('.course-lecture-item-resource a')
  .each(function(index, elem){
    setTimeout(function(){
      elem.click();
    }, index*1000);
  });
});

<commit_msg>enter fullscreen mode when click fullscreen button<commit_after>chrome.extension.sendMessage({}, function(response) {
  var readyStateCheckInterval = setInterval(function() {
  if (document.readyState === "complete") {
    clearInterval(readyStateCheckInterval);

    // ----------------------------------------------------------
    // This part of the script triggers when page is done loading
    console.log("Hello. This message was sent from scripts/inject.js");
    // ----------------------------------------------------------

  }
  }, 10);
});

var $weeklyLectures = $('.course-item-list-header');

$weeklyLectures.find('h3')
.append('<button class="alfred">download this week</button>');

$(document).on("click", "button.alfred", function (e) {
  var $header = $(this).closest('.course-item-list-header')
  e.stopPropagation();
  var $courses = $($header).next();
  $courses.find('.course-lecture-item-resource a')
  .each(function(index, elem){
    setTimeout(function(){
      elem.click();
    }, index*1000);
  });
}).on("click",".lecture-link",function(){
    var readyInterval = setInterval(
        function(){
            var $ifr = $('iframe');
            if($ifr.length){
                var $doc = $($ifr[0].contentDocument);
                if($doc.find(".mejs-fullscreen-button").length){
                    var el = document;
                    var elm = el.documentElement;
                    clearInterval(readyInterval);
                    $doc.on('click','.mejs-fullscreen-button button',function(){
                        if(el.webkitIsFullScreen ||el.fullscreenElement){
                            var efs = el.exitFullscreen || el.webkitCancelFullScreen;
                            efs.call(el);
                        }
                        else{
                            var rfs = elm.requestFullScreen || elm.webkitRequestFullScreen;
                            rfs.call(elm);
                        }
                    });
                }
            }
        },10);
});
<|endoftext|>


<commit_before>/*jslint newcap: true */
/*global XMLHttpRequest: false, inlineAttach: false, FormData: false */
/*
 * Inline Text Attachment
 *
 * Copyright 2012 Roy van Kaathoven.
 * Contact: royvankaathoven@hotmail.com
 *
 * Licensed under the MIT License.
 */
(function(document, window) {
    "use strict";

    /**
     * Simple function to merge the given objects
     *
     * @param {Object[]} object Multiple object parameters
     * @returns {Object}
     */
    function merge() {
        var result = {};
        for (var i = arguments.length - 1; i >= 0; i--) {
            var obj = arguments[i];
            for (var k in obj) {
                result[k] = obj[k];
            }
        }
        return result;
    }

    /**
     * @param {Object} options
     */
    window.inlineAttach = function(options, instance) {

        var settings = merge(options, inlineAttach.defaults),
            editor = instance,
            filenameTag = '{filename}',
            lastValue,
            me = this;

        /**
         * Upload a given file blob
         *
         * @param {Blob} file
         */
        this.uploadFile = function(file) {
            var formData = new FormData(),
                xhr = new XMLHttpRequest();

            // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now)
            // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name
            formData.append(settings.uploadFieldName, file, "image-" + Date.now() + ".png");

            xhr.open('POST', settings.uploadUrl);
            xhr.onload = function() {
                // If HTTP status is OK or Created
                var data = JSON.parse(xhr.responseText);
                if (xhr.status === 200 || xhr.status === 201) {
                    me.onUploadedFile(data);
                } else {
                    me.onErrorUploading(data);
                }
            };
            xhr.send(formData);
        };

        /**
         * Check if the given file is allowed
         *
         * @param {File} file
         */
        this.isAllowedFile = function(file) {
            return settings.allowedTypes.indexOf(file.type) >= 0;
        };

        /**
         * When a file has finished uploading
         *
         * @param {Object} data
         */
        this.onUploadedFile = function(data) {
            var result = settings.onUploadedFile(data),
                filename = data[settings.downloadFieldName];
            if (result !== false && filename) {
                var text = editor.getValue().replace(lastValue, settings.urlText.replace(filenameTag, filename));
                editor.setValue(text);
            }
        };

        /**
         * Custom upload handler
         *
         * @param {Blob} file
         * @return {Boolean} when false is returned it will prevent default upload behavior
         */
        this.customUploadHandler = function(file) {
            return settings.customUploadHandler(file);
        };

        /**
         * When a file didn't upload properly.
         * Override by passing your own onErrorUploading function with settings.
         *
         * @param {Object} data
         */
        this.onErrorUploading = function(data) {
            var text = editor.getValue().replace(lastValue, "");
            editor.setValue(text);
            if (settings.customErrorHandler(data)) {
                window.alert(settings.errorText);
            }
        };

        /**
         * Append a line of text at the bottom, ensuring there aren't unnecessary newlines
         *
         * @param {String} appended Current content
         * @param {String} previous Value which should be appended after the current content
         */
        function appendInItsOwnLine(previous, appended) {
            return (previous + "\n\n[[D]]" + appended)
                  .replace(/(\n{2,})\[\[D\]\]/, "\n\n")
                  .replace(/^(\n*)/, "");
        }

        /**
         * When a file has been received by a drop or paste event
         * @param {Blob} file
         */
        this.onReceivedFile = function(file) {
            var result = settings.onReceivedFile(file);
            if (result !== false) {
                lastValue = settings.progressText;
                editor.setValue(appendInItsOwnLine(editor.getValue(), lastValue));
            }
        };

        /**
         * Catches the paste event
         *
         * @param {Event} e
         * @returns {Boolean} If a file is handled
         */
        this.onPaste = function(e) {
            var result = false,
                clipboardData = e.clipboardData;

            if (typeof clipboardData === "object" && clipboardData.items !== null) {
                for (var i = 0; i < clipboardData.items.length; i++) {
                    var item = clipboardData.items[i];
                    if (me.isAllowedFile(item)) {
                        result = true;
                        this.onReceivedFile(item.getAsFile());
                        if(this.customUploadHandler(item.getAsFile())){
                            this.uploadFile(item.getAsFile());
                        }
                    }
                }
            }

            return result;
        };

        /**
         * Catches onDrop event
         *
         * @param {Event} e
         * @returns {Boolean} If a file is handled
         */
        this.onDrop = function(e) {
            var result = false;
            for (var i = 0; i < e.dataTransfer.files.length; i++) {
                var file = e.dataTransfer.files[i];
                if (me.isAllowedFile(file)) {
                    result = true;
                    this.onReceivedFile(file);
                    if(this.customUploadHandler(file)){
                        this.uploadFile(file);
                    }
                }
            }

            return result;
        };
    };

    /**
     * Editor
     */
    window.inlineAttach.Editor = function(instance) {

        var input = instance;

        return {
            getValue: function() {
                return input.value;
            },
            setValue: function(val) {
                input.value = val;
            }
        };
    };

    /**
     * Default configuration
     */
    window.inlineAttach.defaults = {
        // URL to upload the attachment
        uploadUrl: 'upload_attachment.php',
        // Request field name where the attachment will be placed in the form data
        uploadFieldName: 'file',
        // Where is the filename placed in the response
        downloadFieldName: 'filename',
        allowedTypes: [
            'image/jpeg',
            'image/png',
            'image/jpg',
            'image/gif'
        ],

        /**
         * Will be inserted on a drop or paste event
         */
        progressText: '![Uploading file...]()',

        /**
         * When a file has successfully been uploaded the last inserted text
         * will be replaced by the urlText, the {filename} tag will be replaced
         * by the filename that has been returned by the server
         */
        urlText: "![file]({filename})",

        /**
         * When a file is received by drag-drop or paste
         */
        onReceivedFile: function() {},

        /**
         * Custom upload handler
         *
         * @return {Boolean} when false is returned it will prevent default upload behavior
         */
        customUploadHandler: function() { return true; },

        /**
         * Custom error handler. Runs after removing the placeholder text and before the alert().
         * Return false from this function to prevent the alert dialog.
         *
         * @return {Boolean} when false is returned it will prevent default error behavior
         */
        customErrorHandler: function() { return true; },

        /**
         * Text for default error when uploading
         */
        errorText: "Error uploading file",

        /**
         * When a file has succesfully been uploaded
         */
        onUploadedFile: function() {}
    };

    /**
     * Attach to a standard input field
     *
     * @param {Input} input
     * @param {Object} options
     */
    window.inlineAttach.attachToInput = function(input, options) {

        options = options || {};

        var editor          = new inlineAttach.Editor(input),
            inlineattach    = new inlineAttach(options, editor);

        input.addEventListener('paste', function(e) {
            inlineattach.onPaste(e);
        }, false);
        input.addEventListener('drop', function(e) {
            e.stopPropagation();
            e.preventDefault();
            inlineattach.onDrop(e);
        }, false);
        input.addEventListener('dragenter', function(e) {
            e.stopPropagation();
            e.preventDefault();
        }, false);
        input.addEventListener('dragover', function(e) {
            e.stopPropagation();
            e.preventDefault();
        }, false);
    };

})(document, window);
<commit_msg>Don't pass invalid JSON on errors<commit_after>/*jslint newcap: true */
/*global XMLHttpRequest: false, inlineAttach: false, FormData: false */
/*
 * Inline Text Attachment
 *
 * Copyright 2012 Roy van Kaathoven.
 * Contact: royvankaathoven@hotmail.com
 *
 * Licensed under the MIT License.
 */
(function(document, window) {
    "use strict";

    /**
     * Simple function to merge the given objects
     *
     * @param {Object[]} object Multiple object parameters
     * @returns {Object}
     */
    function merge() {
        var result = {};
        for (var i = arguments.length - 1; i >= 0; i--) {
            var obj = arguments[i];
            for (var k in obj) {
                result[k] = obj[k];
            }
        }
        return result;
    }

    /**
     * @param {Object} options
     */
    window.inlineAttach = function(options, instance) {

        var settings = merge(options, inlineAttach.defaults),
            editor = instance,
            filenameTag = '{filename}',
            lastValue,
            me = this;

        /**
         * Upload a given file blob
         *
         * @param {Blob} file
         */
        this.uploadFile = function(file) {
            var formData = new FormData(),
                xhr = new XMLHttpRequest();

            // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now)
            // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name
            formData.append(settings.uploadFieldName, file, "image-" + Date.now() + ".png");

            xhr.open('POST', settings.uploadUrl);
            xhr.onload = function() {
                // If HTTP status is OK or Created
                if (xhr.status === 200 || xhr.status === 201) {
                    var data = JSON.parse(xhr.responseText);
                    me.onUploadedFile(data);
                } else {
                    me.onErrorUploading();
                }
            };
            xhr.send(formData);
        };

        /**
         * Check if the given file is allowed
         *
         * @param {File} file
         */
        this.isAllowedFile = function(file) {
            return settings.allowedTypes.indexOf(file.type) >= 0;
        };

        /**
         * When a file has finished uploading
         *
         * @param {Object} data
         */
        this.onUploadedFile = function(data) {
            var result = settings.onUploadedFile(data),
                filename = data[settings.downloadFieldName];
            if (result !== false && filename) {
                var text = editor.getValue().replace(lastValue, settings.urlText.replace(filenameTag, filename));
                editor.setValue(text);
            }
        };

        /**
         * Custom upload handler
         *
         * @param {Blob} file
         * @return {Boolean} when false is returned it will prevent default upload behavior
         */
        this.customUploadHandler = function(file) {
            return settings.customUploadHandler(file);
        };

        /**
         * When a file didn't upload properly.
         * Override by passing your own onErrorUploading function with settings.
         *
         * @param {Object} data
         */
        this.onErrorUploading = function() {
            var text = editor.getValue().replace(lastValue, "");
            editor.setValue(text);
            if (settings.customErrorHandler()) {
                window.alert(settings.errorText);
            }
        };

        /**
         * Append a line of text at the bottom, ensuring there aren't unnecessary newlines
         *
         * @param {String} appended Current content
         * @param {String} previous Value which should be appended after the current content
         */
        function appendInItsOwnLine(previous, appended) {
            return (previous + "\n\n[[D]]" + appended)
                  .replace(/(\n{2,})\[\[D\]\]/, "\n\n")
                  .replace(/^(\n*)/, "");
        }

        /**
         * When a file has been received by a drop or paste event
         * @param {Blob} file
         */
        this.onReceivedFile = function(file) {
            var result = settings.onReceivedFile(file);
            if (result !== false) {
                lastValue = settings.progressText;
                editor.setValue(appendInItsOwnLine(editor.getValue(), lastValue));
            }
        };

        /**
         * Catches the paste event
         *
         * @param {Event} e
         * @returns {Boolean} If a file is handled
         */
        this.onPaste = function(e) {
            var result = false,
                clipboardData = e.clipboardData;

            if (typeof clipboardData === "object" && clipboardData.items !== null) {
                for (var i = 0; i < clipboardData.items.length; i++) {
                    var item = clipboardData.items[i];
                    if (me.isAllowedFile(item)) {
                        result = true;
                        this.onReceivedFile(item.getAsFile());
                        if(this.customUploadHandler(item.getAsFile())){
                            this.uploadFile(item.getAsFile());
                        }
                    }
                }
            }

            return result;
        };

        /**
         * Catches onDrop event
         *
         * @param {Event} e
         * @returns {Boolean} If a file is handled
         */
        this.onDrop = function(e) {
            var result = false;
            for (var i = 0; i < e.dataTransfer.files.length; i++) {
                var file = e.dataTransfer.files[i];
                if (me.isAllowedFile(file)) {
                    result = true;
                    this.onReceivedFile(file);
                    if(this.customUploadHandler(file)){
                        this.uploadFile(file);
                    }
                }
            }

            return result;
        };
    };

    /**
     * Editor
     */
    window.inlineAttach.Editor = function(instance) {

        var input = instance;

        return {
            getValue: function() {
                return input.value;
            },
            setValue: function(val) {
                input.value = val;
            }
        };
    };

    /**
     * Default configuration
     */
    window.inlineAttach.defaults = {
        // URL to upload the attachment
        uploadUrl: 'upload_attachment.php',
        // Request field name where the attachment will be placed in the form data
        uploadFieldName: 'file',
        // Where is the filename placed in the response
        downloadFieldName: 'filename',
        allowedTypes: [
            'image/jpeg',
            'image/png',
            'image/jpg',
            'image/gif'
        ],

        /**
         * Will be inserted on a drop or paste event
         */
        progressText: '![Uploading file...]()',

        /**
         * When a file has successfully been uploaded the last inserted text
         * will be replaced by the urlText, the {filename} tag will be replaced
         * by the filename that has been returned by the server
         */
        urlText: "![file]({filename})",

        /**
         * When a file is received by drag-drop or paste
         */
        onReceivedFile: function() {},

        /**
         * Custom upload handler
         *
         * @return {Boolean} when false is returned it will prevent default upload behavior
         */
        customUploadHandler: function() { return true; },

        /**
         * Custom error handler. Runs after removing the placeholder text and before the alert().
         * Return false from this function to prevent the alert dialog.
         *
         * @return {Boolean} when false is returned it will prevent default error behavior
         */
        customErrorHandler: function() { return true; },

        /**
         * Text for default error when uploading
         */
        errorText: "Error uploading file",

        /**
         * When a file has succesfully been uploaded
         */
        onUploadedFile: function() {}
    };

    /**
     * Attach to a standard input field
     *
     * @param {Input} input
     * @param {Object} options
     */
    window.inlineAttach.attachToInput = function(input, options) {

        options = options || {};

        var editor          = new inlineAttach.Editor(input),
            inlineattach    = new inlineAttach(options, editor);

        input.addEventListener('paste', function(e) {
            inlineattach.onPaste(e);
        }, false);
        input.addEventListener('drop', function(e) {
            e.stopPropagation();
            e.preventDefault();
            inlineattach.onDrop(e);
        }, false);
        input.addEventListener('dragenter', function(e) {
            e.stopPropagation();
            e.preventDefault();
        }, false);
        input.addEventListener('dragover', function(e) {
            e.stopPropagation();
            e.preventDefault();
        }, false);
    };

})(document, window);
<|endoftext|>