<commit_before>/*
 * grunt-nice-package
 * https://github.com/bahmutov/grunt-nice-package
 *
 * Copyright (c) 2013 Gleb Bahmutov
 * Licensed under the MIT license.
 */

'use strict';

var PJV = require('package-json-validator').PJV;
var check = require('check-types');
var verify = check.verify;
var fs = require('fs');
var join = require('path').join;
var unary = require('./utils').unary;
var find = require('./utils').find;
var initValidators = require('./validators');

var taskName = 'nice-package';
var taskDescription = 'Opinionated package.json validator';

function warnOnLooseVersion(grunt, name, version) {
  verify.unemptyString(name, 'missing name');
  verify.unemptyString(version, 'missing version for', name);

  if (/\*|\^|\~/.test(version)) {
    grunt.log.warn('loose version', version, 'for dependency', name);
  }
}

function tightenVersion(version) {
  verify.unemptyString(version, 'expected version string, got ' + version);
  return version.replace('^', '').replace('~', '');
}

// removes ~, ^, etc from dependencies versions
function tightenDependenciesVersions(grunt, deps) {
  console.assert(deps, 'missing deps object');
  Object.keys(deps).forEach(function (name) {
    var version = deps[name];
    warnOnLooseVersion(grunt, name, version);
    version = tightenVersion(version);
    deps[name] = version;
  });
}

function tightenVersions(grunt, cb) {
  var pkg = grunt.file.readJSON('package.json');
  if (pkg.dependencies) {
    tightenDependenciesVersions(grunt, pkg.dependencies);
  }

  if (pkg.devDependencies) {
    tightenDependenciesVersions(grunt, pkg.devDependencies);
  }

  if (pkg.peerDependencies) {
    tightenDependenciesVersions(grunt, pkg.peerDependencies);
  }

  fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
  cb();
}

function findFixpack() {
  var choices = [
    join(__dirname, '../node_modules/.bin/fixpack'),
    join(__dirname, '../node_modules/fixpack/fixpack.js')
  ];
  return find(choices, fs.existsSync);
}

function sortPackageProperties(grunt, done, options, valid) {
  options = options || {};
  var fixpack = findFixpack();
  if (!check.unemptyString(fixpack)) {
    grunt.log.warn('Could not find fixpack, skipping ...');
    done(true);
    return;
  }

  var exec = require('child_process').exec;

  exec('node "' + fixpack + '"', function (error, stdout, stderr) {
    if (error) {
      grunt.log.error(error);
      done(false);
    } else {
      grunt.log.writeln(stdout);
      if (stderr) {
        grunt.log.warn(stderr);
      }

      if (options.blankLine) {
        var txt = fs.readFileSync('package.json');
        if (!/\n\n$/.test(txt)) {
          txt += '\n';
          fs.writeFileSync('package.json', txt);
        }
      }
      done(valid);
    }
  });
}

function checkProperties(options, grunt, pkg) {
  var every = Object.keys(options).every(function (key) {
    grunt.verbose.writeln('checking property', key);

    var property = pkg[key];
    if (!property) {
      grunt.log.error('package.json missing', key);
      return false;
    }
    if (typeof options[key] === 'function') {
      if (!options[key](property)) {
        grunt.log.error('failed check for property', key);
        return false;
      }
    }

    return true;
  });
  return every;
}

function isValidLicense(pkg) {
  return check.string(pkg.license) ||
    check.array(pkg.licenses);
}

function printErrors(grunt, result) {
  if (result &&
    !result.valid &&
    check.array(result.errors)) {
    grunt.log.subhead('Errors:');
    result.errors.forEach(unary(grunt.log.error));
  }
}

function makePackageNicer(grunt, validators, done, options) {
  validators = validators || {};
  options = options || {};
  verify.fn(done, 'expected done to be a function');

  var pkg = grunt.file.readJSON('package.json');
  var every = checkProperties(validators, grunt, pkg);

  // advanced checking
  if (!isValidLicense(pkg)) {
    grunt.log.error('missing license information');
    return done(false);
  }

  if (!fs.existsSync('./README.md')) {
    grunt.log.error('missing README.md file');
    return done(false);
  }

  var pkgText = JSON.stringify(pkg, null, 2);
  pkgText = pkgText.replace(/\^/g, '');

  var result = every && PJV.validate(pkgText);
  printErrors(grunt, result);

  if (check.array(result.warnings) &&
    result.warnings.length) {
    grunt.log.subhead('Warnings:');
    result.warnings.forEach(unary(grunt.log.warn));
  }

  tightenVersions(grunt, function () {
    sortPackageProperties(grunt, done, options.blankLine, !!result.valid);
  });
}

function registerUnderTaskName(name, grunt, defaultValidators) {
  verify.unemptyString(name, 'expected name');
  grunt.verbose.writeln('Using', name, 'multi task');

  grunt.registerMultiTask(name, taskDescription, function() {
    // Merge custom validation functions with default ones
    defaultValidators.fix = true;
    var options = this.options(defaultValidators);
    var blankLine = Boolean(options.blankLine);
    delete options.blankLine;
    var fix = Boolean(options.fix);
    delete options.fix;

    var extraOptions = {
      blankLine: blankLine,
      fix: fix
    };
    makePackageNicer(grunt, options, this.async(), extraOptions);
  });
}

module.exports = function(grunt) {
  var defaultValidators = initValidators(grunt);

  var found;
  if (grunt.config.data[taskName]) {
    found = true;
    registerUnderTaskName(taskName, grunt, defaultValidators);
  }

  if (grunt.config.data.nicePackage) {
    found = true;
    registerUnderTaskName('nicePackage', grunt, defaultValidators);
  }

  if (!found) {
    grunt.verbose.writeln('Using default', taskName, 'task');

    grunt.registerTask(taskName, taskDescription, function () {
      makePackageNicer(grunt, defaultValidators, this.async());
    });
  }
};
<commit_msg>better file save name<commit_after>/*
 * grunt-nice-package
 * https://github.com/bahmutov/grunt-nice-package
 *
 * Copyright (c) 2013 Gleb Bahmutov
 * Licensed under the MIT license.
 */

'use strict';

var PJV = require('package-json-validator').PJV;
var check = require('check-types');
var verify = check.verify;
var fs = require('fs');
var join = require('path').join;
var unary = require('./utils').unary;
var find = require('./utils').find;
var initValidators = require('./validators');
var save = fs.writeFileSync;

var taskName = 'nice-package';
var taskDescription = 'Opinionated package.json validator';

function warnOnLooseVersion(grunt, name, version) {
  verify.unemptyString(name, 'missing name');
  verify.unemptyString(version, 'missing version for', name);

  if (/\*|\^|\~/.test(version)) {
    grunt.log.warn('loose version', version, 'for dependency', name);
  }
}

function tightenVersion(version) {
  verify.unemptyString(version, 'expected version string, got ' + version);
  return version.replace('^', '').replace('~', '');
}

// removes ~, ^, etc from dependencies versions
function tightenDependenciesVersions(grunt, deps) {
  console.assert(deps, 'missing deps object');
  Object.keys(deps).forEach(function (name) {
    var version = deps[name];
    warnOnLooseVersion(grunt, name, version);
    version = tightenVersion(version);
    deps[name] = version;
  });
}

function tightenVersions(grunt, cb) {
  var pkg = grunt.file.readJSON('package.json');
  if (pkg.dependencies) {
    tightenDependenciesVersions(grunt, pkg.dependencies);
  }

  if (pkg.devDependencies) {
    tightenDependenciesVersions(grunt, pkg.devDependencies);
  }

  if (pkg.peerDependencies) {
    tightenDependenciesVersions(grunt, pkg.peerDependencies);
  }

  save('package.json', JSON.stringify(pkg, null, 2), 'utf8');
  cb();
}

function findFixpack() {
  var choices = [
    join(__dirname, '../node_modules/.bin/fixpack'),
    join(__dirname, '../node_modules/fixpack/fixpack.js')
  ];
  return find(choices, fs.existsSync);
}

function sortPackageProperties(grunt, done, options, valid) {
  options = options || {};
  var fixpack = findFixpack();
  if (!check.unemptyString(fixpack)) {
    grunt.log.warn('Could not find fixpack, skipping ...');
    done(true);
    return;
  }

  var exec = require('child_process').exec;

  exec('node "' + fixpack + '"', function (error, stdout, stderr) {
    if (error) {
      grunt.log.error(error);
      done(false);
    } else {
      grunt.log.writeln(stdout);
      if (stderr) {
        grunt.log.warn(stderr);
      }

      if (options.blankLine) {
        var txt = fs.readFileSync('package.json');
        if (!/\n\n$/.test(txt)) {
          txt += '\n';
          save('package.json', txt, 'utf8');
        }
      }
      done(valid);
    }
  });
}

function checkProperties(options, grunt, pkg) {
  var every = Object.keys(options).every(function (key) {
    grunt.verbose.writeln('checking property', key);

    var property = pkg[key];
    if (!property) {
      grunt.log.error('package.json missing', key);
      return false;
    }
    if (typeof options[key] === 'function') {
      if (!options[key](property)) {
        grunt.log.error('failed check for property', key);
        return false;
      }
    }

    return true;
  });
  return every;
}

function isValidLicense(pkg) {
  return check.string(pkg.license) ||
    check.array(pkg.licenses);
}

function printErrors(grunt, result) {
  if (result &&
    !result.valid &&
    check.array(result.errors)) {
    grunt.log.subhead('Errors:');
    result.errors.forEach(unary(grunt.log.error));
  }
}

function makePackageNicer(grunt, validators, done, options) {
  validators = validators || {};
  options = options || {};
  verify.fn(done, 'expected done to be a function');

  var pkg = grunt.file.readJSON('package.json');
  var every = checkProperties(validators, grunt, pkg);

  // advanced checking
  if (!isValidLicense(pkg)) {
    grunt.log.error('missing license information');
    return done(false);
  }

  if (!fs.existsSync('./README.md')) {
    grunt.log.error('missing README.md file');
    return done(false);
  }

  var pkgText = JSON.stringify(pkg, null, 2);
  pkgText = pkgText.replace(/\^/g, '');

  var result = every && PJV.validate(pkgText);
  printErrors(grunt, result);

  if (check.array(result.warnings) &&
    result.warnings.length) {
    grunt.log.subhead('Warnings:');
    result.warnings.forEach(unary(grunt.log.warn));
  }

  tightenVersions(grunt, function () {
    sortPackageProperties(grunt, done, options.blankLine, !!result.valid);
  });
}

function registerUnderTaskName(name, grunt, defaultValidators) {
  verify.unemptyString(name, 'expected name');
  grunt.verbose.writeln('Using', name, 'multi task');

  grunt.registerMultiTask(name, taskDescription, function() {
    // Merge custom validation functions with default ones
    defaultValidators.fix = true;
    var options = this.options(defaultValidators);
    var blankLine = Boolean(options.blankLine);
    delete options.blankLine;
    var fix = Boolean(options.fix);
    delete options.fix;

    var extraOptions = {
      blankLine: blankLine,
      fix: fix
    };
    makePackageNicer(grunt, options, this.async(), extraOptions);
  });
}

module.exports = function(grunt) {
  var defaultValidators = initValidators(grunt);

  var found;
  if (grunt.config.data[taskName]) {
    found = true;
    registerUnderTaskName(taskName, grunt, defaultValidators);
  }

  if (grunt.config.data.nicePackage) {
    found = true;
    registerUnderTaskName('nicePackage', grunt, defaultValidators);
  }

  if (!found) {
    grunt.verbose.writeln('Using default', taskName, 'task');

    grunt.registerTask(taskName, taskDescription, function () {
      makePackageNicer(grunt, defaultValidators, this.async());
    });
  }
};
<|endoftext|>


<commit_before>Module.register("weather", {
  defaults: {
    // Whether to load canned weather data from a stub response (for development
    // and testing).
    debug: false,
    // Whether to show a textual forecast summary.
    showForecastSummary: true,
    // Whether to show weekly forecast data.
    showWeeklyForecast: true,
    // Whether to show hourly weather chart.
    showChart: true,
    // Darksky API key.  This value MUST be set for this module to work. A key
    // can be obtained from https://darksky.net/dev/
    apiKey: '',
    // Latitude and longitude for which weather data should be displayed, in
    // 'LAT,LNG' format.  For example, 37.795444,-122.393444.  This value MUST
    // be set for this module to work.
    latLng: '',
    // Time in milliseconds between weather updates.  Darksky provides 1000
    // requests per day free.  To stay under that quota, choose a config value
    // of at least ((24*60*60*1000)/1000)==86400.
    updateInterval: 1000 * 60 * 15,  // 15 minutes
    // The maximum age in milliseconds for which a forecast will be displayed.
    // If data cannot be updated before this limit, the UI will be hidden, so
    // as to prevent the display of stale forecast data.  This value MUST be
    // greater than 'updateInterval'.
    dataAgeLimit: 1000 * 60 * 60 * 3,  // 3 hours
    // Duration in milliseconds for animating in new weather data.
    animationDuration: 500  // 0.5 seconds
  },

  start: function() {
    Log.info('starting weather');

    this.mainTemplate = Handlebars.compile(templates.weather.main);
    if (this.config.showChart) {
      google.charts.load('current', {packages: ['corechart']});
    }

    if (this.config.debug) {
      Log.info(DEBUG_DATA);
      this.lastUpdateTimestamp = Date.now();
      this.forecastData = DEBUG_DATA;
    } else {
      this.lastUpdateTimestamp = 0;
      this.forecastData = null;
      this.downloadForecast();
      setInterval(this.downloadForecast.bind(this), this.config.updateInterval);
    }
  },

  getScripts: function() {
    var scripts = [
      '/modules/hurst/shared/vendor/handlebars.js',
      '/weather/templates.js'
    ];
    if (this.config.showChart) {
      scripts.push('https://www.gstatic.com/charts/loader.js');
    }
    if (this.config.debug) {
      scripts.push(this.file('debug_forecast_response.js'));
    }
    return scripts;
  },

  getStyles: function() {
    return [this.file('styles.css')];
  },

  suspend: function() {
    Log.info('suspending weather');
  },

  resume: function() {
    Log.info('resuming weather');
  },

  // Initiates download of Darksky weather forecast data.
  downloadForecast: function() {
    var url = 'https://api.darksky.net/forecast/' +
        this.config.apiKey + '/' + this.config.latLng;
    this.sendSocketNotification('download', url);
  },

  socketNotificationReceived: function(notification, payload) {
    if (notification == 'download') {
      try {
        var data = JSON.parse(payload);
        if (this.isForecastDataValid(data)) {
          this.forecastData = data;
          this.lastUpdateTimestamp = Date.now();
          this.updateDom(this.config.animationDuration);
        } else {
          Log.error('Invalid forecast data.');
        }
      } catch (err) {
        Log.error(err.toString());
      }
    }
  },

  getDom: function() {
    Log.info('updating weather dom');

    if (!this.forecastData ||
        (Date.now() - this.lastUpdateTimestamp) > this.config.dataAgeLimit) {
      return document.createElement('div');
    }

    this.dom = document.createElement('div');
    this.viewModel = this.getViewModel(this.forecastData);
    this.dom.innerHTML = this.mainTemplate(this.viewModel);

    if (this.config.showChart) {
      google.charts.setOnLoadCallback(this.drawCharts.bind(this));
    }

    return this.dom;
  },

  // Converts Darksky forecast api data to view model object passed to handlebar
  // templates.
  // See https://darksky.net/dev/docs for parameter format.
  getViewModel: function(data) {
    var r = {};

    // current conditions
    r.temperature = Math.round(data.currently.apparentTemperature);
    r.summary = data.currently.summary;
    r.windSpeed = Math.round(data.currently.windSpeed);
    r.cloudCover = Math.round(100 * data.currently.cloudCover);
    var today = data.daily.data[0];
    r.low = Math.round(today.apparentTemperatureLow);
    r.high = Math.round(today.apparentTemperatureHigh);
    // Show average precipitation probability for the current day, instead of
    // instantaneous probability.  This is more useful for determining if you
    // need an umbrella.
    r.precipProbability = Math.round(100 * today.precipProbability);
    // sunrise/sunset times
    r.sunrise = new Date(data.daily.data[0].sunriseTime * 1000)
        .toLocaleTimeString().replace(/:\d\d\s/, '').toLowerCase();
    r.sunset =  new Date(data.daily.data[0].sunsetTime * 1000)
        .toLocaleTimeString().replace(/:\d\d\s/, '').toLowerCase();
    r.shortForecast = [];
    for (var i = 1; i < 3; i++) {
      var day = data.daily.data[i];
      r.shortForecast.push({
        low: Math.round(day.apparentTemperatureLow),
        high: Math.round(day.apparentTemperatureHigh),
        iconUrl: this.getIconUrl(day.icon),
        day: new Date(day.sunriseTime * 1000)
          .toDateString().replace(/\s.*/, '')
      });
    }

    // weather alerts
    if (data.alerts && data.alerts.length) {
      // De-duplicate alerts.
      var alertSet = {}
      r.alerts = [];
      for (var i = 0; i < data.alerts.length; i++) {
        var title = data.alerts[i].title;
        if (!alertSet[title]) {
          alertSet[title] = 1;
          r.alerts.push({title: title.replace("for San Francisco, CA", "")});
        }
      }
    }

    // 24h rain, temperature, and wind forecast chart
    if (this.config.showChart) {
      r.chart = true;
      r.hourLabels = [];
      r.temperatures = [];
      r.windSpeeds = [];
      r.precipitationProbabilities = [];
      for (var i = 0; i < 24; i++) {
        var hour = data.hourly.data[i];
        var h = new Date(hour.time * 1000);
        if (i % 4 == 2) {
          r.hourLabels.push(new Date(hour.time * 1000)
              .toLocaleTimeString().replace(/:\d\d:\d\d\s/, '').toLowerCase());
        } else {
          r.hourLabels.push('');
        }
        r.precipitationProbabilities.push(hour.precipProbability);
        r.temperatures.push(Math.round(hour.apparentTemperature));
        r.windSpeeds.push(Math.round(hour.windSpeed));
      }
    } else {
      r.chart = false;
    }

    // forecast summaries
    if (this.config.showForecastSummary) {
      r.hourSummary = data.minutely.summary;
      r.daySummary = data.hourly.summary;
      r.weekSummary = data.daily.summary.replace('°F', '°');
    }

    // daily forecast
    if (this.config.showWeeklyForecast) {
      r.dailyForecasts = [];
      var weeklyMin = 1000;
      var weeklyMax = -1;
      var opacity = 0.75;
      // skip day 0 (today) since it's covered elsewhere in the UI
      for (var i = 1; i < 7; i++) {
        var day = data.daily.data[i];
        r.dailyForecasts.push({
          low: Math.round(day.apparentTemperatureLow),
          high: Math.round(day.apparentTemperatureHigh),
          iconUrl: this.getIconUrl(day.icon),
          opacity: opacity,
          day: new Date(day.sunriseTime * 1000)
              .toDateString().replace(/\s.*/, '')
        });
        weeklyMin = Math.min(weeklyMin, day.apparentTemperatureLow);
        weeklyMax = Math.max(weeklyMax, day.apparentTemperatureHigh);
        opacity -= 0.1;
      }

      // calculate offsets for temperature bars in daily forecasts
      weeklyMin = Math.round(weeklyMin);
      weeklyMax = Math.round(weeklyMax);
      var temperatureRange = weeklyMax - weeklyMin;
      // Size of temperature bar container and hi/lo label text.
      // These values must be updated if temp bar styles/layout change. :/
      var cellWidth = 255;
      var tempTextWidth = 30;
      for (var i = 0; i < r.dailyForecasts.length; i++) {
        var barWidth = Math.round(
            (cellWidth - 2 * tempTextWidth)  *
            ((r.dailyForecasts[i].high - r.dailyForecasts[i].low)
             / temperatureRange));
        var rowOffset = Math.round(
            (cellWidth - 2 * tempTextWidth)  *
            ((r.dailyForecasts[i].low - weeklyMin) / temperatureRange));
        r.dailyForecasts[i].barWidth = barWidth;
        r.dailyForecasts[i].barOffset = rowOffset;
      }
    }

    return r;
  },

  // Validates Darksky forecast api data has expected fields in the expected
  // format.
  // See https://darksky.net/dev/docs for parameter format.
  isForecastDataValid: function(data) {
    // Ideally every utilized field of every object would be validated, but
    // this is a hobby project, so I'm just checking that containers exist.
    if (!data) {
      Log.info('null weather data.');
      return false;
    }
    if (!data.currently) {
      Log.info('missing current weather data');
      return false;
    }
    if (!data.daily || !data.daily.data || data.daily.data.length < 7) {
      Log.info('missing daily forecasts');
      return false;
    }
    if (!data.minutely || !data.minutely.summary ||
        !data.hourly || !data.hourly.summary ||
        !data.daily || !data.daily.summary) {
      Log.info('missing forecast summaries');
      return false;
    }
    if (!data.hourly || !data.hourly.data || data.hourly.data.length < 24) {
      Log.info('missing hourly forecasts');
      return false;
    }

    return true;
  },

  // Maps Darksky 'icon' enum values to actual icon filenames. See
  // https://darksky.net/dev/docs for icon enum definition.
  getIconUrl: function(iconName) {
    var iconMap = {
      'clear-day': 'Sun.svg',
      'clear-night': 'Moon-Full.svg',
      'rain': 'Cloud-Rain.svg',
      'snow': 'Cloud-Snow.svg',
      'sleet': 'Cloud-Hail.svg',
      'wind': 'Cloud-Wind-Sun.svg',
      'fog': 'Cloud-Fog-Alt.svg',
      'cloudy': 'Cloud.svg',
      'partly-cloudy-day': 'Cloud-Sun.svg',
      'partly-cloudy-night': 'Cloud-Moon.svg'
    };
    var iconFile = iconMap[iconName] || 'Sun.svg';
    return 'modules/hurst/weather/public/icons/' + iconFile;
  },

  // Injects google charts into weather dom.
  // This must be done outside templates/getViewModel because the charts API is
  // asynchronous.
  drawCharts: function() {
    var windMin = 0;
    var windMax = 30;
    var tempMin = 40;
    var tempMax = 90;

    var data = new google.visualization.DataTable();
    data.addColumn('string', 'hour');
    data.addColumn('number', 'temperature');
    data.addColumn({type:'string', role:'annotation'});
    data.addColumn('number', 'windspeeds');
    data.addColumn({type:'string', role:'annotation'});
    data.addColumn('number', 'precipitation');
    for (var i = 0; i < this.viewModel.hourLabels.length; i++) {
      windMax = Math.max(windMax, this.viewModel.windSpeeds[i]);
      tempMin = Math.min(tempMin, this.viewModel.temperatures[i]);
      tempMax = Math.max(tempMax, this.viewModel.temperatures[i]);
      data.addRows([[
          this.viewModel.hourLabels[i],
          this.viewModel.temperatures[i],
          i % 4 == 1 ? this.viewModel.temperatures[i] + '°' : null,
          this.viewModel.windSpeeds[i],
          (i % 4 == 3 && i < (this.viewModel.hourLabels.length - 3))
           ? this.viewModel.windSpeeds[i] + 'mph' : null,
          this.viewModel.precipitationProbabilities[i]
      ]]);
    }

    var options = {
      height: 150,
      width: 270,
      areaOpacity: 1,
      axisTitlesPosition: 'omit',
      backgroundColor: 'transparent',
      chartArea: {left: 0, top: 0, width: '100%', height: '70%'},
      curveType: 'function',
      enableInteractivity: false,
      fontName: 'Roboto Condensed',
      fontSize: 11,
      hAxis: {
        textPosition: 'out',
        slantedText: true,
        textStyle: {color: '#ffffff'},
        allowContainerBoundaryTextCufoff: true,
        showTextEvery: 1
      },
      legend: {position: 'none'},
      pointsVisible: false,
      seriesType: 'line',
      series: {
        0: {
          color: '#ff6666',
          targetAxisIndex: 0
        },
        1: {
          color: '#bbbbbb',
          targetAxisIndex: 1
        },
        2: {
          color: '#222266',
          lineWidth: 0,
          targetAxisIndex: 2,
          type: 'area'
        }
      },
      vAxes: {
        0: {
          minValue: tempMin,
          maxValue: tempMax,
          targetAxisIndex: 0
        },
        1: {
          minValue: windMin,
          maxValue: windMax,
          targetAxisIndex: 1
        },
        2: {
          minValue: 0,
          maxValue: 1,
          targetAxisIndex: 2
        }
      },
      vAxis: {
        baselineColor: 'transparent',
        gridlines: {color: '#333', count: 2},
        minorGridlines: {color: '#333', count: 3}
      }
    };

    var chart = new google.visualization.ComboChart(
        this.dom.querySelector('#chart'));
    chart.draw(data, options);
  }
});
<commit_msg>Show max precipitation probability for rest of day.<commit_after>Module.register("weather", {
  defaults: {
    // Whether to load canned weather data from a stub response (for development
    // and testing).
    debug: false,
    // Whether to show a textual forecast summary.
    showForecastSummary: true,
    // Whether to show weekly forecast data.
    showWeeklyForecast: true,
    // Whether to show hourly weather chart.
    showChart: true,
    // Darksky API key.  This value MUST be set for this module to work. A key
    // can be obtained from https://darksky.net/dev/
    apiKey: '',
    // Latitude and longitude for which weather data should be displayed, in
    // 'LAT,LNG' format.  For example, 37.795444,-122.393444.  This value MUST
    // be set for this module to work.
    latLng: '',
    // Time in milliseconds between weather updates.  Darksky provides 1000
    // requests per day free.  To stay under that quota, choose a config value
    // of at least ((24*60*60*1000)/1000)==86400.
    updateInterval: 1000 * 60 * 15,  // 15 minutes
    // The maximum age in milliseconds for which a forecast will be displayed.
    // If data cannot be updated before this limit, the UI will be hidden, so
    // as to prevent the display of stale forecast data.  This value MUST be
    // greater than 'updateInterval'.
    dataAgeLimit: 1000 * 60 * 60 * 3,  // 3 hours
    // Duration in milliseconds for animating in new weather data.
    animationDuration: 500  // 0.5 seconds
  },

  start: function() {
    Log.info('starting weather');

    this.mainTemplate = Handlebars.compile(templates.weather.main);
    if (this.config.showChart) {
      google.charts.load('current', {packages: ['corechart']});
    }

    if (this.config.debug) {
      Log.info(DEBUG_DATA);
      this.lastUpdateTimestamp = Date.now();
      this.forecastData = DEBUG_DATA;
    } else {
      this.lastUpdateTimestamp = 0;
      this.forecastData = null;
      this.downloadForecast();
      setInterval(this.downloadForecast.bind(this), this.config.updateInterval);
    }
  },

  getScripts: function() {
    var scripts = [
      '/modules/hurst/shared/vendor/handlebars.js',
      '/weather/templates.js'
    ];
    if (this.config.showChart) {
      scripts.push('https://www.gstatic.com/charts/loader.js');
    }
    if (this.config.debug) {
      scripts.push(this.file('debug_forecast_response.js'));
    }
    return scripts;
  },

  getStyles: function() {
    return [this.file('styles.css')];
  },

  suspend: function() {
    Log.info('suspending weather');
  },

  resume: function() {
    Log.info('resuming weather');
  },

  // Initiates download of Darksky weather forecast data.
  downloadForecast: function() {
    var url = 'https://api.darksky.net/forecast/' +
        this.config.apiKey + '/' + this.config.latLng;
    this.sendSocketNotification('download', url);
  },

  socketNotificationReceived: function(notification, payload) {
    if (notification == 'download') {
      try {
        var data = JSON.parse(payload);
        if (this.isForecastDataValid(data)) {
          this.forecastData = data;
          this.lastUpdateTimestamp = Date.now();
          this.updateDom(this.config.animationDuration);
        } else {
          Log.error('Invalid forecast data.');
        }
      } catch (err) {
        Log.error(err.toString());
      }
    }
  },

  getDom: function() {
    Log.info('updating weather dom');

    if (!this.forecastData ||
        (Date.now() - this.lastUpdateTimestamp) > this.config.dataAgeLimit) {
      return document.createElement('div');
    }

    this.dom = document.createElement('div');
    this.viewModel = this.getViewModel(this.forecastData);
    this.dom.innerHTML = this.mainTemplate(this.viewModel);

    if (this.config.showChart) {
      google.charts.setOnLoadCallback(this.drawCharts.bind(this));
    }

    return this.dom;
  },

  // Converts Darksky forecast api data to view model object passed to handlebar
  // templates.
  // See https://darksky.net/dev/docs for parameter format.
  getViewModel: function(data) {
    var r = {};

    // current conditions
    r.temperature = Math.round(data.currently.apparentTemperature);
    r.summary = data.currently.summary;
    r.windSpeed = Math.round(data.currently.windSpeed);
    r.cloudCover = Math.round(100 * data.currently.cloudCover);
    var today = data.daily.data[0];
    r.low = Math.round(today.apparentTemperatureLow);
    r.high = Math.round(today.apparentTemperatureHigh);
    // Show max precipitation probability for the rest of the day (to 4am),
    // instead of instantaneous probability.  This is more useful for
    // determining if you need an umbrella.
    var precipProbability = 0.0;
    var hourly = data.hourly.data;
    var i = 0;
    do {
      var hour = new Date(hourly[i].time * 1000).getHours();
      precipProbability = Math.max(
          precipProbability, hourly[i].precipProbability);
      i++;
    } while (i < hourly.length && hour != 4)
    r.precipProbability = Math.round(100 * precipProbability);
    // sunrise/sunset times
    r.sunrise = new Date(data.daily.data[0].sunriseTime * 1000)
        .toLocaleTimeString().replace(/:\d\d\s/, '').toLowerCase();
    r.sunset =  new Date(data.daily.data[0].sunsetTime * 1000)
        .toLocaleTimeString().replace(/:\d\d\s/, '').toLowerCase();
    r.shortForecast = [];
    for (var i = 1; i < 3; i++) {
      var day = data.daily.data[i];
      r.shortForecast.push({
        low: Math.round(day.apparentTemperatureLow),
        high: Math.round(day.apparentTemperatureHigh),
        iconUrl: this.getIconUrl(day.icon),
        day: new Date(day.sunriseTime * 1000)
          .toDateString().replace(/\s.*/, '')
      });
    }

    // weather alerts
    if (data.alerts && data.alerts.length) {
      // De-duplicate alerts.
      var alertSet = {}
      r.alerts = [];
      for (var i = 0; i < data.alerts.length; i++) {
        var title = data.alerts[i].title;
        if (!alertSet[title]) {
          alertSet[title] = 1;
          r.alerts.push({title: title.replace("for San Francisco, CA", "")});
        }
      }
    }

    // 24h rain, temperature, and wind forecast chart
    if (this.config.showChart) {
      r.chart = true;
      r.hourLabels = [];
      r.temperatures = [];
      r.windSpeeds = [];
      r.precipitationProbabilities = [];
      for (var i = 0; i < 24; i++) {
        var hour = data.hourly.data[i];
        var h = new Date(hour.time * 1000);
        if (i % 4 == 2) {
          r.hourLabels.push(new Date(hour.time * 1000)
              .toLocaleTimeString().replace(/:\d\d:\d\d\s/, '').toLowerCase());
        } else {
          r.hourLabels.push('');
        }
        r.precipitationProbabilities.push(hour.precipProbability);
        r.temperatures.push(Math.round(hour.apparentTemperature));
        r.windSpeeds.push(Math.round(hour.windSpeed));
      }
    } else {
      r.chart = false;
    }

    // forecast summaries
    if (this.config.showForecastSummary) {
      r.hourSummary = data.minutely.summary;
      r.daySummary = data.hourly.summary;
      r.weekSummary = data.daily.summary.replace('°F', '°');
    }

    // daily forecast
    if (this.config.showWeeklyForecast) {
      r.dailyForecasts = [];
      var weeklyMin = 1000;
      var weeklyMax = -1;
      var opacity = 0.75;
      // skip day 0 (today) since it's covered elsewhere in the UI
      for (var i = 1; i < 7; i++) {
        var day = data.daily.data[i];
        r.dailyForecasts.push({
          low: Math.round(day.apparentTemperatureLow),
          high: Math.round(day.apparentTemperatureHigh),
          iconUrl: this.getIconUrl(day.icon),
          opacity: opacity,
          day: new Date(day.sunriseTime * 1000)
              .toDateString().replace(/\s.*/, '')
        });
        weeklyMin = Math.min(weeklyMin, day.apparentTemperatureLow);
        weeklyMax = Math.max(weeklyMax, day.apparentTemperatureHigh);
        opacity -= 0.1;
      }

      // calculate offsets for temperature bars in daily forecasts
      weeklyMin = Math.round(weeklyMin);
      weeklyMax = Math.round(weeklyMax);
      var temperatureRange = weeklyMax - weeklyMin;
      // Size of temperature bar container and hi/lo label text.
      // These values must be updated if temp bar styles/layout change. :/
      var cellWidth = 255;
      var tempTextWidth = 30;
      for (var i = 0; i < r.dailyForecasts.length; i++) {
        var barWidth = Math.round(
            (cellWidth - 2 * tempTextWidth)  *
            ((r.dailyForecasts[i].high - r.dailyForecasts[i].low)
             / temperatureRange));
        var rowOffset = Math.round(
            (cellWidth - 2 * tempTextWidth)  *
            ((r.dailyForecasts[i].low - weeklyMin) / temperatureRange));
        r.dailyForecasts[i].barWidth = barWidth;
        r.dailyForecasts[i].barOffset = rowOffset;
      }
    }

    return r;
  },

  // Validates Darksky forecast api data has expected fields in the expected
  // format.
  // See https://darksky.net/dev/docs for parameter format.
  isForecastDataValid: function(data) {
    // Ideally every utilized field of every object would be validated, but
    // this is a hobby project, so I'm just checking that containers exist.
    if (!data) {
      Log.info('null weather data.');
      return false;
    }
    if (!data.currently) {
      Log.info('missing current weather data');
      return false;
    }
    if (!data.daily || !data.daily.data || data.daily.data.length < 7) {
      Log.info('missing daily forecasts');
      return false;
    }
    if (!data.minutely || !data.minutely.summary ||
        !data.hourly || !data.hourly.summary ||
        !data.daily || !data.daily.summary) {
      Log.info('missing forecast summaries');
      return false;
    }
    if (!data.hourly || !data.hourly.data || data.hourly.data.length < 24) {
      Log.info('missing hourly forecasts');
      return false;
    }

    return true;
  },

  // Maps Darksky 'icon' enum values to actual icon filenames. See
  // https://darksky.net/dev/docs for icon enum definition.
  getIconUrl: function(iconName) {
    var iconMap = {
      'clear-day': 'Sun.svg',
      'clear-night': 'Moon-Full.svg',
      'rain': 'Cloud-Rain.svg',
      'snow': 'Cloud-Snow.svg',
      'sleet': 'Cloud-Hail.svg',
      'wind': 'Cloud-Wind-Sun.svg',
      'fog': 'Cloud-Fog-Alt.svg',
      'cloudy': 'Cloud.svg',
      'partly-cloudy-day': 'Cloud-Sun.svg',
      'partly-cloudy-night': 'Cloud-Moon.svg'
    };
    var iconFile = iconMap[iconName] || 'Sun.svg';
    return 'modules/hurst/weather/public/icons/' + iconFile;
  },

  // Injects google charts into weather dom.
  // This must be done outside templates/getViewModel because the charts API is
  // asynchronous.
  drawCharts: function() {
    var windMin = 0;
    var windMax = 30;
    var tempMin = 40;
    var tempMax = 90;

    var data = new google.visualization.DataTable();
    data.addColumn('string', 'hour');
    data.addColumn('number', 'temperature');
    data.addColumn({type:'string', role:'annotation'});
    data.addColumn('number', 'windspeeds');
    data.addColumn({type:'string', role:'annotation'});
    data.addColumn('number', 'precipitation');
    for (var i = 0; i < this.viewModel.hourLabels.length; i++) {
      windMax = Math.max(windMax, this.viewModel.windSpeeds[i]);
      tempMin = Math.min(tempMin, this.viewModel.temperatures[i]);
      tempMax = Math.max(tempMax, this.viewModel.temperatures[i]);
      data.addRows([[
          this.viewModel.hourLabels[i],
          this.viewModel.temperatures[i],
          i % 4 == 1 ? this.viewModel.temperatures[i] + '°' : null,
          this.viewModel.windSpeeds[i],
          (i % 4 == 3 && i < (this.viewModel.hourLabels.length - 3))
           ? this.viewModel.windSpeeds[i] + 'mph' : null,
          this.viewModel.precipitationProbabilities[i]
      ]]);
    }

    var options = {
      height: 150,
      width: 270,
      areaOpacity: 1,
      axisTitlesPosition: 'omit',
      backgroundColor: 'transparent',
      chartArea: {left: 0, top: 0, width: '100%', height: '70%'},
      curveType: 'function',
      enableInteractivity: false,
      fontName: 'Roboto Condensed',
      fontSize: 11,
      hAxis: {
        textPosition: 'out',
        slantedText: true,
        textStyle: {color: '#ffffff'},
        allowContainerBoundaryTextCufoff: true,
        showTextEvery: 1
      },
      legend: {position: 'none'},
      pointsVisible: false,
      seriesType: 'line',
      series: {
        0: {
          color: '#ff6666',
          targetAxisIndex: 0
        },
        1: {
          color: '#bbbbbb',
          targetAxisIndex: 1
        },
        2: {
          color: '#222266',
          lineWidth: 0,
          targetAxisIndex: 2,
          type: 'area'
        }
      },
      vAxes: {
        0: {
          minValue: tempMin,
          maxValue: tempMax,
          targetAxisIndex: 0
        },
        1: {
          minValue: windMin,
          maxValue: windMax,
          targetAxisIndex: 1
        },
        2: {
          minValue: 0,
          maxValue: 1,
          targetAxisIndex: 2
        }
      },
      vAxis: {
        baselineColor: 'transparent',
        gridlines: {color: '#333', count: 2},
        minorGridlines: {color: '#333', count: 3}
      }
    };

    var chart = new google.visualization.ComboChart(
        this.dom.querySelector('#chart'));
    chart.draw(data, options);
  }
});
<|endoftext|>


<commit_before>

const chalk = require('chalk');

// group together entries from different stores
var simplifyString = function simplifyString(string) {
    return string.toLowerCase().split(/\W/).filter(function(e) { return e; }).join(' ');
};

// this will eventually take an array of searchResult objects
// it will call the simplifyString() function on array[i].title
// it will then loop through the matches and
// console.log(matches[i].title + ": " + chalk.gray(matches[i].normalPrice) + "  " + chalk.green(matches[i].price) + " at " + matches[i].store)
function sortByTitle(options, objArray) {

    while (objArray.length > 0) {
        var matches = [];

        var string = simplifyString(objArray[0].title);

        for (var i = 0; i < objArray.length; i++) {
            if (string === simplifyString(objArray[i].title)) {
                matches.push(objArray[i]);
                objArray.splice(i, 1);
                i--;
            }
        }

        matches = matches.sort(byPrice);

        for (var i = 0; i < matches.length; i++) {
            if (options.best && i > 0) { break; }

            var match = matches[i];
            if (match.normalPrice) {
                console.log(chalk.black.bold(match.title) + ": " + chalk.gray(match.normalPrice) + "  " + chalk.green(match.price) + " from " + chalk.blue(match.store));
            } else {
                if (options.sale) { continue; }
                console.log(chalk.black.bold(match.title) + ": " + match.price + " from " + chalk.blue(match.store));
            }
        }
    }
};

var byPrice = function byPrice(a, b) {
    a = parseFloat(a.price.replace("$", ""));
    b = parseFloat(b.price.replace("$", ""));

    if (a > b) {
        return 1;
    }
    if (a < b) {
        return -1;
    }
    return 0;
};

module.exports = sortByTitle;
<commit_msg>Add copyright info to best-prices.js<commit_after>
/*
 * best-price
 * Copyright (c) 2016 Jeff Minnear
 * Licensed under the MIT license.
 */

const chalk = require('chalk');

// group together entries from different stores
var simplifyString = function simplifyString(string) {
    return string.toLowerCase().split(/\W/).filter(function(e) { return e; }).join(' ');
};

// this will eventually take an array of searchResult objects
// it will call the simplifyString() function on array[i].title
// it will then loop through the matches and
// console.log(matches[i].title + ": " + chalk.gray(matches[i].normalPrice) + "  " + chalk.green(matches[i].price) + " at " + matches[i].store)
function sortByTitle(options, objArray) {

    while (objArray.length > 0) {
        var matches = [];

        var string = simplifyString(objArray[0].title);

        for (var i = 0; i < objArray.length; i++) {
            if (string === simplifyString(objArray[i].title)) {
                matches.push(objArray[i]);
                objArray.splice(i, 1);
                i--;
            }
        }

        matches = matches.sort(byPrice);

        for (var i = 0; i < matches.length; i++) {
            if (options.best && i > 0) { break; }

            var match = matches[i];
            if (match.normalPrice) {
                console.log(chalk.black.bold(match.title) + ": " + chalk.gray(match.normalPrice) + "  " + chalk.green(match.price) + " from " + chalk.blue(match.store));
            } else {
                if (options.sale) { continue; }
                console.log(chalk.black.bold(match.title) + ": " + match.price + " from " + chalk.blue(match.store));
            }
        }
    }
};

var byPrice = function byPrice(a, b) {
    a = parseFloat(a.price.replace("$", ""));
    b = parseFloat(b.price.replace("$", ""));

    if (a > b) {
        return 1;
    }
    if (a < b) {
        return -1;
    }
    return 0;
};

module.exports = sortByTitle;
<|endoftext|>


<commit_before>// <nowiki>
/* globals TranslateVariants:true */
(function() {

	if (mw.config.get('wgPageName').match(/MediaWiki:.+\/zh/)) {
		let link = mw.util.addPortletLink(
			'p-cactions',
			'#',
			'轉換變體'
		)
		$(link).on('click', function() {
			this.remove();
			main();
		});
	} else {
		return;
	}

	if (typeof (TranslateVariants) == 'undefined') {
		TranslateVariants = {};
	}
	if (typeof (TranslateVariants.summary) != 'string') {
		TranslateVariants.summary = '自動轉換變體自[[$1]] via [[User:Xiplus/js/TranslateVariants.js|TranslateVariants]]'
	}

	function main() {
		const langs = ['zh', 'zh-hans', 'zh-cn', 'zh-my', 'zh-sg', 'zh-hant', 'zh-hk', 'zh-mo', 'zh-tw'];
		const langname = {
			'zh': '原始',
			'zh-hans': '简体',
			'zh-cn': '大陆简体',
			'zh-my': '大马简体',
			'zh-sg': '新加坡简体',
			'zh-hant': '繁體',
			'zh-hk': '香港繁體',
			'zh-mo': '澳門繁體',
			'zh-tw': '臺灣正體'
		}
		let result = {};

		var api = new mw.Api();
		api.get({
			action: 'query',
			prop: 'revisions',
			rvprop: ['content', 'timestamp'],
			titles: [mw.config.get('wgPageName')],
			formatversion: '2',
			curtimestamp: true
		}).then(function(data) {
			var page, revision;
			if (!data.query || !data.query.pages) {
				return $.Deferred().reject('unknown');
			}
			page = data.query.pages[0];
			if (!page || page.invalid) {
				return $.Deferred().reject('invalidtitle');
			}
			if (page.missing) {
				return $.Deferred().reject('nocreate-missing');
			}
			revision = page.revisions[0];
			return {
				content: revision.content
			};
		}).then(function(data) {
			let text = data.content;
			result['zh'] = text;

			text = text.replace(/[[\]{}<>|:*'_#&\s]/gim, function(s) {
				return "&#" + s.charCodeAt(0) + ";";
			});
			text = text.replace(/([[)((?:(?!|)(?!]).)+?)(|(?:(?!]).)+?]])/, '$1-{$2}-$3');
			text = text.replace(/-{(.+?)}-/g, function(s) {
				return s
					.replace('-{', '-{')
					.replace('}-', '}-')
					.replace(/:/g, ':')
			});
			let p = [];
			langs.forEach(lang => {
				p.push(
					api.parse(
						'{{NoteTA|G1=IT|G2=MediaWiki}}
' + text + '
',
						{
							'uselang': lang,
							'prop': 'text'
						}
					).then(function(data) {
						result[lang] = $('
').html(data).find('#TVcontent').text();
					})
				);
			})
			return Promise.all(p);
		}).then(function() {
			let table = $('
').prependTo('#bodyContent');
			$('
提醒：TranslateVariants小工具仍在試驗階段，編輯完成後亦請複查真正做出的編輯是否正確！
').appendTo(table);
			const basename = mw.config.get('wgPageName').replace(/\/zh$/, '');
			let editlangs = [];
			langs.forEach(lang => {
				if (lang == 'zh') {
					editlangs.push(['', lang]);
				} else {
					editlangs.push(['/' + lang, lang]);
				}
			});
			let diffTables = {}
			editlangs.forEach(lang => {
				diffTables[lang[1]] = $('
').appendTo(table);
				$('
').appendTo(table);
			});
			editlangs.forEach(lang => {
				let newtext = result[lang[1]];
				let targetTitle = basename + lang[0];
				api.post({
					action: 'query',
					prop: 'revisions',
					titles: [targetTitle],
					rvdifftotext: newtext,
					formatversion: '2',
				}).then(function(data) {
					let diffTable = diffTables[lang[1]];
					let tool = $('
' + lang[1] + '（' + langname[lang[1]] + '）（編）
').appendTo(diffTable);
					let page = data.query.pages[0];
					if (page.missing) {
						let submit = $('<button style="float: right;">發佈頁面</button>').appendTo(tool);
						submit.on('click', function() {
							this.remove();
							api.create(
								targetTitle,
								{ summary: TranslateVariants.summary.replace(/\$1/g, mw.config.get('wgPageName')) },
								newtext
							).then(function() {
								mw.notify('已編輯 ' + targetTitle);
							}, function(e) {
								mw.notify('編輯 ' + targetTitle + ' 發生錯誤：' + e);
							});
						});
						$('
').html(newtext.replace(/[<>&]/gim, function(s) {
							return "&#" + s.charCodeAt(0) + ";";
						})).appendTo(diffTable);
						return;
					}
					let diff = page.revisions[0].diff.body;
					if (diff == '') {
						$('無變更').appendTo(tool);
					} else {
						let submit = $('<button style="float: right;">發佈變更</button>').appendTo(tool);
						submit.on('click', function() {
							this.remove();
							api.edit(
								targetTitle,
								function() {
									return {
										text: newtext,
										summary: TranslateVariants.summary.replace(/\$1/g, mw.config.get('wgPageName')),
										nocreate: false
									};
								}
							).then(function() {
								mw.notify('已編輯 ' + targetTitle);
							}, function(e) {
								mw.notify('編輯 ' + targetTitle + ' 發生錯誤：' + e);
							});
						});
						$('
').html(diff).prepend('<colgroup><col class="diff-marker"><col class="diff-content"><col class="diff-marker"><col class="diff-content"></colgroup>').appendTo(diffTable);
					}
				});
			});
		});
	}

})();
// </nowiki>
<commit_msg>TranslateVariants: Enable on /zh and root page<commit_after>// <nowiki>
/* globals TranslateVariants:true */
(function() {

	if (mw.config.get('wgPageName').match(/^MediaWiki:[^/]+(\/zh)?$/)) {
		let link = mw.util.addPortletLink(
			'p-cactions',
			'#',
			'轉換變體'
		)
		$(link).on('click', function() {
			this.remove();
			main();
		});
	} else {
		return;
	}

	if (typeof (TranslateVariants) == 'undefined') {
		TranslateVariants = {};
	}
	if (typeof (TranslateVariants.summary) != 'string') {
		TranslateVariants.summary = '自動轉換變體自[[$1]] via [[User:Xiplus/js/TranslateVariants.js|TranslateVariants]]'
	}

	function main() {
		const langs = ['zh', 'zh-hans', 'zh-cn', 'zh-my', 'zh-sg', 'zh-hant', 'zh-hk', 'zh-mo', 'zh-tw'];
		const langname = {
			'zh': '原始',
			'zh-hans': '简体',
			'zh-cn': '大陆简体',
			'zh-my': '大马简体',
			'zh-sg': '新加坡简体',
			'zh-hant': '繁體',
			'zh-hk': '香港繁體',
			'zh-mo': '澳門繁體',
			'zh-tw': '臺灣正體'
		}
		let result = {};

		var api = new mw.Api();
		api.get({
			action: 'query',
			prop: 'revisions',
			rvprop: ['content', 'timestamp'],
			titles: [mw.config.get('wgPageName')],
			formatversion: '2',
			curtimestamp: true
		}).then(function(data) {
			var page, revision;
			if (!data.query || !data.query.pages) {
				return $.Deferred().reject('unknown');
			}
			page = data.query.pages[0];
			if (!page || page.invalid) {
				return $.Deferred().reject('invalidtitle');
			}
			if (page.missing) {
				return $.Deferred().reject('nocreate-missing');
			}
			revision = page.revisions[0];
			return {
				content: revision.content
			};
		}).then(function(data) {
			let text = data.content;
			result['zh'] = text;

			text = text.replace(/[[\]{}<>|:*'_#&\s]/gim, function(s) {
				return "&#" + s.charCodeAt(0) + ";";
			});
			text = text.replace(/([[)((?:(?!|)(?!]).)+?)(|(?:(?!]).)+?]])/, '$1-{$2}-$3');
			text = text.replace(/-{(.+?)}-/g, function(s) {
				return s
					.replace('-{', '-{')
					.replace('}-', '}-')
					.replace(/:/g, ':')
			});
			let p = [];
			langs.forEach(lang => {
				p.push(
					api.parse(
						'{{NoteTA|G1=IT|G2=MediaWiki}}
' + text + '
',
						{
							'uselang': lang,
							'prop': 'text'
						}
					).then(function(data) {
						result[lang] = $('
').html(data).find('#TVcontent').text();
					}, function(err) {
						mw.notify('請求時發生錯誤：' + err);
					})
				);
			})
			return Promise.all(p);
		}).then(function() {
			let table = $('
').prependTo('#bodyContent');
			$('
提醒：TranslateVariants小工具仍在試驗階段，編輯完成後亦請複查真正做出的編輯是否正確！
').appendTo(table);
			const basename = mw.config.get('wgPageName').replace(/\/zh$/, '');
			let editlangs = [];
			langs.forEach(lang => {
				if (lang == 'zh') {
					editlangs.push(['', lang]);
				} else {
					editlangs.push(['/' + lang, lang]);
				}
			});
			let diffTables = {}
			editlangs.forEach(lang => {
				diffTables[lang[1]] = $('
').appendTo(table);
				$('
').appendTo(table);
			});
			editlangs.forEach(lang => {
				let newtext = result[lang[1]];
				let targetTitle = basename + lang[0];
				api.post({
					action: 'query',
					prop: 'revisions',
					titles: [targetTitle],
					rvdifftotext: newtext,
					formatversion: '2',
				}).then(function(data) {
					let diffTable = diffTables[lang[1]];
					let tool = $('
' + lang[1] + '（' + langname[lang[1]] + '）（編）
').appendTo(diffTable);
					let page = data.query.pages[0];
					if (page.missing) {
						let submit = $('<button style="float: right;">發佈頁面</button>').appendTo(tool);
						submit.on('click', function() {
							this.remove();
							api.create(
								targetTitle,
								{ summary: TranslateVariants.summary.replace(/\$1/g, mw.config.get('wgPageName')) },
								newtext
							).then(function() {
								mw.notify('已編輯 ' + targetTitle);
							}, function(e) {
								mw.notify('編輯 ' + targetTitle + ' 發生錯誤：' + e);
							});
						});
						$('
').html(newtext.replace(/[<>&]/gim, function(s) {
							return "&#" + s.charCodeAt(0) + ";";
						})).appendTo(diffTable);
						return;
					}
					let diff = page.revisions[0].diff.body;
					if (diff == '') {
						$('無變更').appendTo(tool);
					} else {
						let submit = $('<button style="float: right;">發佈變更</button>').appendTo(tool);
						submit.on('click', function() {
							this.remove();
							api.edit(
								targetTitle,
								function() {
									return {
										text: newtext,
										summary: TranslateVariants.summary.replace(/\$1/g, mw.config.get('wgPageName')),
										nocreate: false
									};
								}
							).then(function() {
								mw.notify('已編輯 ' + targetTitle);
							}, function(e) {
								mw.notify('編輯 ' + targetTitle + ' 發生錯誤：' + e);
							});
						});
						$('
').html(diff).prepend('<colgroup><col class="diff-marker"><col class="diff-content"><col class="diff-marker"><col class="diff-content"></colgroup>').appendTo(diffTable);
					}
				});
			});
		});
	}

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


<commit_before>'use strict';

var path = require('path');
var gulp = require('gulp');
var runSequence = require('run-sequence');
var concat = require('gulp-concat');
var conf = require('./conf');

var $ = require('gulp-load-plugins')({
	pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
});

gulp.task('partials', function() {
	return gulp.src([
    path.join(conf.paths.src, conf.paths.template),
    path.join(conf.paths.tmp, '/serve/app/**/*.html')
	])
    .pipe($.htmlmin({
    	removeEmptyAttributes: true,
    	removeAttributeQuotes: true,
    	collapseBooleanAttributes: true,
    	collapseWhitespace: true
    }))
    .pipe($.angularTemplatecache('templateCacheHtml.js', {
    	module: conf.names.module,
    	root: conf.paths.templateRoot,
    }))
    .pipe(gulp.dest(conf.paths.tmp + '/partials/'));
});

gulp.task('html', ['inject', 'partials'], function() {
	var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false });
	var partialsInjectOptions = {
		starttag: '<!-- inject:partials -->',
		ignorePath: path.join(conf.paths.tmp, '/partials'),
		addRootSlash: false
	};

	var htmlFilter = $.filter('*.html', { restore: true });
	var jsFilter = $.filter('**/*.js', { restore: true });
	var cssFilter = $.filter('**/*.css', { restore: true });

	return gulp.src(path.join(conf.paths.tmp, '/serve/*.html'))
    .pipe($.inject(partialsInjectFile, partialsInjectOptions))
    .pipe($.useref())
    .pipe(jsFilter)
    .pipe($.sourcemaps.init())
    .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
    .pipe($.sourcemaps.write('maps'))
    .pipe(jsFilter.restore)
    .pipe(cssFilter)
    // .pipe($.sourcemaps.init())
    .pipe($.replace('../../bower_components/bootstrap-sass/assets/fonts/bootstrap/', '../fonts/'))
    .pipe($.cssnano())
    // .pipe($.sourcemaps.write('maps'))
    .pipe(cssFilter.restore)
    .pipe(htmlFilter)
    .pipe($.htmlmin({
    	removeEmptyAttributes: true,
    	removeAttributeQuotes: true,
    	collapseBooleanAttributes: true,
    	collapseWhitespace: true
    }))
    .pipe(htmlFilter.restore)
    .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
    .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
});

// Only applies for fonts from bower dependencies
// Custom fonts are handled by the "other" task
gulp.task('fonts', function() {
	return gulp.src($.mainBowerFiles({
		paths: {
			bowerJson: 'bowerDocs.json'
		}
	}))
    .pipe($.filter('**/*.{eot,otf,svg,ttf,woff,woff2}'))
    .pipe($.flatten())
    .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/')));
});

gulp.task('other', function() {
	var fileFilter = $.filter(function(file) {
		return file.stat.isFile();
	});

	return gulp.src([
    path.join(conf.paths.src, '/**/*'),
    path.join('!' + conf.paths.src, '/**/*.{html,css,js,scss}')
	])
    .pipe(fileFilter)
    .pipe(gulp.dest(path.join(conf.paths.dist, '/')));
});

gulp.task('clean', function() {
	return $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')]);
});

gulp.task('build', ['html', 'fonts', 'other']);

gulp.task('conf:component', function(cb) {
	conf.paths.src = 'src/app/component';
	conf.paths.dist = 'dist';
	conf.paths.template = '**/*.html';
	conf.paths.index = 'angularjs-dropdown-multiselect.module.js';
	conf.paths.templateRoot = 'app/component/';
	conf.paths.cssRoot = '';
	conf.paths.bowerJson = '../bower.json';
	conf.names.module = 'angularjs-dropdown-multiselect';
	conf.wiredep.bowerJson = require(conf.paths.bowerJson);
	cb();
});

gulp.task('clean:component', ['conf:component'], function() {
	return $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')]);
});

gulp.task('compile:component', ['clean:component'], function(cb) {
	runSequence(['scripts', 'styles', 'partials'], cb);
});

gulp.task('build:component', ['compile:component'], function() {
	var jsFilter = $.filter('**/*.js', { restore: true });
	var cssFilter = $.filter('**/*.css', { restore: true });

	return gulp.src([
		path.join(conf.paths.tmp, 'serve/app/index.css'),
		path.join(conf.paths.tmp, 'serve/app/index.module.js'),
		path.join(conf.paths.tmp, 'partials/templateCacheHtml.js')
	])
		.pipe(jsFilter)
		.pipe(concat({ path: 'angularjs-dropdown-multiselect.min.js'}))
    .pipe($.sourcemaps.init())
    .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
    .pipe($.sourcemaps.write('maps'))
		.pipe(jsFilter.restore)
		.pipe(cssFilter)
		.pipe($.cssnano())
		.pipe(cssFilter.restore)
		.pipe(gulp.dest(path.join(conf.paths.dist, '/')))
    .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
});
<commit_msg>Add a compiled but not minified version of the library to the dist folder<commit_after>'use strict';

var path = require('path');
var gulp = require('gulp');
var runSequence = require('run-sequence');
var concat = require('gulp-concat');
var conf = require('./conf');

var $ = require('gulp-load-plugins')({
	pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
});

gulp.task('partials', function() {
	return gulp.src([
    path.join(conf.paths.src, conf.paths.template),
    path.join(conf.paths.tmp, '/serve/app/**/*.html')
	])
    .pipe($.htmlmin({
    	removeEmptyAttributes: true,
    	removeAttributeQuotes: true,
    	collapseBooleanAttributes: true,
    	collapseWhitespace: true
    }))
    .pipe($.angularTemplatecache('templateCacheHtml.js', {
    	module: conf.names.module,
    	root: conf.paths.templateRoot,
    }))
    .pipe(gulp.dest(conf.paths.tmp + '/partials/'));
});

gulp.task('html', ['inject', 'partials'], function() {
	var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false });
	var partialsInjectOptions = {
		starttag: '<!-- inject:partials -->',
		ignorePath: path.join(conf.paths.tmp, '/partials'),
		addRootSlash: false
	};

	var htmlFilter = $.filter('*.html', { restore: true });
	var jsFilter = $.filter('**/*.js', { restore: true });
	var cssFilter = $.filter('**/*.css', { restore: true });

	return gulp.src(path.join(conf.paths.tmp, '/serve/*.html'))
    .pipe($.inject(partialsInjectFile, partialsInjectOptions))
    .pipe($.useref())
    .pipe(jsFilter)
    .pipe($.sourcemaps.init())
    .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
    .pipe($.sourcemaps.write('maps'))
    .pipe(jsFilter.restore)
    .pipe(cssFilter)
    // .pipe($.sourcemaps.init())
    .pipe($.replace('../../bower_components/bootstrap-sass/assets/fonts/bootstrap/', '../fonts/'))
    .pipe($.cssnano())
    // .pipe($.sourcemaps.write('maps'))
    .pipe(cssFilter.restore)
    .pipe(htmlFilter)
    .pipe($.htmlmin({
    	removeEmptyAttributes: true,
    	removeAttributeQuotes: true,
    	collapseBooleanAttributes: true,
    	collapseWhitespace: true
    }))
    .pipe(htmlFilter.restore)
    .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
    .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
});

// Only applies for fonts from bower dependencies
// Custom fonts are handled by the "other" task
gulp.task('fonts', function() {
	return gulp.src($.mainBowerFiles({
		paths: {
			bowerJson: 'bowerDocs.json'
		}
	}))
    .pipe($.filter('**/*.{eot,otf,svg,ttf,woff,woff2}'))
    .pipe($.flatten())
    .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/')));
});

gulp.task('other', function() {
	var fileFilter = $.filter(function(file) {
		return file.stat.isFile();
	});

	return gulp.src([
    path.join(conf.paths.src, '/**/*'),
    path.join('!' + conf.paths.src, '/**/*.{html,css,js,scss}')
	])
    .pipe(fileFilter)
    .pipe(gulp.dest(path.join(conf.paths.dist, '/')));
});

gulp.task('clean', function() {
	return $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')]);
});

gulp.task('build', ['html', 'fonts', 'other']);

gulp.task('conf:component', function(cb) {
	conf.paths.src = 'src/app/component';
	conf.paths.dist = 'dist';
	conf.paths.template = '**/*.html';
	conf.paths.index = 'angularjs-dropdown-multiselect.module.js';
	conf.paths.templateRoot = 'app/component/';
	conf.paths.cssRoot = '';
	conf.paths.bowerJson = '../bower.json';
	conf.names.module = 'angularjs-dropdown-multiselect';
	conf.wiredep.bowerJson = require(conf.paths.bowerJson);
	cb();
});

gulp.task('clean:component', ['conf:component'], function() {
	return $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')]);
});

gulp.task('compile:component', ['clean:component'], function(cb) {
	runSequence(['scripts', 'styles', 'partials'], cb);
});

gulp.task('build.component.minified', ['compile:component'], function () {
	var jsFilter = $.filter('**/*.js', { restore: true });
	var cssFilter = $.filter('**/*.css', { restore: true });

	return gulp.src([
		path.join(conf.paths.tmp, 'serve/app/index.css'),
		path.join(conf.paths.tmp, 'serve/app/index.module.js'),
		path.join(conf.paths.tmp, 'partials/templateCacheHtml.js')
	])
		.pipe(jsFilter)
		.pipe(concat({ path: 'angularjs-dropdown-multiselect.min.js' }))
		.pipe($.sourcemaps.init())
		.pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
		.pipe($.sourcemaps.write('maps'))
		.pipe(jsFilter.restore)
		.pipe(cssFilter)
		.pipe($.cssnano())
		.pipe(cssFilter.restore)
		.pipe(gulp.dest(path.join(conf.paths.dist, '/')))
		.pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
});

gulp.task('build.component', ['compile:component'], function () {
	var jsFilter = $.filter('**/*.js', { restore: true });
	var cssFilter = $.filter('**/*.css', { restore: true });

	return gulp.src([
		path.join(conf.paths.tmp, 'serve/app/index.css'),
		path.join(conf.paths.tmp, 'serve/app/index.module.js'),
		path.join(conf.paths.tmp, 'partials/templateCacheHtml.js')
	])
		.pipe(jsFilter)
		.pipe(concat({ path: 'angularjs-dropdown-multiselect.js' }))
		.pipe(jsFilter.restore)
		.pipe(cssFilter)
		.pipe($.cssnano())
		.pipe(cssFilter.restore)
		.pipe(gulp.dest(path.join(conf.paths.dist, '/src')))
		.pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
});

gulp.task('build:component', ['build.component.minified', 'build.component']);
<|endoftext|>


<commit_before>/*
 * Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

$(".owl-carousel").owlCarousel({
  singleItem  : true,
  autoPlay    : 5000,
  stopOnHover : true,
  rewindSpeed : 5000
});
<commit_msg>Reduce rewind speed to zero to remove animation<commit_after>/*
 * Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

$(".owl-carousel").owlCarousel({
  singleItem  : true,
  autoPlay    : 5000,
  stopOnHover : true,
  rewindSpeed : 0
});
<|endoftext|>


<commit_before>var mockery = require('mockery');
var expect = require('chai').expect;

var Interpolator = require('i18next/lib/Interpolator');

var test3Save = 0;
var test4Save = 0;

var fsMock = {
  readFile: function (path, encoding, cb) {
    if (path.indexOf('test.json') > -1) return cb(null, '{"key": "passing"}');
    if (path.indexOf('test3.missing.json') > -1 && test3Save > 0) return cb(null, JSON.stringify({ key1: '1', key2: '2' }, null, 2));

    cb(null, '{}');
  },

  writeFile: function(path, data, cb) {
    if (path.indexOf('test.missing.json') > -1) {
      expect(data).to.be.eql(JSON.stringify({some: { key: 'myDefault' }}, null, 2));
    }
    else if (path.indexOf('test2.missing.json') > -1) {
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2', key3: '3', key4: '4' }, null, 2));
    }
    else if (path.indexOf('test3.missing.json') > -1 && test3Save === 0) {
      test3Save = test3Save + 1;
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2' }, null, 2));
    }
    else if (path.indexOf('test3.missing.json') > -1 && test3Save > 0) {
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2', key3: '3', key4: '4' }, null, 2));
    }
    else if (path.indexOf('test4.missing.json') > -1) {
      test4Save = test4Save + 1;
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2', key3: '3', key4: '4' }, null, 2));
    }

    cb(null);
  }
};

describe('backend', function() {
  var backend;

  before(function() {
    mockery.enable();
    mockery.registerMock('fs', fsMock);

    var Backend = require('../lib');
    backend = new Backend({
      interpolator: new Interpolator()
    }, {
      loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json'
    });
  });

  after(function() {
    mockery.disable();
  });

  it('read', function(done) {
    backend.read('en', 'test', function(err, data) {
      expect(err).to.be.not.ok;
      expect(data).to.eql({key: 'passing'});
      done();
    });
  });

  it('create simple', function(done) {
    backend.create('en', 'test', 'some.key', 'myDefault', function() {
      done();
    });
  });

  it('create multiple', function(done) {
    backend.create('en', 'test2', 'key1', '1')
    backend.create('en', 'test2', 'key2', '2')
    backend.create('en', 'test2', 'key3', '3')
    backend.create('en', 'test2', 'key4', '4', function() {
      done();
    });
  });

  it('create multiple - with pause', function(done) {
    backend.create('en', 'test3', 'key1', '1')
    backend.create('en', 'test3', 'key2', '2', function() {
      setTimeout(function () {
        backend.create('en', 'test3', 'key3', '3')
        backend.create('en', 'test3', 'key4', '4', function() {
          done();
        });
      }, 200);
    });
  });

  it('create multiple with multiple languages to write to (saveMissingTo=all)', function(done) {
    backend.create(['en', 'de'], 'test4', 'key1', '1')
    backend.create(['en', 'de'], 'test4', 'key2', '2')
    backend.create(['en', 'de'], 'test4', 'key3', '3')
    backend.create(['en', 'de'], 'test4', 'key4', '4', function() {
      expect(test4Save).to.equal(2);
      done();
    });
  });

});
<commit_msg>update reference<commit_after>var mockery = require('mockery');
var expect = require('chai').expect;

var Interpolator = require('i18next/dist/commonjs/Interpolator').default;

var test3Save = 0;
var test4Save = 0;

var fsMock = {
  readFile: function (path, encoding, cb) {
    if (path.indexOf('test.json') > -1) return cb(null, '{"key": "passing"}');
    if (path.indexOf('test3.missing.json') > -1 && test3Save > 0) return cb(null, JSON.stringify({ key1: '1', key2: '2' }, null, 2));

    cb(null, '{}');
  },

  writeFile: function(path, data, cb) {
    if (path.indexOf('test.missing.json') > -1) {
      expect(data).to.be.eql(JSON.stringify({some: { key: 'myDefault' }}, null, 2));
    }
    else if (path.indexOf('test2.missing.json') > -1) {
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2', key3: '3', key4: '4' }, null, 2));
    }
    else if (path.indexOf('test3.missing.json') > -1 && test3Save === 0) {
      test3Save = test3Save + 1;
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2' }, null, 2));
    }
    else if (path.indexOf('test3.missing.json') > -1 && test3Save > 0) {
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2', key3: '3', key4: '4' }, null, 2));
    }
    else if (path.indexOf('test4.missing.json') > -1) {
      test4Save = test4Save + 1;
      expect(data).to.be.eql(JSON.stringify({ key1: '1', key2: '2', key3: '3', key4: '4' }, null, 2));
    }

    cb(null);
  }
};

describe('backend', function() {
  var backend;

  before(function() {
    mockery.enable();
    mockery.registerMock('fs', fsMock);

    var Backend = require('../lib');
    backend = new Backend({
      interpolator: new Interpolator()
    }, {
      loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json'
    });
  });

  after(function() {
    mockery.disable();
  });

  it('read', function(done) {
    backend.read('en', 'test', function(err, data) {
      expect(err).to.be.not.ok;
      expect(data).to.eql({key: 'passing'});
      done();
    });
  });

  it('create simple', function(done) {
    backend.create('en', 'test', 'some.key', 'myDefault', function() {
      done();
    });
  });

  it('create multiple', function(done) {
    backend.create('en', 'test2', 'key1', '1')
    backend.create('en', 'test2', 'key2', '2')
    backend.create('en', 'test2', 'key3', '3')
    backend.create('en', 'test2', 'key4', '4', function() {
      done();
    });
  });

  it('create multiple - with pause', function(done) {
    backend.create('en', 'test3', 'key1', '1')
    backend.create('en', 'test3', 'key2', '2', function() {
      setTimeout(function () {
        backend.create('en', 'test3', 'key3', '3')
        backend.create('en', 'test3', 'key4', '4', function() {
          done();
        });
      }, 200);
    });
  });

  it('create multiple with multiple languages to write to (saveMissingTo=all)', function(done) {
    backend.create(['en', 'de'], 'test4', 'key1', '1')
    backend.create(['en', 'de'], 'test4', 'key2', '2')
    backend.create(['en', 'de'], 'test4', 'key3', '3')
    backend.create(['en', 'de'], 'test4', 'key4', '4', function() {
      expect(test4Save).to.equal(2);
      done();
    });
  });

});
<|endoftext|>


<commit_before>import is_nil from 'lodash.isnil';

export default ({emitter, onRender}) => {
	if(is_nil(emitter)){
		throw new TypeError('Scene objects must contain an emitter');
	}

	const state = Object.assign({
		zIndex: 0,
		renderEnabled: true,
		scene: null
	}, {emitter, onRender});

	return {
		set zIndex(value){
			state.zIndex = value;
			state.emitter.emit('zindex-changed', state.zIndex);
		},
		get zIndex(){
			return state.zIndex;
		},
		toggleRender(enabled){
			if(is_nil(enabled)){
				state.renderEnabled = !state.renderEnabled;
			} else {
				state.renderEnabled = enabled;
			}
		},
		render(screen){
			if(state.renderEnabled){
				screen.save();
				onRender(screen);
				screen.restore();
			}
		}
	};
}
<commit_msg>permit to set the zIndex when building<commit_after>import is_nil from 'lodash.isnil';

export default ({emitter, onRender, zIndex = 0}) => {
	if(is_nil(emitter)){
		throw new TypeError('Scene objects must contain an emitter');
	}

	const state = Object.assign({
		renderEnabled: true
	}, {emitter, onRender, zIndex});

	return {
		set zIndex(value){
			state.zIndex = value;
			state.emitter.emit('zindex-changed', state.zIndex);
		},
		get zIndex(){
			return state.zIndex;
		},
		toggleRender(enabled){
			if(is_nil(enabled)){
				state.renderEnabled = !state.renderEnabled;
			} else {
				state.renderEnabled = enabled;
			}
		},
		render(screen){
			if(state.renderEnabled){
				screen.save();
				onRender(screen);
				screen.restore();
			}
		}
	};
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { fetchNeeds } from '../utils/fetchComponentData';

import * as AppActions from '../actions/appActions.js';

const needs = [
  AppActions.loadUniverseList,
  AppActions.setUniverseId,
  AppActions.setCategoryId
];

class MarketList extends React.Component {

  constructor(params) {
    super(params);
    this.needs = needs;
    this.renderMarkets = this.renderMarkets.bind(this);
  }

  componentDidMount() {
    if (!this.props.state.getIn(['app', 'categoryId']) || !this.props.state.getIn(['app', 'category'])) {
      fetchNeeds(this.needs, this.props);
    }
  }

  renderMarkets(marketData) {
    if (!this.props.state.getIn(['app', 'universeId'])) {
      return (

        ...loading...

      );
    } else {
      const universeId = this.props.params.universeId
      const universeList = this.props.state.getIn(['app', 'universeList'])
      const linkBase = "/universe/"+universeId+"/category/"+marketData.parentId+"/market/"+marketData.id
      var linkTarget

      if (universeList.toJS().segment[marketData.id].length == 1) {
        linkTarget = linkBase+"/segment/"+universeList.toJS().segment[marketData.id][0].id
      } else {
        linkTarget = linkBase
      }
      console.log("LINK TARGET", linkTarget);

      return(

            <Link to={linkTarget}>{marketData.name}</Link>

            >

      )
    }
  }

  render() {
    const universeList = this.props.state.getIn(['app', 'universeList'])
    const categoryId = this.props.params.categoryId
    // console.log(universeList.toJS());
    return (

        {universeList.toJS().market[categoryId].map(this.renderMarkets)}

    );
  }

}

MarketList.propTypes = {
  actions: PropTypes.object.isRequired,
  state: PropTypes.object.isRequired
};

MarketList.needs = needs;

function mapStateToProps(state) {
  return { state };
}

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    actions: bindActionCreators(AppActions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MarketList);
<commit_msg>removed console.log<commit_after>import React, { PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { fetchNeeds } from '../utils/fetchComponentData';

import * as AppActions from '../actions/appActions.js';

const needs = [
  AppActions.loadUniverseList,
  AppActions.setUniverseId,
  AppActions.setCategoryId
];

class MarketList extends React.Component {

  constructor(params) {
    super(params);
    this.needs = needs;
    this.renderMarkets = this.renderMarkets.bind(this);
  }

  componentDidMount() {
    if (!this.props.state.getIn(['app', 'categoryId']) || !this.props.state.getIn(['app', 'category'])) {
      fetchNeeds(this.needs, this.props);
    }
  }

  renderMarkets(marketData) {
    if (!this.props.state.getIn(['app', 'universeId'])) {
      return (

        ...loading...

      );
    } else {
      const universeId = this.props.params.universeId
      const universeList = this.props.state.getIn(['app', 'universeList'])
      const linkBase = "/universe/"+universeId+"/category/"+marketData.parentId+"/market/"+marketData.id
      var linkTarget

      if (universeList.toJS().segment[marketData.id].length == 1) {
        linkTarget = linkBase+"/segment/"+universeList.toJS().segment[marketData.id][0].id
      } else {
        linkTarget = linkBase
      }

      return(

            <Link to={linkTarget}>{marketData.name}</Link>

            >

      )
    }
  }

  render() {
    const universeList = this.props.state.getIn(['app', 'universeList'])
    const categoryId = this.props.params.categoryId
    // console.log(universeList.toJS());
    return (

        {universeList.toJS().market[categoryId].map(this.renderMarkets)}

    );
  }

}

MarketList.propTypes = {
  actions: PropTypes.object.isRequired,
  state: PropTypes.object.isRequired
};

MarketList.needs = needs;

function mapStateToProps(state) {
  return { state };
}

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    actions: bindActionCreators(AppActions, dispatch)
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MarketList);
<|endoftext|>


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

/**
 * Internal dependencies
 */
import ReminderBanner from './ReminderBanner';
import { CORE_USER } from '../../../../../googlesitekit/datastore/user/constants';
import WithRegistrySetup from '../../../../../../../tests/js/WithRegistrySetup';

const Template = () => <ReminderBanner onCTAClick={ () => {} } />;

export const InitialNotice = Template.bind( {} );
InitialNotice.storyName = 'Before 1 June 2023';
InitialNotice.decorators = [
	( Story ) => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( CORE_USER ).setReferenceDate( '2023-05-31' );
		};

		return (
			<WithRegistrySetup func={ setupRegistry }>
				<Story />
			</WithRegistrySetup>
		);
	},
];

export const LastMonth = Template.bind( {} );
LastMonth.storyName = 'During June 2023';
LastMonth.decorators = [
	( Story ) => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( CORE_USER ).setReferenceDate( '2023-06-01' );
		};

		return (
			<WithRegistrySetup func={ setupRegistry }>
				<Story />
			</WithRegistrySetup>
		);
	},
];

export const PostCutoff = Template.bind( {} );
PostCutoff.storyName = 'After 30 June 2023';
PostCutoff.decorators = [
	( Story ) => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( CORE_USER ).setReferenceDate( '2023-07-01' );
		};

		return (
			<WithRegistrySetup func={ setupRegistry }>
				<Story />
			</WithRegistrySetup>
		);
	},
];

export default {
	title: 'Modules/Analytics4/ReminderBanner',
};
<commit_msg>Fix storybook regression.<commit_after>/**
 * ReminderBanner Component Stories.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import ReminderBanner from './ReminderBanner';
import { CORE_USER } from '../../../../../googlesitekit/datastore/user/constants';
import WithRegistrySetup from '../../../../../../../tests/js/WithRegistrySetup';

const Template = () => <ReminderBanner onSubmitSuccess={ () => {} } />;

export const InitialNotice = Template.bind( {} );
InitialNotice.storyName = 'Before 1 June 2023';
InitialNotice.decorators = [
	( Story ) => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( CORE_USER ).setReferenceDate( '2023-05-31' );
		};

		return (
			<WithRegistrySetup func={ setupRegistry }>
				<Story />
			</WithRegistrySetup>
		);
	},
];

export const LastMonth = Template.bind( {} );
LastMonth.storyName = 'During June 2023';
LastMonth.decorators = [
	( Story ) => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( CORE_USER ).setReferenceDate( '2023-06-01' );
		};

		return (
			<WithRegistrySetup func={ setupRegistry }>
				<Story />
			</WithRegistrySetup>
		);
	},
];

export const PostCutoff = Template.bind( {} );
PostCutoff.storyName = 'After 30 June 2023';
PostCutoff.decorators = [
	( Story ) => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( CORE_USER ).setReferenceDate( '2023-07-01' );
		};

		return (
			<WithRegistrySetup func={ setupRegistry }>
				<Story />
			</WithRegistrySetup>
		);
	},
];

export default {
	title: 'Modules/Analytics4/ReminderBanner',
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import GitEvent from '../GitEvent';
import Select from 'react-select';
import Toggle from 'material-ui/lib/toggle';
import Cache from '../../utils/Cache';
import withStyles from '../../decorators/withStyles';
import styles from './GitEvents.css';

@withStyles(styles)
class GitEvents extends Component {

    constructor(props) {
        super(props);
        this.state = {filter: [], data: props.data, load: false};
        this.loadInterval = null;
    }

    loadEvents() {
        this.setState({
            //filter: this.state.filter,
            data: Cache.get('events').concat(this.state.data)
        });
    }

    setLoad(bool) {
        if(bool && !this.loadInterval)
            this.loadInterval = setInterval(::this.loadEvents, 50);
        else {
            clearInterval(this.loadInterval)
            this.loadInterval = null;
        }
    }

    randomEvents() {
        this.setState({
            //filter: this.state.filter,
            data: this.shuffle(this.state.data)
        });
    }

    setRandom(bool) {
        if(bool && !this.randomInterval)
            this.randomInterval = setInterval(::this.randomEvents, 50);
    else {
            clearInterval(this.randomInterval)
            this.randomInterval = null;
        }
    }

    shuffle(array) {
        var currentIndex = array.length, temporaryValue, randomIndex ;

        // While there remain elements to shuffle...
        while (0 !== currentIndex) {

            // Pick a remaining element...
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex -= 1;

            // And swap it with the current element.
            temporaryValue = array[currentIndex];
            array[currentIndex] = array[randomIndex];
            array[randomIndex] = temporaryValue;
        }

        return array;
    }

    getEventsTypes(events) {
        var result = events.reduce(function(acc, next) {
            if(acc.indexOf(next.type) === -1) {
                acc.push(next.type)
            }

            return acc;
        }, []);
        result.sort();
        return result;
    }

    filterEvents(event) {
        if(this.state.filter.length === 0)
            return true;
        return this.state.filter.indexOf(event.type) !== -1;
    }

    handleSortChange(val) {
        let filter = val.split(',');
        this.setState({
            filter: filter[0] === '' ? [] : filter,
            //data: this.state.data
        });
    }

    toggleLoad(event, toggled) {
        this.setLoad(toggled);
    }

    toggleRandom(event, toggled) {
        this.setRandom(toggled);
    }

    componentDidcomponentWillUnmountUnMount() {
        clearInterval(this.loadInterval);
        clearInterval(this.randomInterval);
    }

    render() {

        let eventNodes = this.state.data.filter(::this.filterEvents).map(function(event, index) {
            return (
                <GitEvent key={index} dataEvent={event} />
            );
        });

        let eventTypesOptions = this.getEventsTypes(this.state.data).map(function(type) {
            return (
                { value: type, label: type }
            );
        });
        let selectedTypes = this.state.filter.join();
        return (

                <Select
                    name="form-field-name"
                    value={selectedTypes}
                    options={eventTypesOptions}
                    onChange={::this.handleSortChange}
                    multi={true}
                    />

                    <Toggle
                        name="toggleName1"
                        value="toggleValue1"
                        label="Load"
                        onToggle={::this.toggleLoad}
                        />
                    {this.state.data.length}

                    <Toggle
                        name="toggleName2"
                        value="toggleValue2"
                        label="Randomize"
                        onToggle={::this.toggleRandom}
                        />

{eventNodes}

        );
    }
}

export default GitEvents;<commit_msg>added some controls<commit_after>import React, { Component } from 'react';
import GitEvent from '../GitEvent';
import Select from 'react-select';
import Toggle from 'material-ui/lib/toggle';
import Slider from 'material-ui/lib/slider';
import Cache from '../../utils/Cache';
import withStyles from '../../decorators/withStyles';
import styles from './GitEvents.css';

@withStyles(styles)
class GitEvents extends Component {

    constructor(props) {
        super(props);
        this.state = {filter: [], data: props.data, load: false};
        this.loadInterval = null;
        this.randomInterval = null;
        this.randomFrequencyFactor = 1000;
        this.randomFrequency = 1 * this.randomFrequencyFactor;
    }

    loadEvents() {
        this.setState({
            //filter: this.state.filter,
            data: Cache.get('events').concat(this.state.data)
        });
    }

    setLoad(bool) {
        if(bool && !this.loadInterval)
            this.loadInterval = setInterval(::this.loadEvents, 200);
        else {
            clearInterval(this.loadInterval)
            this.loadInterval = null;
        }
    }

    randomEvents() {
        this.setState({
            //filter: this.state.filter,
            data: this.shuffle(this.state.data)
        });
    }

    setRandom(bool) {
        console.log(this.randomFrequency);
        if(bool && !this.randomInterval)
            this.randomInterval = setInterval(::this.randomEvents, this.randomFrequency);
    else {
            clearInterval(this.randomInterval)
            this.randomInterval = null;
        }
    }

    shuffle(array) {
        var currentIndex = array.length, temporaryValue, randomIndex ;

        // While there remain elements to shuffle...
        while (0 !== currentIndex) {

            // Pick a remaining element...
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex -= 1;

            // And swap it with the current element.
            temporaryValue = array[currentIndex];
            array[currentIndex] = array[randomIndex];
            array[randomIndex] = temporaryValue;
        }

        return array;
    }

    getEventsTypes(events) {
        var result = events.reduce(function(acc, next) {
            if(acc.indexOf(next.type) === -1) {
                acc.push(next.type)
            }

            return acc;
        }, []);
        result.sort();
        return result;
    }

    filterEvents(event) {
        if(this.state.filter.length === 0)
            return true;
        return this.state.filter.indexOf(event.type) !== -1;
    }

    handleSortChange(val) {
        let filter = val.split(',');
        this.setState({
            filter: filter[0] === '' ? [] : filter,
            //data: this.state.data
        });
    }

    toggleLoad(event, toggled) {
        this.setLoad(toggled);
    }

    toggleRandom(event, toggled) {
        this.setRandom(toggled);
    }

    changeRandomFrequency(event, value) {
        this.randomFrequency = (1 - value) > 0 ? (1 - value) * this.randomFrequencyFactor: 0;
        if(this.randomInterval) {
            this.setRandom(false);
            this.setRandom(true);
        }
    }

    componentDidcomponentWillUnmountUnMount() {
        clearInterval(this.loadInterval);
        clearInterval(this.randomInterval);
    }

    render() {

        let eventNodes = this.state.data.filter(::this.filterEvents).map(function(event, index) {
            return (
                <GitEvent key={index} dataEvent={event} />
            );
        });

        let eventTypesOptions = this.getEventsTypes(this.state.data).map(function(type) {
            return (
                { value: type, label: type }
            );
        });
        let selectedTypes = this.state.filter.join();
        return (

                <Select
                    name="form-field-name"
                    value={selectedTypes}
                    options={eventTypesOptions}
                    onChange={::this.handleSortChange}
                    multi={true}
                    />

                    <Toggle
                        name="toggleName1"
                        value="toggleValue1"
                        label="Load"
                        onToggle={::this.toggleLoad}
                        />
                    {this.state.data.length}

                    <Toggle
                        name="toggleName2"
                        value="toggleValue2"
                        label="Randomize"
                        onToggle={::this.toggleRandom}
                        />

                <Slider name="slider1" onChange={::this.changeRandomFrequency} />

{eventNodes}

        );
    }
}

export default GitEvents;<|endoftext|>


<commit_before>
vendor_css = [];//add the path to any vendor css to this array
tests_css = ['./bower_components/qunit/qunit/qunit.css'];
module.exports = {
	development: {
		options: {
			paths: ["<%= paths.source %>/less/<%= theme %>/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.outputDir %>/<%= theme %>/<%= paths.development %>/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true
		},
		files: {
			"<%= paths.outputDir %>/<%= theme %>/<%= paths.development %>/css/index.css": [vendor_css, "<%= paths.source %>/less/style.less"]
		}
	},
	test: {
		options: {
			paths: ["<%= paths.temporary %>/tests/css/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.temporary %>/tests/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true
		},
		files: {
			"<%= paths.temporary %>/tests/css/index.css": [vendor_css, tests_css, "<%= paths.source %>/less/style.less"]
		}
	},
	staging: {
		options: {
			paths: ["<%= paths.source %>/less/<%= theme %>/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.outputDir %>/<%= theme %>/<%= paths.staging %>/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true,
			compress: true
		},
		files: {
			"<%= paths.outputDir %>/<%= theme %>/<%= paths.staging %>/css/index.css": [vendor_css, "<%= paths.source %>/less/style.less"]
		}
	},
	production: {
		options: {
			paths: ["<%= paths.source %>/less/<%= theme %>/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.outputDir %>/<%= theme %>/<%= paths.production %>/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true,
			compress: true
		},
		files: {
			"<%= paths.outputDir %>/<%= theme %>/<%= paths.production %>/css/index.css": [vendor_css, "<%= paths.source %>/less/style.less"]
		}
	}
}<commit_msg>Fixed source file addressing issue in less task<commit_after>
vendor_css = [];//add the path to any vendor css to this array
tests_css = ['./bower_components/qunit/qunit/qunit.css'];
module.exports = {
	development: {
		options: {
			paths: ["<%= paths.source %>/less/<%= theme %>/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.outputDir %>/<%= theme %>/<%= paths.development %>/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true
		},
		files: {
			"<%= paths.outputDir %>/<%= theme %>/<%= paths.development %>/css/index.css": [vendor_css, "<%= paths.source %>/less/<%= theme %>/style.less"]
		}
	},
	test: {
		options: {
			paths: ["<%= paths.temporary %>/tests/css/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.temporary %>/tests/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true
		},
		files: {
			"<%= paths.temporary %>/tests/css/index.css": [vendor_css, tests_css, "<%= paths.source %>/less/<%= theme %>/style.less"]
		}
	},
	staging: {
		options: {
			paths: ["<%= paths.source %>/less/<%= theme %>/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.outputDir %>/<%= theme %>/<%= paths.staging %>/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true,
			compress: true
		},
		files: {
			"<%= paths.outputDir %>/<%= theme %>/<%= paths.staging %>/css/index.css": [vendor_css, "<%= paths.source %>/less/<%= theme %>/style.less"]
		}
	},
	production: {
		options: {
			paths: ["<%= paths.source %>/less/<%= theme %>/"],
			sourceMap: true,
			sourceMapFilename: '<%= paths.outputDir %>/<%= theme %>/<%= paths.production %>/css/index.css.map',
			sourceMapURL: 'index.css.map',
			sourceMapRootpath: '',
			outputSourceFiles: true,
			compress: true
		},
		files: {
			"<%= paths.outputDir %>/<%= theme %>/<%= paths.production %>/css/index.css": [vendor_css, "<%= paths.source %>/less/<%= theme %>/style.less"]
		}
	}
}<|endoftext|>