<commit_before>/*
 * Copyright 2016 e-UCM (http://www.e-ucm.es/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * This project has received funding from the European Union’s Horizon
 * 2020 research and innovation programme under grant agreement No 644187.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0 (link is external)
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

/**
 * This file exports the main roles AnalyticsBackend uses which are:
 *      'developer', 'teacher' and 'student'.
 *
 * Also indicates the anonymous routes used by the gleaner-tracker module to
 * send data to the collector server.
 */

exports.app = {
    roles: [
        {
            roles: 'student',
            allows: [
                {
                    resources: [
                        '/games/public',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/games/:gameId/versions/:versionId/activities/my',
                        '/classes/my',
                        '/classes/:classId/activities/my',
                        '/activities/my',
                        '/activities/:activityId/results',
                        '/lti/keyid/:gameId/:versionId/:classId',
                        '/activities/:id/attemps/my',
                        '/activities/:id/attemps/:userId',
                        '/activities/:id/attemps',
                        '/courses/:id'
                    ],
                    permissions: [
                        'get'
                    ]
                },
                {
                    resources: [
                        '/classes/:classId',
                        '/activities/:activityId'
                    ],
                    permissions: [
                        'put',
                        'get'
                    ]
                },
                {
                    resources: [
                        '/activities/:activityId/results/resultId'
                    ],
                    permissions: [
                        'put'
                    ]
                }
            ]
        },
        {
            roles: 'teacher',
            allows: [
                {
                    resources: [
                        '/activities/data/:activityId',
                        '/activities/data/:activityId/:user'
                    ],
                    permissions: [
                        'delete'
                    ]
                },
                {
                    resources: [
                        '/analysis/:id',
                        '/games/public',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/games/:gameId/versions/:versionId/activities/my',
                        '/classes/my',
                        '/classes/:classId/activities/my',
                        '/activities/my',
                        '/activities/:activityId/results',
                        '/lti/keyid/:classId',
                        '/activities/:id/attemps/my',
                        '/activities/:id/attemps/:userId',
                        '/activities/:id/attemps'
                    ],
                    permissions: [
                        'get'
                    ]
                },
                {
                    resources: [
                        '/classes/:classId',
                        '/classes/:classId/remove',
                        '/activities/:activityId',
                        '/activities/:activityId/remove',
                        '/kibana/*',
                        '/courses/:id',
                        '/classes/groups/:id',
                        '/classes/groupings/:id'
                    ],
                    permissions: [
                        '*'
                    ]
                },
                {
                    resources: [
                        '/classes',
                        '/activities',
                        '/activities/:activityId/event/:event',
                        '/activities/:activityId/results',
                        '/lti'
                    ],
                    permissions: [
                        'post'
                    ]
                },
                {
                    resources: [
                        '/courses',
                        '/classes/:id/groups',
                        '/classes/:id/groupings'
                    ],
                    permissions: [
                        'get',
                        'post'
                    ]
                },
                {
                    resources: [
                        '/activities/:activityId/results/resultId',
                        '/classes/groups/:id/remove'
                    ],
                    permissions: [
                        'put'
                    ]
                }
            ]
        },
        {
            roles: 'teachingassistant',
            allows: [
                {
                    resources: [
                        '/activities',
                        '/activities/:activityId/event/:event',
                        '/activities/:activityId/results',
                        '/analysis/:id',
                        '/games/public',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/games/:gameId/versions/:versionId/activities/my',
                        '/classes',
                        '/classes/my',
                        '/classes/:classId',
                        '/classes/:classId/remove',
                        '/classes/:classId/activities/my',
                        '/activities/my',
                        '/activities/:activityId/results',
                        '/activities/:activityId',
                        '/activities/:activityId/remove',
                        '/activities/:activityId/results/:resultsId',
                        '/lti',
                        '/lti/keyid/:classId',
                        '/kibana/*'
                    ],
                    permissions: [
                        'get'
                    ]
                }
            ]
        },
        {
            roles: 'developer',
            allows: [
                {
                    resources: [
                        '/games/my',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/kibana/*'
                    ],
                    permissions: [
                        '*'
                    ]
                },
                {
                    resources: [
                        '/analysis/:id',
                        '/analysis/:versionId'
                    ],
                    permissions: [
                        '*'
                    ]
                },
                {
                    resources: [
                        '/games/:gameId/remove'
                    ],
                    permissions: [
                        'put'
                    ]
                },
                {
                    resources: [
                        '/classes',
                        '/classes/:classId',
                        '/activities',
                        '/sessions/:sessionId',
                        '/lti/keyid/:classId'
                    ],
                    permissions: [
                        'get'
                    ]
                },
                {
                    resources: [
                        '/sessions/test/:versionId',
                        '/games'
                    ],
                    permissions: [
                        'post'
                    ]
                }
            ]
        }
    ],
    anonymous: [
        '/games/:id/xapi/:versionId',
        '/collector/start/:trackingCode',
        '/collector/stop/:trackingCode',
        '/collector/track',
        '/lti/key/:id',
        '/env'
    ],
    autoroles: [
        'student',
        'teacher',
        'teachingassistant',
        'developer'
    ]
};
<commit_msg>Corrected urls for collector and attempts<commit_after>/*
 * Copyright 2016 e-UCM (http://www.e-ucm.es/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * This project has received funding from the European Union’s Horizon
 * 2020 research and innovation programme under grant agreement No 644187.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0 (link is external)
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

/**
 * This file exports the main roles AnalyticsBackend uses which are:
 *      'developer', 'teacher' and 'student'.
 *
 * Also indicates the anonymous routes used by the gleaner-tracker module to
 * send data to the collector server.
 */

exports.app = {
    roles: [
        {
            roles: 'student',
            allows: [
                {
                    resources: [
                        '/games/public',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/games/:gameId/versions/:versionId/activities/my',
                        '/classes/my',
                        '/classes/:classId/activities/my',
                        '/activities/my',
                        '/activities/:activityId/results',
                        '/lti/keyid/:gameId/:versionId/:classId',
                        '/activities/:activityId/attemps/my',
                        '/activities/:activityId/attemps/:userId',
                        '/activities/:activityId/attemps',
                        '/courses/:id'
                    ],
                    permissions: [
                        'get'
                    ]
                },
                {
                    resources: [
                        '/classes/:classId',
                        '/activities/:activityId'
                    ],
                    permissions: [
                        'put',
                        'get'
                    ]
                },
                {
                    resources: [
                        '/activities/:activityId/results/resultId'
                    ],
                    permissions: [
                        'put'
                    ]
                }
            ]
        },
        {
            roles: 'teacher',
            allows: [
                {
                    resources: [
                        '/activities/data/:activityId',
                        '/activities/data/:activityId/:user'
                    ],
                    permissions: [
                        'delete'
                    ]
                },
                {
                    resources: [
                        '/analysis/:id',
                        '/games/public',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/games/:gameId/versions/:versionId/activities/my',
                        '/classes/my',
                        '/classes/:classId/activities/my',
                        '/activities/my',
                        '/activities/:activityId/results',
                        '/activities/:activityId/attempts',
                        '/activities/:activityId/attempts/my',
                        '/activities/:activityId/attempts/:username',
                        '/lti/keyid/:classId'
                    ],
                    permissions: [
                        'get'
                    ]
                },
                {
                    resources: [
                        '/classes/:classId',
                        '/classes/:classId/remove',
                        '/activities/:activityId',
                        '/activities/:activityId/remove',
                        '/kibana/*',
                        '/courses/:id',
                        '/classes/groups/:id',
                        '/classes/groupings/:id'
                    ],
                    permissions: [
                        '*'
                    ]
                },
                {
                    resources: [
                        '/classes',
                        '/activities',
                        '/activities/:activityId/event/:event',
                        '/activities/:activityId/results',
                        '/lti'
                    ],
                    permissions: [
                        'post'
                    ]
                },
                {
                    resources: [
                        '/courses',
                        '/classes/:id/groups',
                        '/classes/:id/groupings'
                    ],
                    permissions: [
                        'get',
                        'post'
                    ]
                },
                {
                    resources: [
                        '/activities/:activityId/results/resultId',
                        '/classes/groups/:id/remove'
                    ],
                    permissions: [
                        'put'
                    ]
                }
            ]
        },
        {
            roles: 'teachingassistant',
            allows: [
                {
                    resources: [
                        '/activities',
                        '/activities/:activityId/event/:event',
                        '/activities/:activityId/results',
                        '/analysis/:id',
                        '/games/public',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/games/:gameId/versions/:versionId/activities/my',
                        '/classes',
                        '/classes/my',
                        '/classes/:classId',
                        '/classes/:classId/remove',
                        '/classes/:classId/activities/my',
                        '/activities/my',
                        '/activities/:activityId/results',
                        '/activities/:activityId',
                        '/activities/:activityId/remove',
                        '/activities/:activityId/results/:resultsId',
                        '/lti',
                        '/lti/keyid/:classId',
                        '/kibana/*'
                    ],
                    permissions: [
                        'get'
                    ]
                }
            ]
        },
        {
            roles: 'developer',
            allows: [
                {
                    resources: [
                        '/games/my',
                        '/games/:gameId',
                        '/games/:gameId/versions',
                        '/games/:gameId/versions/:versionId',
                        '/kibana/*'
                    ],
                    permissions: [
                        '*'
                    ]
                },
                {
                    resources: [
                        '/analysis/:id',
                        '/analysis/:versionId'
                    ],
                    permissions: [
                        '*'
                    ]
                },
                {
                    resources: [
                        '/games/:gameId/remove'
                    ],
                    permissions: [
                        'put'
                    ]
                },
                {
                    resources: [
                        '/classes',
                        '/classes/:classId',
                        '/activities',
                        '/sessions/:sessionId',
                        '/lti/keyid/:classId'
                    ],
                    permissions: [
                        'get'
                    ]
                },
                {
                    resources: [
                        '/sessions/test/:versionId',
                        '/games'
                    ],
                    permissions: [
                        'post'
                    ]
                }
            ]
        }
    ],
    anonymous: [
        '/games/:id/xapi/:versionId',
        '/collector/start/:trackingCode',
        '/collector/track',
        '/collector/end',
        '/lti/key/:id',
        '/env'
    ],
    autoroles: [
        'student',
        'teacher',
        'teachingassistant',
        'developer'
    ]
};
<|endoftext|>


<commit_before>en_US = {
  day: {
    abbrev: "Sun Mon Tue Wed Thu Fri Sat".split(/\s/),
    full: "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(/\s+/)
  }
};
function die () {
  if (arguments.length) console.log.apply(console, Array.prototype.slice.call(arguments, 0));
  process.exit(1);
}

function say() {
  if (arguments.length) console.log.apply(console, Array.prototype.slice.call(arguments, 0));
}

const SECOND = 1000, MINUTE = SECOND * 60, HOUR = MINUTE * 60, DAY = HOUR * 24;

data = (function () {
  function copy(dest, source) {
    for (var key in source) if (source.hasOwnProperty(key)) dest[key] = source[key];
  }
  var file, continent, zoneFiles = require('fs').readdirSync('./timezones'), data = { zones: {}, rules: {} };
  for (var i = 0, stop = zoneFiles.length; i < stop; i++) {
    file = zoneFiles[i];
    if (file === 'index.js' || /^\./.test(file)) continue;
    continent = require('./timezones/' + file, 'utf8');
    copy(data.zones, continent.zones);
    copy(data.rules, continent.rules);
  }
  return data;
})();

var transitions = (function createTransitions() {

  function parseOffset (pattern) {
    if (typeof pattern == "number") return pattern;

    var offset = 0;

    // TODO Revert when rules becomes an offset integer.
    var match = /^(-?)(\d+)(?::(\d+))?(?::(\d+))?$/.exec(pattern)
    if (! match) return null;
    match = match.slice(1, 5);
    match[0] += '1'

    var milliseconds = [ HOUR, MINUTE, SECOND ];
    for (var i = 1, stop = match.length; i < stop; i++) {
      offset += parseInt(match[i] || '0', 10) * milliseconds[i - 1];
    }

    offset *= parseInt(match[0]);

    return offset;
  }

  function isLeapYear (year) {
    if (year % 400 == 0) return true;
    if (year % 100 == 0) return false
    if (year % 4 == 0) return true;
    return false;
  }

  var DAYS_IN_MONTH = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

  function daysInMonth (month, year) {
    var days = DAYS_IN_MONTH[month];
    if (month == 1 && isLeapYear(year)) days++;
    return days;
  }

  function parseUntil (until) {
    if (until) {
      var fields = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).000Z$/.exec(until).slice(1, 7);
      for (var i = 0, stop = fields.length; i < stop; i++) {
        fields[i] = parseInt(fields[i], 10);
      }
      --fields[1];
      return Date.UTC.apply(Date.UTC, fields);
    }
    return Number.MAX_VALUE;
  }
  function actualize (entry, rule, year, ruleIndex) {
    var time = /^(\d+):(\d+)(?::(\d+))?[us]?$/.exec(rule.time).slice(1, 4);
    for (var i = 0; i < 3; i++) {
      time[i] = parseInt(time[i] || 0, 10);
    }
    var hours = time[0], minutes = time[1], seconds = time[2];
    var date = /^(?:(\d+)|last(\w+)|(\w+)>=(\d+))$/.exec(rule.day);
    var fields;
    if (date[1]) {
      fields = new Date(Date.UTC(year, rule.month, parseInt(date[1], 10), hours, minutes, seconds));
    } else if (date[2]) {
      var days = en_US.day.abbrev;
      for (var i = 0, stop = days.length; i < stop; i++)
        if (days[i] === date[2]) break;
      var day = daysInMonth(rule.month, year);
      for (;;) {
        fields = new Date(Date.UTC(year, rule.month, day, hours, minutes, seconds))
        if (fields.getUTCDay() === i) break;
        day--;
      }
    } else {
      var min = parseInt(date[4], 10);
      var days = en_US.day.abbrev;
      for (var i = 0, stop = days.length; i < stop; i++)
        if (days[i] === date[3]) break;
      day = 1;
      for (;;) {
        fields = new Date(Date.UTC(year, rule.month, day, hours, minutes, seconds))
        if (fields.getUTCDay() === i && fields.getUTCDate() >= min) break;
        day++;
      }
    }

    var save = parseOffset(rule.save || '0');

    var offset = entry.offset;

    var sortable, wallclock, clock, posix, standard;
    if (/u$/.test(rule.time)) {
      posix = fields.getTime();
      fields = new Date(fields.getTime() + offset);
      clock = "posix";
    } else if (/s$/.test(rule.time)) {
      standard = fields.getTime();
      fields = new Date(standard);
      clock = "standard";
    } else {
      wallclock = fields.getTime();
      fields = new Date(wallclock);
      clock = "wallclock";
    }

    sortable = Date.UTC(fields.getUTCFullYear(), fields.getUTCMonth(), fields.getUTCDate());

    return {
      clock: clock,
      standard: standard,
      entry: entry,
      sortable: sortable,
      rule:rule,
      wallclock: wallclock,
      year: year,
      posix: posix,
      save: save,
      ruleIndex: ruleIndex,
      offset: offset
    };
  }

  function setClocks (record, effective, clock) {
    var offset = effective.offset
      , save = effective.save;
    switch (clock || record.clock) {
    case "posix":
      record.standard = record.posix + offset;
      record.wallclock = record.posix + offset + save;
      break;
    case "wallclock":
      record.posix = record.wallclock - offset - save;
      record.standard = record.wallclock + save;
      break;
    case "standard":
      record.posix = record.standard - offset;
      record.wallclock = record.standard + save;
      break;
    }
    return record;
  }

  function ruleClock(rule) {
    if (/[ugz]$/.exec(rule.time)) return "posix";
    if (/s$/.exec(rule.time)) return "standard";
    return "wallclock";
  }

  function getYear (time) { return new Date(time).getUTCFullYear() }

  function pushRule (table, entry, actual, abbrevs) {
    var save = parseOffset(actual.rule.save || "0")
      , abbrev = abbrevs[save ? 1 : 0] || entry.format.replace(/%s/, function () { return actual.rule.letter });
    table.push({
      actualization: true,
      offset: entry.offset,
      abbrev: abbrev,
      clock: actual.clock,
      wallclock: actual.wallclock,
      posix: actual.posix,
      standard: actual.standard,
      save: parseOffset(actual.rule.save || "0")
    });
  }

  function iso8601 (date) { return new Date(date).toISOString().replace(/\..*$/, "") }

  function step (begin, end, table) {
    var match
      , actual
      , max;

    // Oink. Oink. We'll be pigs.

    var rule
      , abbrevs
      , year = getYear(new Date().getTime())
      , rules = data.rules[begin.rules].slice(0)
      , actualized = [];

    if (abbrevs = /(\w+)\/(\w+)/.exec(begin.format)) abbrevs.shift();
    else abbrevs = []

    for (var i = 0, length = rules.length; i < length; i++) {
      rules[i].clock = ruleClock(rules[i]); // TODO Temporary.
      for (var j = rules[i].from, to = Math.min(rules[i].to, year); j <= to; j++) {
        actualized.push(actualize(begin, rules[i], j, i));
      }
    }

    actualized.sort(function (a, b) { return a.sortable - b.sortable });

    i = 0, length = actualized.length

    for (; i < length; i++) {
      if (begin[actualized[i].clock] <= actualized[i][actualized[i].clock]) break;
    }

    var previous = begin;

    if (begin[actualized[i].clock] == actualized[i][actualized[i].clock]) {
      table.pop(); // A rule on the seam overrides the seam.
      previous = table[table.length - 1];
    } else {
      if (i === 0) {
        var rules = data.rules[begin.rules], offset;
        for (var j = 0; j < rules.length; j++) {
          if (!(offset = parseOffset(rules[j].save || "0"))) {
            begin.abbrev = abbrevs[0] || begin.format.replace(/%s/, function () { return rules[j].letter });
            break;
          }
        }
      } else {
        begin.save = parseOffset(actualized[i - 1].save);
        begin.abbrev = abbrevs[begin.save ? 1 : 0] || begin.format.replace(/%s/, function () { return actualized[i - 1].rule.letter });
      }
    }

    for (; i < length; i++) {
      setClocks(actualized[i], previous);
      if (actualized[i][end.clock] >= end[end.clock]) {
        break;
      }
      pushRule(table, begin, actualized[i], abbrevs);
      previous = actualized[i];
    }
  }

  function walk (begin, end, table) {
    step(begin, end, table);
    return table[table.length - 1];
  }

  function begins (zone) {
    var copy = [];
    for (var i = zone.length - 2; i >= 0; --i) {
      copy[i] = {
        rules: zone[i].rules,
        format: zone[i].format,
        offset: parseOffset(zone[i].offset),
        save: 0,
        // TODO Intermediate format is all kinds of broken.
        clock: zone[i + 1].utc ? 'posix' : (zone[i + 1].standard ? 'standard' : 'wallclock')
      };
      copy[i][copy[i].clock] = parseUntil(zone[i + 1].until);
    }

    copy.push({
      offset: parseOffset(zone[zone.length - 1].offset),
      format: zone[zone.length - 1].format,
      save: 0
    });

    copy.reverse();
    copy[0].posix = copy[0].wallclock = Number.MIN_VALUE;
    copy.forEach(function (e) { e.begins =  iso8601(e[e.clock]) + " " + e.clock });
    return copy;
  }

  return function transitions (zoneName, consumer) {
    // We `concat` because someday, we'll read right out of the database and the
    // `concat` will be our defensive copy.
    var table = []
      , zone = begins(data.zones[zoneName]).concat({ offset: "0" })
      , entry = zone.shift()
      , previous, offset, save = 0, wallclock, posix, rules;

    table.push(entry);

    for (var i = 0, length = zone.length; i < Math.min(13, length); i++) {
      previous = entry;
      entry = zone[i];

      previous.abbrev = previous.format;

      if (previous.rules) {
        if (!(previous.save = parseOffset(previous.rules))) {
          previous = walk(previous, entry, table); // previous is last rule, not last zone.
        }
      } else {
        previous.save = 0;
      }

      setClocks(entry, previous);

      table.push(entry);
    }
    return table.reverse();
  }
})();

(function () {
  function pad (value) { return ('00' + value).slice(-2) }
  function formatOffset (offset) {
    var increment = offset / Math.abs(offset);
    var sign = offset < 0 ? '-' : '+';
    var millis, seconds, minutes, hours;
    offset = Math.abs(offset);
    offset -= (millis = offset % 1000);
    offset /= 1000;
    offset -= (seconds = offset % 60);
    offset /= 60;
    offset -= (minutes = offset % 60);
    hours = offset / 60;
    return sign + pad(hours) + ':' + pad(minutes) + ':' + pad(seconds);
  }
  function iso8601 (date) {
    return new Date(date).toISOString().replace(/\..*$/, "");
  }
  function format(entry) {
    if (! entry.abbrev) entry.abbrev = entry.format;
    return formatOffset(entry.offset + (entry.save || 0)) + '/' + entry.abbrev;
  }
  var set = process.argv[2] ? [ process.argv[2] ] : Object.keys(data.zones).filter(function (e) { return /^A/.test(e) });
  for (var i = 0, length = set.length; i < length; i++) {
    try {
      var table = transitions(set[i], function (entry, posix, wallclock) {
        entry.posix = posix;
        entry.wallclock = wallclock;
      });
      for (var j = 1, stop = table.length - 1; j < stop; j++) {
        entry = table[j];
        say("%s %s %s %s", set[i], iso8601(entry.wallclock), iso8601(entry.posix), format(table[j + 1]), format(table[j]));
      }
    } catch (e) {
      say("Failed on " + set[i] + ".");
      throw e;
    }
  }
})();
<commit_msg>Zone is only clock transition when no rules apply.<commit_after>en_US = {
  day: {
    abbrev: "Sun Mon Tue Wed Thu Fri Sat".split(/\s/),
    full: "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(/\s+/)
  }
};
function die () {
  if (arguments.length) console.log.apply(console, Array.prototype.slice.call(arguments, 0));
  process.exit(1);
}

function say() {
  if (arguments.length) console.log.apply(console, Array.prototype.slice.call(arguments, 0));
}

const SECOND = 1000, MINUTE = SECOND * 60, HOUR = MINUTE * 60, DAY = HOUR * 24;

data = (function () {
  function copy(dest, source) {
    for (var key in source) if (source.hasOwnProperty(key)) dest[key] = source[key];
  }
  var file, continent, zoneFiles = require('fs').readdirSync('./timezones'), data = { zones: {}, rules: {} };
  for (var i = 0, stop = zoneFiles.length; i < stop; i++) {
    file = zoneFiles[i];
    if (file === 'index.js' || /^\./.test(file)) continue;
    continent = require('./timezones/' + file, 'utf8');
    copy(data.zones, continent.zones);
    copy(data.rules, continent.rules);
  }
  return data;
})();

var transitions = (function createTransitions() {

  function parseOffset (pattern) {
    if (typeof pattern == "number") return pattern;

    var offset = 0;

    // TODO Revert when rules becomes an offset integer.
    var match = /^(-?)(\d+)(?::(\d+))?(?::(\d+))?$/.exec(pattern)
    if (! match) return null;
    match = match.slice(1, 5);
    match[0] += '1'

    var milliseconds = [ HOUR, MINUTE, SECOND ];
    for (var i = 1, stop = match.length; i < stop; i++) {
      offset += parseInt(match[i] || '0', 10) * milliseconds[i - 1];
    }

    offset *= parseInt(match[0]);

    return offset;
  }

  function isLeapYear (year) {
    if (year % 400 == 0) return true;
    if (year % 100 == 0) return false
    if (year % 4 == 0) return true;
    return false;
  }

  var DAYS_IN_MONTH = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

  function daysInMonth (month, year) {
    var days = DAYS_IN_MONTH[month];
    if (month == 1 && isLeapYear(year)) days++;
    return days;
  }

  function parseUntil (until) {
    if (until) {
      var fields = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).000Z$/.exec(until).slice(1, 7);
      for (var i = 0, stop = fields.length; i < stop; i++) {
        fields[i] = parseInt(fields[i], 10);
      }
      --fields[1];
      return Date.UTC.apply(Date.UTC, fields);
    }
    return Number.MAX_VALUE;
  }
  function actualize (entry, rule, year, ruleIndex) {
    var time = /^(\d+):(\d+)(?::(\d+))?[us]?$/.exec(rule.time).slice(1, 4);
    for (var i = 0; i < 3; i++) {
      time[i] = parseInt(time[i] || 0, 10);
    }
    var hours = time[0], minutes = time[1], seconds = time[2];
    var date = /^(?:(\d+)|last(\w+)|(\w+)>=(\d+))$/.exec(rule.day);
    var fields;
    if (date[1]) {
      fields = new Date(Date.UTC(year, rule.month, parseInt(date[1], 10), hours, minutes, seconds));
    } else if (date[2]) {
      var days = en_US.day.abbrev;
      for (var i = 0, stop = days.length; i < stop; i++)
        if (days[i] === date[2]) break;
      var day = daysInMonth(rule.month, year);
      for (;;) {
        fields = new Date(Date.UTC(year, rule.month, day, hours, minutes, seconds))
        if (fields.getUTCDay() === i) break;
        day--;
      }
    } else {
      var min = parseInt(date[4], 10);
      var days = en_US.day.abbrev;
      for (var i = 0, stop = days.length; i < stop; i++)
        if (days[i] === date[3]) break;
      day = 1;
      for (;;) {
        fields = new Date(Date.UTC(year, rule.month, day, hours, minutes, seconds))
        if (fields.getUTCDay() === i && fields.getUTCDate() >= min) break;
        day++;
      }
    }

    var save = parseOffset(rule.save || '0');

    var offset = entry.offset;

    var sortable, wallclock, clock, posix, standard;
    if (/u$/.test(rule.time)) {
      posix = fields.getTime();
      fields = new Date(fields.getTime() + offset);
      clock = "posix";
    } else if (/s$/.test(rule.time)) {
      standard = fields.getTime();
      fields = new Date(standard);
      clock = "standard";
    } else {
      wallclock = fields.getTime();
      fields = new Date(wallclock);
      clock = "wallclock";
    }

    sortable = Date.UTC(fields.getUTCFullYear(), fields.getUTCMonth(), fields.getUTCDate());

    return {
      clock: clock,
      standard: standard,
      entry: entry,
      sortable: sortable,
      rule:rule,
      wallclock: wallclock,
      year: year,
      posix: posix,
      save: save,
      ruleIndex: ruleIndex,
      offset: offset
    };
  }

  function setClocks (record, effective, clock) {
    var offset = effective.offset
      , save = effective.save;
    switch (clock || record.clock) {
    case "posix":
      record.standard = record.posix + offset;
      record.wallclock = record.posix + offset + save;
      break;
    case "wallclock":
      record.posix = record.wallclock - offset - save;
      record.standard = record.wallclock + save;
      break;
    case "standard":
      record.posix = record.standard - offset;
      record.wallclock = record.standard + save;
      break;
    }
    return record;
  }

  function ruleClock(rule) {
    if (/[ugz]$/.exec(rule.time)) return "posix";
    if (/s$/.exec(rule.time)) return "standard";
    return "wallclock";
  }

  function getYear (time) { return new Date(time).getUTCFullYear() }

  function pushRule (table, entry, actual, abbrevs) {
    var save = parseOffset(actual.rule.save || "0")
      , abbrev = abbrevs[save ? 1 : 0] || entry.format.replace(/%s/, function () { return actual.rule.letter });
    table.push({
      actualization: true,
      offset: entry.offset,
      abbrev: abbrev,
      clock: actual.clock,
      wallclock: actual.wallclock,
      posix: actual.posix,
      standard: actual.standard,
      save: parseOffset(actual.rule.save || "0")
    });
  }

  function iso8601 (date) { return new Date(date).toISOString().replace(/\..*$/, "") }

  function step (begin, end, table) {
    var match
      , actual
      , max;

    // Oink. Oink. We'll be pigs.

    var rule
      , abbrevs
      , year = getYear(new Date().getTime())
      , rules = data.rules[begin.rules].slice(0)
      , actualized = [];

    if (abbrevs = /(\w+)\/(\w+)/.exec(begin.format)) abbrevs.shift();
    else abbrevs = []

    for (var i = 0, length = rules.length; i < length; i++) {
      rules[i].clock = ruleClock(rules[i]); // TODO Temporary.
      for (var j = rules[i].from, to = Math.min(rules[i].to, year); j <= to; j++) {
        actualized.push(actualize(begin, rules[i], j, i));
      }
    }

    actualized.sort(function (a, b) { return a.sortable - b.sortable });

    i = 0, length = actualized.length

    for (; i < length; i++) {
      if (begin[actualized[i].clock] <= actualized[i][actualized[i].clock]) break;
    }

    var previous = begin;

    // TODO Combine with below.
    if (i == length) {
      var rules = data.rules[begin.rules], offset;
      for (var j = 0; j < rules.length; j++) {
        if (!(offset = parseOffset(rules[j].save || "0"))) {
          begin.abbrev = abbrevs[0] || begin.format.replace(/%s/, function () { return rules[j].letter });
          break;
        }
      }
      return begin;
    }

    if (begin[actualized[i].clock] == actualized[i][actualized[i].clock]) {
      table.pop(); // A rule on the seam overrides the seam.
      previous = table[table.length - 1];
    } else {
      if (i === 0) {
        var rules = data.rules[begin.rules], offset;
        for (var j = 0; j < rules.length; j++) {
          if (!(offset = parseOffset(rules[j].save || "0"))) {
            begin.abbrev = abbrevs[0] || begin.format.replace(/%s/, function () { return rules[j].letter });
            break;
          }
        }
      } else {
        begin.save = parseOffset(actualized[i - 1].save);
        begin.abbrev = abbrevs[begin.save ? 1 : 0] || begin.format.replace(/%s/, function () { return actualized[i - 1].rule.letter });
      }
    }

    for (; i < length; i++) {
      setClocks(actualized[i], previous);
      if (actualized[i][end.clock] >= end[end.clock]) {
        break;
      }
      pushRule(table, begin, actualized[i], abbrevs);
      previous = actualized[i];
    }
  }

  function walk (begin, end, table) {
    step(begin, end, table);
    return table[table.length - 1];
  }

  function begins (zone) {
    var copy = [];
    for (var i = zone.length - 2; i >= 0; --i) {
      copy[i] = {
        rules: zone[i].rules,
        format: zone[i].format,
        offset: parseOffset(zone[i].offset),
        save: 0,
        // TODO Intermediate format is all kinds of broken.
        clock: zone[i + 1].utc ? 'posix' : (zone[i + 1].standard ? 'standard' : 'wallclock')
      };
      copy[i][copy[i].clock] = parseUntil(zone[i + 1].until);
    }

    copy.push({
      offset: parseOffset(zone[zone.length - 1].offset),
      format: zone[zone.length - 1].format,
      save: 0
    });

    copy.reverse();
    copy[0].posix = copy[0].wallclock = Number.MIN_VALUE;
    copy.forEach(function (e) { e.begins =  iso8601(e[e.clock]) + " " + e.clock });
    return copy;
  }

  return function transitions (zoneName, consumer) {
    // We `concat` because someday, we'll read right out of the database and the
    // `concat` will be our defensive copy.
    var table = []
      , zone = begins(data.zones[zoneName]).concat({ offset: "0" })
      , entry = zone.shift()
      , previous, offset, save = 0, wallclock, posix, rules;

    table.push(entry);

    for (var i = 0, length = zone.length; i < Math.min(13, length); i++) {
      previous = entry;
      entry = zone[i];

      previous.abbrev = previous.format;

      if (previous.rules) {
        if (!(previous.save = parseOffset(previous.rules))) {
          previous = walk(previous, entry, table); // previous is last rule, not last zone.
        }
      } else {
        previous.save = 0;
      }

      setClocks(entry, previous);

      table.push(entry);
    }
    return table.reverse();
  }
})();

(function () {
  function pad (value) { return ('00' + value).slice(-2) }
  function formatOffset (offset) {
    var increment = offset / Math.abs(offset);
    var sign = offset < 0 ? '-' : '+';
    var millis, seconds, minutes, hours;
    offset = Math.abs(offset);
    offset -= (millis = offset % 1000);
    offset /= 1000;
    offset -= (seconds = offset % 60);
    offset /= 60;
    offset -= (minutes = offset % 60);
    hours = offset / 60;
    return sign + pad(hours) + ':' + pad(minutes) + ':' + pad(seconds);
  }
  function iso8601 (date) {
    return new Date(date).toISOString().replace(/\..*$/, "");
  }
  function format(entry) {
    if (! entry.abbrev) entry.abbrev = entry.format;
    return formatOffset(entry.offset + (entry.save || 0)) + '/' + entry.abbrev;
  }
  var set = process.argv[2] ? [ process.argv[2] ] : Object.keys(data.zones).filter(function (e) { return /^A/.test(e) });
  for (var i = 0, length = set.length; i < length; i++) {
    try {
      var table = transitions(set[i], function (entry, posix, wallclock) {
        entry.posix = posix;
        entry.wallclock = wallclock;
      });
      for (var j = 1, stop = table.length - 1; j < stop; j++) {
        entry = table[j];
        say("%s %s %s %s", set[i], iso8601(entry.wallclock), iso8601(entry.posix), format(table[j + 1]), format(table[j]));
      }
    } catch (e) {
      say("Failed on " + set[i] + ".");
      throw e;
    }
  }
})();
<|endoftext|>


<commit_before>const sendData = require('../helpers/sendData');
const BroadlinkRMAccessory = require('./accessory');

class FanAccessory extends BroadlinkRMAccessory {

  setFanSpeed (currentStatus, callback) {
    const { data, host, log } = this;

    const allHexKeys = Object.keys(data);

    // Create an array of temperatures specified in the data config
    const foundTemperatures = [];

    allHexKeys.forEach(() => {
      const parts = key.split('temperature');

      if (parts.length !== 2) return;

      foundTemperatures.push(parseInt(parts[1]));
    });

    // Find temperature closest to the one requested
    const closest = foundTemperatures.reduce((prev, curr) => Math.abs(curr - this.fanSpeed) < Math.abs(prev - this.fanSpeed) ? curr : prev);
    log(`setFanSpeed: (closest: ${closest})`);

    // Get the closest temperature's hex data
    const hexData = data[`temperature${closest}`];

    sendData(host, swingToggle, callback, log);
  }

  getServices () {
    const services = super.getServices();
    const { data, name } = this;
    const { on, off, swingToggle } = data;

    const service = new Service.Fanv2(name);
    this.addNameService(service);

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.Active,
      propertyName: 'switchState',
      onHex: on,
      offHex: off
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.SwingMode,
      propertyName: 'swingMode',
      onHex: swingToggle,
      offHex: swingToggle
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.RotationSpeed,
      propertyName: 'fanSpeed',
      setValuePromise: this.setFanSpeed
    });

    services.push(service);

    return services;
  }
}

module.exports = FanAccessory;
<commit_msg>Update fan.js # Conflicts: #	accessories/fan.js<commit_after>const sendData = require('../helpers/sendData');
const BroadlinkRMAccessory = require('./accessory');

class FanAccessory extends BroadlinkRMAccessory {

  setFanSpeed (currentStatus, callback) {
    const { data, host, log } = this;

    const allHexKeys = Object.keys(data);

    // Create an array of temperatures specified in the data config
    const foundTemperatures = [];

    allHexKeys.forEach(() => {
      const parts = key.split('temperature');

      if (parts.length !== 2) return;

  setSwingMode (currentStatus, callback) {
    const { data, host, log } = this
    const { swingToggle } = data

    // Find temperature closest to the one requested
    const closest = foundTemperatures.reduce((prev, curr) => Math.abs(curr - this.fanSpeed) < Math.abs(prev - this.fanSpeed) ? curr : prev);
    log(`setFanSpeed: (closest: ${closest})`);

    // Get the closest temperature's hex data
    const hexData = data[`temperature${closest}`];

    sendData(host, swingToggle, callback, log);
  }

  getServices () {
    const services = super.getServices();
    const { data, name } = this;
    const { on, off, swingToggle } = data;

    const service = new Service.Fanv2(name);
    this.addNameService(service);

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.Active,
      propertyName: 'switchState',
      onHex: on,
      offHex: off
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.SwingMode,
      propertyName: 'swingMode',
      onHex: swingToggle,
      offHex: swingToggle
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.RotationSpeed,
      propertyName: 'fanSpeed',
      setValuePromise: this.setFanSpeed
    });

    services.push(service);

    return services;
  }
}

module.exports = FanAccessory;
<|endoftext|>


<commit_before>var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var async = require('async');
var mkdirp = require('mkdirp');
var childProcess = require('child_process');
var util = require('util');
var sha1 = require('sha1');

module.exports = function(animeData, episodesData, opts, callback) {
  async.eachSeries(episodesData, function onEpisode(episode, nextEpisode) {
    var titleForFilename = animeData.anime.title;
    titleForFilename = titleForFilename.split('/').pop();
    titleForFilename = _.trim(titleForFilename);
    titleForFilename = titleForFilename.replace(/[\/\\:\*\?"<>\|]/g, '-');
    var fileName = titleForFilename + ' - ' + _.padLeft(episode.index + 1, 3, '0') + episode.typeDescription + '.mp4';
    var destDir = path.join(__dirname, 'downloads');
    if (opts['downloads-dest']) {
      destDir = opts['downloads-dest'];
    }
    var filePath = path.join(destDir, titleForFilename);
    if (!fs.existsSync(filePath)) {
      mkdirp.sync(filePath);
    }
    if (fs.existsSync(path.join(filePath, fileName))) {
      console.log('Episode %d exists, skipping...', episode.index + 1);
      return nextEpisode();
    }
    console.log('Downloading episode %d...', episode.index + 1);
    var tempFileName = sha1(fileName);
    var axelBinaryPath = 'axel';
    if (/^win/.test(process.platform)) {
      axelBinaryPath = path.join(__dirname, 'bin', 'windows', 'axel.exe');
    }
    var axel = childProcess.spawn(axelBinaryPath, ['-a', '-n 8', '-o' + tempFileName, episode.url], {cwd: filePath});
    axel.stdout.on('data', function(data) {
      process.stdout.write(data);
    });
    axel.stderr.on('data', function(data) {
      console.error(data);
    });
    axel.on('error', function(err) {
      console.error('Error while downloading episode %d: %s', episode.index + 1, err.message);
      process.exit(1);
    });
    axel.on('close', function(code) {
      if (code) {
        console.error('Error while downloading episode %d.', episode.index + 1);
        process.exit(1);
      }
      console.log('Episode %d download done.', episode.index + 1);
      fs.rename(path.join(filePath, tempFileName), path.join(filePath, fileName));
      nextEpisode();
    });

    //var len;
    //var cur = 0;
    //var downloadStatus = _.throttle(function() {
    //  process.stdout.write("Downloading episode " + (episode.index + 1).toString() + " - " + (100.0 * cur / len).toFixed(2) + "% - " + filesize(cur).human() + "          \r");
    //}, 300);
    //request(episode.url)
    //  .on('response', function(response) {
    //    len = parseInt(response.headers['content-length'], 10);
    //  })
    //  .on('data', function(chunk) {
    //    cur += chunk.length;
    //    downloadStatus();
    //  })
    //  .on('end', function() {
    //    console.log('\nEpisode %d download done.', episode.index + 1);
    //    nextEpisode();
    //  })
    //  .on('error', function (err) {
    //    console.log('\nError when downloading file %s: %s', episode.url, err.message);
    //    process.exit(1);
    //  })
    //  .pipe(fs.createWriteStream(path.join(filePath, fileName)));

  }, callback);
};<commit_msg>Probably improved Axel output on Windows.<commit_after>var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var async = require('async');
var mkdirp = require('mkdirp');
var childProcess = require('child_process');
var util = require('util');
var sha1 = require('sha1');

module.exports = function(animeData, episodesData, opts, callback) {
  async.eachSeries(episodesData, function onEpisode(episode, nextEpisode) {
    var titleForFilename = animeData.anime.title;
    titleForFilename = titleForFilename.split('/').pop();
    titleForFilename = _.trim(titleForFilename);
    titleForFilename = titleForFilename.replace(/[\/\\:\*\?"<>\|]/g, '-');
    var fileName = titleForFilename + ' - ' + _.padLeft(episode.index + 1, 3, '0') + episode.typeDescription + '.mp4';
    var destDir = path.join(__dirname, 'downloads');
    if (opts['downloads-dest']) {
      destDir = opts['downloads-dest'];
    }
    var filePath = path.join(destDir, titleForFilename);
    if (!fs.existsSync(filePath)) {
      mkdirp.sync(filePath);
    }
    if (fs.existsSync(path.join(filePath, fileName))) {
      console.log('Episode %d exists, skipping...', episode.index + 1);
      return nextEpisode();
    }
    console.log('Downloading episode %d...', episode.index + 1);
    var tempFileName = sha1(fileName);
    var axelBinaryPath = 'axel';
    var isWindows = /^win/.test(process.platform);
    if (isWindows) {
      axelBinaryPath = path.join(__dirname, 'bin', 'windows', 'axel.exe');
    }
    var axel = childProcess.spawn(axelBinaryPath, ['-a', '-n 8', '-o' + tempFileName, episode.url], {cwd: filePath});
    axel.stdout.on('data', function(data) {
      if (isWindows) {
        data = data.replace(/\r/g, '\033[0G');
      }
      process.stdout.write(data);
    });
    axel.stderr.on('data', function(data) {
      console.error(data);
    });
    axel.on('error', function(err) {
      console.error('Error while downloading episode %d: %s', episode.index + 1, err.message);
      process.exit(1);
    });
    axel.on('close', function(code) {
      if (code) {
        console.error('Error while downloading episode %d.', episode.index + 1);
        process.exit(1);
      }
      console.log('Episode %d download done.', episode.index + 1);
      fs.rename(path.join(filePath, tempFileName), path.join(filePath, fileName));
      nextEpisode();
    });

    //var len;
    //var cur = 0;
    //var downloadStatus = _.throttle(function() {
    //  process.stdout.write("Downloading episode " + (episode.index + 1).toString() + " - " + (100.0 * cur / len).toFixed(2) + "% - " + filesize(cur).human() + "          \r");
    //}, 300);
    //request(episode.url)
    //  .on('response', function(response) {
    //    len = parseInt(response.headers['content-length'], 10);
    //  })
    //  .on('data', function(chunk) {
    //    cur += chunk.length;
    //    downloadStatus();
    //  })
    //  .on('end', function() {
    //    console.log('\nEpisode %d download done.', episode.index + 1);
    //    nextEpisode();
    //  })
    //  .on('error', function (err) {
    //    console.log('\nError when downloading file %s: %s', episode.url, err.message);
    //    process.exit(1);
    //  })
    //  .pipe(fs.createWriteStream(path.join(filePath, fileName)));

  }, callback);
};<|endoftext|>


<commit_before>document.addEventListener("DOMContentLoaded", function () {
    "use strict";
    initialSetup();
    reload();
});

var emailGitHubIssuesClassName = "emailGitHubIssues";

function initialSetup() {
    var s = document.createElement('script');
    s.src = chrome.extension.getURL('scripts/injectedcode.js');
    s.onload = function () {
        this.parentNode.removeChild(this);
    };

    (document.head || document.documentElement).appendChild(s);

    document.addEventListener('_pjax:end', function () {
        reload();
    }, false);
}

function reload() {
    $('.' + emailGitHubIssuesClassName).remove();

    addIndividualIssueButton();
    addButtonsToIssuesList();

    openJenkinsDetailsInNewTab();
    makeBuildStatusWindowsBig();

    addTestFailureButtonsAndDescriptions();
    addJenkinsTestRunTimes();
}

function addIndividualIssueButton() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["emailIssue"] }, function (response) {
        if (response.data["emailIssue"]) {
            var titleElement = document.getElementsByClassName("js-issue-title")[0]

            if (typeof titleElement !== 'undefined') {
                var button = document.createElement("input");
                button.setAttribute("type", "button");
                button.setAttribute("value", "Email Issue");
                button.setAttribute("name", "buttonname");
                var issueNumber = document.getElementsByClassName("gh-header-number")[0].innerHTML.substring(1);
                var issueTitle = document.getElementsByClassName("js-issue-title")[0].innerHTML;
                button.onclick = (function () {
                    var currentTitle = issueTitle;
                    var currentNumber = issueNumber;
                    return function () {
                        sendmail(currentNumber, currentTitle);
                    };
                });

                button.className = "btn " + emailGitHubIssuesClassName;
                titleElement.parentNode.insertBefore(button, document.getElementsByClassName("js-issue-title")[0].parentNode.firstChild);
            }
        }
    });
}

// TODO: Only scrape once between this and addTestFailureButtonsAndDescriptions
function addJenkinsTestRunTimes() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["jenkinsShowRunTime"] }, function (response) {
        if (response.data["jenkinsShowRunTime"]) {

            var testRuns = document.getElementsByClassName("build-status-item");
            for (var i = 0; i < testRuns.length; i++) {
                var run = testRuns[i];
                var detailsLink = run.getElementsByClassName("build-status-details")[0];
                if (typeof detailsLink === 'undefined') {
                    continue;
                }

                var textToUpdate = run.getElementsByClassName("text-muted")[0];

                var loading = document.createElement("img");
                var imgUrl = chrome.extension.getURL("images/loading.gif");
                loading.src = imgUrl;
                var specificClassName = emailGitHubIssuesClassName + "_TestRunTime_" + i;
                loading.className = emailGitHubIssuesClassName + " " + specificClassName;
                textToUpdate.appendChild(loading);

                (function (_run, _url, _specificClassName) {
                    chrome.runtime.sendMessage({
                        method: 'GET',
                        action: 'xhttp',
                        url: _url,
                        data: ''
                    }, function (responseText) {
                        var parser = new DOMParser();
                        var doc = parser.parseFromString(responseText, "text/html");
                        var header = doc.getElementsByClassName("build-caption page-headline")[0];
                        if (typeof header === "undefined") {
                          $('.' + _specificClassName).remove();
                          return;
                        }

                        var timestamp = header.innerText.split("(")[1].split(")")[0];
                        var timestampDisplay = " [Run on " + timestamp + ". "

                        // TODO: time zones?
                        var date = new Date(Date.parse(timestamp));
                        var currentdate = new Date();

                        var delta = currentdate - date;
                        var dayCount = delta / (1000 * 60 * 60 * 24);
                        var minuteCount = delta / (1000 * 60);

                        var backgroundColor = "#000000";
                        var timeAgo = Math.round(dayCount * 10) / 10 + " days ago";

                        if (dayCount <= 2) { backgroundColor = "#AAFFAA"; } // green
                        else if (dayCount <= 5) { backgroundColor = "#FFC85A"; } // yellow
                        else { backgroundColor = "#FFAAAA"; } // red

			$('.' + _specificClassName).remove();

                        var textToUpdate = _run.getElementsByClassName("text-muted")[0];

                        var span = document.createElement("span");
                        span.innerHTML = timestampDisplay;
                        span.className = emailGitHubIssuesClassName;
                        textToUpdate.appendChild(span);

                        var span2 = document.createElement("span");
                        span2.innerHTML = "(" + timeAgo + ")";
                        span2.style.backgroundColor = backgroundColor;
                        span2.setAttribute("title", "Green: < 2 days\nYellow: 2 to 5 days\nRed: > 5 days");
                        span2.className = emailGitHubIssuesClassName;
                        textToUpdate.appendChild(span2);

                        var span3 = document.createElement("span");
                        span3.innerHTML = "]";
                        span3.className = emailGitHubIssuesClassName;
                        textToUpdate.appendChild(span3);
                    });
                })(run, detailsLink.href, specificClassName);
            }
        }
    });
}

function addTestFailureButtonsAndDescriptions() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["jenkinsShowBugFilingButton", "jenkinsShowFailureIndications", "jenkinsShowTestFailures"] }, function (response) {
        if (response.data["jenkinsShowBugFilingButton"] || response.data["jenkinsShowFailureIndications"]) {
                var testFailures = document.getElementsByClassName("octicon-x build-status-icon");

                for (var i = 0; i < testFailures.length; i++) {
                    var isDropdown = false;

                    var ancestor = testFailures[i];
                    while ((ancestor = ancestor.parentElement) != null) {
                        if (ancestor.classList.contains("dropdown-menu")) {
                            isDropdown = true;
                            break;
                        }
                    }

                    if (isDropdown) {
                        continue;
                    }

                    var testFailure = testFailures[i];
                    var testFailUrl = testFailure.parentNode.getElementsByClassName("build-status-details")[0].href;

                    var loading = document.createElement("img");
                    var imgUrl = chrome.extension.getURL("images/loading.gif");
                    loading.src = imgUrl;
                    var specificClassName = emailGitHubIssuesClassName + "_TestFailures_" + i;
                    loading.className = emailGitHubIssuesClassName + " " + specificClassName;
                    testFailure.parentNode.insertBefore(loading, testFailure.parentNode.firstChild);

                    (function (_testFailure, _testFailUrl, _specificClassName) {
                        chrome.runtime.sendMessage({
                            method: 'GET',
                            action: 'xhttp',
                            url: _testFailUrl,
                            data: ''
                        }, function (responseText) {
                            var parser = new DOMParser();
                            var doc = parser.parseFromString(responseText, "text/html");
                            var h2elements = doc.getElementsByTagName("h2");
                            var aelements = doc.getElementsByTagName("a");

                            var url = window.location.href;
                            var urlParts = url.split("/");
                            var pullNumber = urlParts[urlParts.length - 1];
                            var pullTitle = document.getElementsByClassName("js-issue-title")[0].innerText.trim();
                            var pullAuthor = document.getElementsByClassName("pull-header-username")[0].innerText.trim();

                            var issueBody = "PR: [#" + pullNumber + "](" + url + ") *" + pullTitle + "* by @" + pullAuthor + "\r\n";
                            issueBody = issueBody + "Failure: " + _testFailUrl + "\r\n\r\n";
                            var htmlDescription = "";
                            var issueDescription = "<description>";

                            if (response.data["jenkinsShowTestFailures"]) {
                                for (var i = 0; i < aelements.length; i++) {
                                    var aelement = aelements[i];
                                    if (aelement.innerText == "Test Result" && aelement.parentNode.tagName == "TD") {
                                        var unitTestFailures = aelement.parentNode.getElementsByTagName("li");

                                        if (unitTestFailures.length > 0) {
                                            if (unitTestFailures.length <= 10) {
                                                htmlDescription = htmlDescription + "" + unitTestFailures.length + " Test Failures:
";
                                                issueBody = issueBody + "**" + unitTestFailures.length + " Test Failures:**\r\n";
                                            }
                                            else {
                                                htmlDescription = htmlDescription + "" + unitTestFailures.length + " Test Failures: (showing first 10)
";
                                                issueBody = issueBody + "**" + unitTestFailures.length + " Test Failures:** (showing first 10)\r\n";
                                            }
                                        }

                                        for (var j = 0; j < unitTestFailures.length && j < 10; j++) {
                                            var unitTestFailure = unitTestFailures[j];
                                            htmlDescription = htmlDescription + "    " + unitTestFailure.innerText + "
";
                                            issueBody = issueBody + unitTestFailure.innerText + "\r\n";
                                        }

                                        htmlDescription = htmlDescription + "
";
                                        issueBody = issueBody + "\r\n";
                                    }
                                }
                            }

                            var count = 1;
                            for (var i = 0; i < h2elements.length; i++) {
                                var h2 = h2elements[i];

                                if (h2.innerHTML == "HTTP ERROR 404") {
                                    htmlDescription = htmlDescription + "404: Build details page could not be found.";
                                    issueDescription = "404: Build details page could not be found.";
                                }

                                if (h2.innerHTML == "Identified problems") {
                                    var nodeWithErrorSiblings = h2.parentNode.parentNode;
                                    var errorRow = nodeWithErrorSiblings;
                                    while ((errorRow = errorRow.nextSibling) != null) {
                                        if (count > 1) {
                                            issueBody = issueBody + "\r\n\r\n";
                                            htmlDescription = htmlDescription + "

";
                                        }

                                        var failureTitle = "";
                                        var failureDescription = "";

                                        var h3s = errorRow.getElementsByTagName("h3");
                                        var h4s = errorRow.getElementsByTagName("h4");
                                        if (h3s.length > 0) {
                                            failureTitle = h3s[0].innerHTML.split("
 0) {
                                            failureTitle = h4s[0].innerHTML.trim();
                                            failureDescription = h4s[1].innerHTML.trim();
                                        }

                                        if (count == 1) {
                                          issueDescription = failureTitle;
                                        }

                                        issueBody = issueBody + "**Issue " + count + ": " + failureTitle + "**\r\n";
                                        issueBody = issueBody + failureDescription;
                                        htmlDescription = htmlDescription + "Issue " + count + ": " + failureTitle + "
" + failureDescription;

                                        count++;
                                    }
                                }
                            }

                            if (count > 2) {
                              issueDescription = issueDescription + " (+" + (count - 2) + " more)";
                            }

                            if (count == 1) {
                                // we failed to find the failure, or there was none.
                                // should we add special handling here?
                            }

                            var testQueueName = _testFailure.parentNode.getElementsByClassName("text-emphasized")[0].innerText.trim();
                            var issueTitle = "[Test Failure] " + issueDescription + " in " + testQueueName + " on PR #" + pullNumber;

                            var url = "https://github.com/dotnet/roslyn/issues/new?title=" + encodeURIComponent(issueTitle) + "&body=" + encodeURIComponent(issueBody) + "&labels[]=Area-Infrastructure&labels[]=Contributor%20Pain";

                            $('.' + _specificClassName).remove();

                            if (response.data["jenkinsShowBugFilingButton"]) {
                                var button = document.createElement("input");
                                button.setAttribute("type", "button");
                                button.setAttribute("value", "Create Issue");
                                button.setAttribute("name", "buttonname");
                                button.onclick = (function () {
                                    var thisUrl = url;
                                    return function () {
                                        window.open(thisUrl);
                                    };
                                })();

                                button.className = "btn btn-sm " + emailGitHubIssuesClassName;
                                button.style.margin = "0px 0px 3px 0px";

                                _testFailure.parentNode.insertBefore(button, _testFailure.parentNode.firstChild);
                            }

                            if (response.data["jenkinsShowFailureIndications"]) {
                                var div = document.createElement("div");
                                div.innerHTML = htmlDescription;
                                div.style.backgroundColor = "#FFAAAA";
                                div.className = emailGitHubIssuesClassName;
                                _testFailure.parentNode.appendChild(div);
                            }
                        });
                    })(testFailure, testFailUrl, specificClassName);
                }
        }
    });
}

function makeBuildStatusWindowsBig() {
    var lists = document.getElementsByClassName("build-statuses-list");
    for (var i = 0; i < lists.length; i++) {
        lists[i].style.maxHeight = "5000px";
    }
}

function addButtonsToIssuesList() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["emailIssuesList"] }, function (response) {
        if (response.data["emailIssuesList"]) {
            var issuesList = document.getElementsByClassName("table-list-issues")[0];

            if (typeof issuesList !== 'undefined') {
                var issues = issuesList.children;

                for (var i = 0; i < issues.length; i++) {
                    var issue = issues[i];
                    var title = issue.getElementsByClassName("issue-title")[0];

                    var button = document.createElement("input");
                    button.setAttribute("type", "button");
                    button.setAttribute("value", "Email Issue");
                    button.setAttribute("name", "buttonname");

                    var urlParts = title.getElementsByClassName("issue-title-link")[0].href.split("/");
                    var issueNumber = urlParts[urlParts.length - 1];
                    var issueTitle = title.getElementsByClassName("issue-title-link")[0].innerHTML;

                    button.onclick = (function () {
                        var currentTitle = issueTitle;
                        var currentNumber = issueNumber;
                        return function () {
                            sendmail(currentNumber, currentTitle);
                        };
                    })();
                    button.className = "btn btn-sm " + emailGitHubIssuesClassName;

                    title.insertBefore(button, title.firstChild);
                }
            }
        }
    });
}

function openJenkinsDetailsInNewTab() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["jenkinsOpenDetailsLinksInNewTab"] }, function (response) {
        if (response.data["jenkinsOpenDetailsLinksInNewTab"]) {
            var detailsLinks = document.getElementsByClassName("build-status-details");
            for (var i = 0; i < detailsLinks.length; i++) {
                var detailsLink = detailsLinks[i];
                detailsLink.target = "_blank";
            }
        }
    });
}

function sendmail(issueNumber, issueTitle) {
    issueTitle = issueTitle.trim();
    issueNumber = issueNumber.trim();

    var baseUrl = document.getElementsByClassName("entry-title public")[0].getElementsByTagName('strong')[0].getElementsByTagName('a')[0].href + "/issues/";

    var owner = document.getElementsByClassName("entry-title public")[0].getElementsByClassName("author")[0].getElementsByTagName("span")[0].innerHTML;
    var repo = document.getElementsByClassName("entry-title public")[0].getElementsByTagName("strong")[0].getElementsByTagName("a")[0].innerHTML;

    var subject = owner + "/" + repo + " #" + issueNumber + ": " + issueTitle;

    var body = baseUrl + issueNumber + "\r\n\r\n"; // TODO: Assigned to, etc.

    window.location.href = "mailto:?subject=" + encodeURI(subject) + "&body=" + encodeURI(body);
}<commit_msg>Distinguish between Issues & Pull Requests<commit_after>document.addEventListener("DOMContentLoaded", function () {
    "use strict";
    initialSetup();
    reload();
});

var emailGitHubIssuesClassName = "emailGitHubIssues";

function initialSetup() {
    var s = document.createElement('script');
    s.src = chrome.extension.getURL('scripts/injectedcode.js');
    s.onload = function () {
        this.parentNode.removeChild(this);
    };

    (document.head || document.documentElement).appendChild(s);

    document.addEventListener('_pjax:end', function () {
        reload();
    }, false);
}

function reload() {
    $('.' + emailGitHubIssuesClassName).remove();

    addIndividualIssueButton();
    addButtonsToIssuesList();

    openJenkinsDetailsInNewTab();
    makeBuildStatusWindowsBig();

    addTestFailureButtonsAndDescriptions();
    addJenkinsTestRunTimes();
}

function addIndividualIssueButton() {
    var urlParts = stripTrailingSlash(window.location.href.substr(0, window.location.href.indexOf('#'))).split("/");
    var isPull = urlParts[urlParts.length - 2] == "pull";

    chrome.runtime.sendMessage({ method: "getSettings", keys: ["emailIssue", "emailPullRequest"] }, function (response) {
        var titleElement = document.getElementsByClassName("js-issue-title")[0]
        if (typeof titleElement !== 'undefined' && ((isPull && response.data["emailPullRequest"]) || (!isPull && response.data["emailIssue"]))) {
            var button = document.createElement("input");
            button.setAttribute("type", "button");
            button.setAttribute("value", isPull ? "Email PR" : "Email Issue");
            button.setAttribute("name", "buttonname");
            var issueNumber = document.getElementsByClassName("gh-header-number")[0].innerHTML.substring(1);
            var issueTitle = document.getElementsByClassName("js-issue-title")[0].innerHTML;
            button.onclick = (function () {
                var currentTitle = issueTitle;
                var currentNumber = issueNumber;
                var currentIsPull = isPull;
                return function () {
                    sendmail(currentNumber, currentTitle, currentIsPull);
                };
            })();

            button.className = "btn " + emailGitHubIssuesClassName;
            titleElement.parentNode.insertBefore(button, document.getElementsByClassName("js-issue-title")[0].parentNode.firstChild);
        }
    });
}

// TODO: Only scrape once between this and addTestFailureButtonsAndDescriptions
function addJenkinsTestRunTimes() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["jenkinsShowRunTime"] }, function (response) {
        if (response.data["jenkinsShowRunTime"]) {

            var testRuns = document.getElementsByClassName("build-status-item");
            for (var i = 0; i < testRuns.length; i++) {
                var run = testRuns[i];
                var detailsLink = run.getElementsByClassName("build-status-details")[0];
                if (typeof detailsLink === 'undefined') {
                    continue;
                }

                var textToUpdate = run.getElementsByClassName("text-muted")[0];

                var loading = document.createElement("img");
                var imgUrl = chrome.extension.getURL("images/loading.gif");
                loading.src = imgUrl;
                var specificClassName = emailGitHubIssuesClassName + "_TestRunTime_" + i;
                loading.className = emailGitHubIssuesClassName + " " + specificClassName;
                textToUpdate.appendChild(loading);

                (function (_run, _url, _specificClassName) {
                    chrome.runtime.sendMessage({
                        method: 'GET',
                        action: 'xhttp',
                        url: _url,
                        data: ''
                    }, function (responseText) {
                        var parser = new DOMParser();
                        var doc = parser.parseFromString(responseText, "text/html");
                        var header = doc.getElementsByClassName("build-caption page-headline")[0];
                        if (typeof header === "undefined") {
                          $('.' + _specificClassName).remove();
                          return;
                        }

                        var timestamp = header.innerText.split("(")[1].split(")")[0];
                        var timestampDisplay = " [Run on " + timestamp + ". "

                        // TODO: time zones?
                        var date = new Date(Date.parse(timestamp));
                        var currentdate = new Date();

                        var delta = currentdate - date;
                        var dayCount = delta / (1000 * 60 * 60 * 24);
                        var minuteCount = delta / (1000 * 60);

                        var backgroundColor = "#000000";
                        var timeAgo = Math.round(dayCount * 10) / 10 + " days ago";

                        if (dayCount <= 2) { backgroundColor = "#AAFFAA"; } // green
                        else if (dayCount <= 5) { backgroundColor = "#FFC85A"; } // yellow
                        else { backgroundColor = "#FFAAAA"; } // red

			$('.' + _specificClassName).remove();

                        var textToUpdate = _run.getElementsByClassName("text-muted")[0];

                        var span = document.createElement("span");
                        span.innerHTML = timestampDisplay;
                        span.className = emailGitHubIssuesClassName;
                        textToUpdate.appendChild(span);

                        var span2 = document.createElement("span");
                        span2.innerHTML = "(" + timeAgo + ")";
                        span2.style.backgroundColor = backgroundColor;
                        span2.setAttribute("title", "Green: < 2 days\nYellow: 2 to 5 days\nRed: > 5 days");
                        span2.className = emailGitHubIssuesClassName;
                        textToUpdate.appendChild(span2);

                        var span3 = document.createElement("span");
                        span3.innerHTML = "]";
                        span3.className = emailGitHubIssuesClassName;
                        textToUpdate.appendChild(span3);
                    });
                })(run, detailsLink.href, specificClassName);
            }
        }
    });
}

function addTestFailureButtonsAndDescriptions() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["jenkinsShowBugFilingButton", "jenkinsShowFailureIndications", "jenkinsShowTestFailures"] }, function (response) {
        if (response.data["jenkinsShowBugFilingButton"] || response.data["jenkinsShowFailureIndications"]) {
                var testFailures = document.getElementsByClassName("octicon-x build-status-icon");

                for (var i = 0; i < testFailures.length; i++) {
                    var isDropdown = false;

                    var ancestor = testFailures[i];
                    while ((ancestor = ancestor.parentElement) != null) {
                        if (ancestor.classList.contains("dropdown-menu")) {
                            isDropdown = true;
                            break;
                        }
                    }

                    if (isDropdown) {
                        continue;
                    }

                    var testFailure = testFailures[i];
                    var testFailUrl = testFailure.parentNode.getElementsByClassName("build-status-details")[0].href;

                    var loading = document.createElement("img");
                    var imgUrl = chrome.extension.getURL("images/loading.gif");
                    loading.src = imgUrl;
                    var specificClassName = emailGitHubIssuesClassName + "_TestFailures_" + i;
                    loading.className = emailGitHubIssuesClassName + " " + specificClassName;
                    testFailure.parentNode.insertBefore(loading, testFailure.parentNode.firstChild);

                    (function (_testFailure, _testFailUrl, _specificClassName) {
                        chrome.runtime.sendMessage({
                            method: 'GET',
                            action: 'xhttp',
                            url: _testFailUrl,
                            data: ''
                        }, function (responseText) {
                            var parser = new DOMParser();
                            var doc = parser.parseFromString(responseText, "text/html");
                            var h2elements = doc.getElementsByTagName("h2");
                            var aelements = doc.getElementsByTagName("a");

                            var url = window.location.href;
                            var urlParts = url.split("/");
                            var pullNumber = urlParts[urlParts.length - 1];
                            var pullTitle = document.getElementsByClassName("js-issue-title")[0].innerText.trim();
                            var pullAuthor = document.getElementsByClassName("pull-header-username")[0].innerText.trim();

                            var issueBody = "PR: [#" + pullNumber + "](" + url + ") *" + pullTitle + "* by @" + pullAuthor + "\r\n";
                            issueBody = issueBody + "Failure: " + _testFailUrl + "\r\n\r\n";
                            var htmlDescription = "";
                            var issueDescription = "<description>";

                            if (response.data["jenkinsShowTestFailures"]) {
                                for (var i = 0; i < aelements.length; i++) {
                                    var aelement = aelements[i];
                                    if (aelement.innerText == "Test Result" && aelement.parentNode.tagName == "TD") {
                                        var unitTestFailures = aelement.parentNode.getElementsByTagName("li");

                                        if (unitTestFailures.length > 0) {
                                            if (unitTestFailures.length <= 10) {
                                                htmlDescription = htmlDescription + "" + unitTestFailures.length + " Test Failures:
";
                                                issueBody = issueBody + "**" + unitTestFailures.length + " Test Failures:**\r\n";
                                            }
                                            else {
                                                htmlDescription = htmlDescription + "" + unitTestFailures.length + " Test Failures: (showing first 10)
";
                                                issueBody = issueBody + "**" + unitTestFailures.length + " Test Failures:** (showing first 10)\r\n";
                                            }
                                        }

                                        for (var j = 0; j < unitTestFailures.length && j < 10; j++) {
                                            var unitTestFailure = unitTestFailures[j];
                                            htmlDescription = htmlDescription + "    " + unitTestFailure.innerText + "
";
                                            issueBody = issueBody + unitTestFailure.innerText + "\r\n";
                                        }

                                        htmlDescription = htmlDescription + "
";
                                        issueBody = issueBody + "\r\n";
                                    }
                                }
                            }

                            var count = 1;
                            for (var i = 0; i < h2elements.length; i++) {
                                var h2 = h2elements[i];

                                if (h2.innerHTML == "HTTP ERROR 404") {
                                    htmlDescription = htmlDescription + "404: Build details page could not be found.";
                                    issueDescription = "404: Build details page could not be found.";
                                }

                                if (h2.innerHTML == "Identified problems") {
                                    var nodeWithErrorSiblings = h2.parentNode.parentNode;
                                    var errorRow = nodeWithErrorSiblings;
                                    while ((errorRow = errorRow.nextSibling) != null) {
                                        if (count > 1) {
                                            issueBody = issueBody + "\r\n\r\n";
                                            htmlDescription = htmlDescription + "

";
                                        }

                                        var failureTitle = "";
                                        var failureDescription = "";

                                        var h3s = errorRow.getElementsByTagName("h3");
                                        var h4s = errorRow.getElementsByTagName("h4");
                                        if (h3s.length > 0) {
                                            failureTitle = h3s[0].innerHTML.split("
 0) {
                                            failureTitle = h4s[0].innerHTML.trim();
                                            failureDescription = h4s[1].innerHTML.trim();
                                        }

                                        if (count == 1) {
                                          issueDescription = failureTitle;
                                        }

                                        issueBody = issueBody + "**Issue " + count + ": " + failureTitle + "**\r\n";
                                        issueBody = issueBody + failureDescription;
                                        htmlDescription = htmlDescription + "Issue " + count + ": " + failureTitle + "
" + failureDescription;

                                        count++;
                                    }
                                }
                            }

                            if (count > 2) {
                              issueDescription = issueDescription + " (+" + (count - 2) + " more)";
                            }

                            if (count == 1) {
                                // we failed to find the failure, or there was none.
                                // should we add special handling here?
                            }

                            var testQueueName = _testFailure.parentNode.getElementsByClassName("text-emphasized")[0].innerText.trim();
                            var issueTitle = "[Test Failure] " + issueDescription + " in " + testQueueName + " on PR #" + pullNumber;

                            var url = "https://github.com/dotnet/roslyn/issues/new?title=" + encodeURIComponent(issueTitle) + "&body=" + encodeURIComponent(issueBody) + "&labels[]=Area-Infrastructure&labels[]=Contributor%20Pain";

                            $('.' + _specificClassName).remove();

                            if (response.data["jenkinsShowBugFilingButton"]) {
                                var button = document.createElement("input");
                                button.setAttribute("type", "button");
                                button.setAttribute("value", "Create Issue");
                                button.setAttribute("name", "buttonname");
                                button.onclick = (function () {
                                    var thisUrl = url;
                                    return function () {
                                        window.open(thisUrl);
                                    };
                                })();

                                button.className = "btn btn-sm " + emailGitHubIssuesClassName;
                                button.style.margin = "0px 0px 3px 0px";

                                _testFailure.parentNode.insertBefore(button, _testFailure.parentNode.firstChild);
                            }

                            if (response.data["jenkinsShowFailureIndications"]) {
                                var div = document.createElement("div");
                                div.innerHTML = htmlDescription;
                                div.style.backgroundColor = "#FFAAAA";
                                div.className = emailGitHubIssuesClassName;
                                _testFailure.parentNode.appendChild(div);
                            }
                        });
                    })(testFailure, testFailUrl, specificClassName);
                }
        }
    });
}

function makeBuildStatusWindowsBig() {
    var lists = document.getElementsByClassName("build-statuses-list");
    for (var i = 0; i < lists.length; i++) {
        lists[i].style.maxHeight = "5000px";
    }
}

function stripTrailingSlash(str) {
    if(str.substr(-1) === '/') {
        return str.substr(0, str.length - 1);
    }
    return str;
}

function addButtonsToIssuesList() {
    var url = stripTrailingSlash(window.location.href);
    var urlParts = url.split("/");

    var isPull = false;
    if (urlParts[urlParts.length - 1] == "pulls") {
      isPull = true;
    }

    chrome.runtime.sendMessage({ method: "getSettings", keys: ["emailIssuesList", "emailPullRequestList"] }, function (response) {
        var issuesList = document.getElementsByClassName("table-list-issues")[0];
        if (typeof issuesList !== 'undefined' && ((isPull && response.data["emailPullRequestList"]) || (!isPull && response.data["emailIssuesList"]))) {
            var issues = issuesList.children;

            for (var i = 0; i < issues.length; i++) {
                var issue = issues[i];
                var title = issue.getElementsByClassName("issue-title")[0];
                var button = document.createElement("input");
                button.setAttribute("type", "button");
                button.setAttribute("value", isPull ? "Email PR" : "Email Issue");
                button.setAttribute("name", "buttonname");

                var urlParts = title.getElementsByClassName("issue-title-link")[0].href.split("/");
                var issueNumber = urlParts[urlParts.length - 1];
                var issueTitle = title.getElementsByClassName("issue-title-link")[0].innerHTML;

                button.onclick = (function () {
                    var currentTitle = issueTitle;
                    var currentNumber = issueNumber;
                    return function () {
                        sendmail(currentNumber, currentTitle, isPull);
                    };
                })();
                button.className = "btn btn-sm " + emailGitHubIssuesClassName;

                title.insertBefore(button, title.firstChild);
            }
        }
    });
}

function openJenkinsDetailsInNewTab() {
    chrome.runtime.sendMessage({ method: "getSettings", keys: ["jenkinsOpenDetailsLinksInNewTab"] }, function (response) {
        if (response.data["jenkinsOpenDetailsLinksInNewTab"]) {
            var detailsLinks = document.getElementsByClassName("build-status-details");
            for (var i = 0; i < detailsLinks.length; i++) {
                var detailsLink = detailsLinks[i];
                detailsLink.target = "_blank";
            }
        }
    });
}

function sendmail(issueNumber, issueTitle, isPull) {
    issueTitle = issueTitle.trim();
    issueNumber = issueNumber.trim();

    var baseUrl = document.getElementsByClassName("entry-title public")[0].getElementsByTagName('strong')[0].getElementsByTagName('a')[0].href;
    var kind = isPull ? "PR" : "Issue";
    baseUrl = baseUrl + (isPull ? "/pull/" : "/issues/");

    var owner = document.getElementsByClassName("entry-title public")[0].getElementsByClassName("author")[0].getElementsByTagName("span")[0].innerHTML;
    var repo = document.getElementsByClassName("entry-title public")[0].getElementsByTagName("strong")[0].getElementsByTagName("a")[0].innerHTML;

    var subject = owner + "/" + repo + " " + kind + " #" + issueNumber + ": " + issueTitle;

    var body = baseUrl + issueNumber + "\r\n\r\n"; // TODO: Assigned to, etc.

    window.location.href = "mailto:?subject=" + encodeURI(subject) + "&body=" + encodeURI(body);
}<|endoftext|>


<commit_before>
  function showpoint(point) {
    var latdegrees = Math.abs(point.latitude);
    var latdegreepart = Math.floor(latdegrees);
    var latminutepart = 60 * (latdegrees - latdegreepart);
    var latdir = (point.lat > 0) ? "N" : "S";
    var lngdegrees = Math.abs(point.longitude);
    var lngdegreepart = Math.floor(lngdegrees);
    var lngminutepart = 60 * (lngdegrees - lngdegreepart);
    var lngdir = (point.lng > 0) ? "E" : "W";
    var retstr = latdegreepart.toString() + "°" + latminutepart.toFixed(3) + "′" + latdir + " " + lngdegreepart.toString() + "°" + lngminutepart.toFixed(3) + "′" + lngdir;
    return retstr;
  }

 function leginfo(start, end) {
    var earthrad = 6378; // km
    var lat1 = start['latitude'] * Math.PI / 180;
    var lat2 = end['latitude'] * Math.PI / 180;
    var lon1 = start['longitude'] * Math.PI / 180;
    var lon2 = end['longitude'] * Math.PI / 180;
    var deltaLat = lat2 - lat1;
    var deltaLon = (end['longitude'] - start['longitude']) * Math.PI / 180;
    var a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = earthrad * c;
    var y = Math.sin(lon2 - lon1) * Math.cos(lat2);
    var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
    var brng = Math.round((360 + Math.atan2(y, x) * 180 / Math.PI) % 360);
    return {
      distance: d,
      bearing: brng
    };
  }
<commit_msg>Correct error where all longitudes are shown as west<commit_after>
  function showpoint(point) {
    var latdegrees = Math.abs(point.latitude);
    var latdegreepart = Math.floor(latdegrees);
    var latminutepart = 60 * (latdegrees - latdegreepart);
    var latdir = (point.lat > 0) ? "N" : "S";
    var lngdegrees = Math.abs(point.longitude);
    var lngdegreepart = Math.floor(lngdegrees);
    var lngminutepart = 60 * (lngdegrees - lngdegreepart);
    var lngdir = (point.longitude > 0) ? "E" : "W";
    var retstr = latdegreepart.toString() + "°" + latminutepart.toFixed(3) + "′" + latdir + " " + lngdegreepart.toString() + "°" + lngminutepart.toFixed(3) + "′" + lngdir;
    return retstr;
  }

 function leginfo(start, end) {
    var earthrad = 6378; // km
    var lat1 = start['latitude'] * Math.PI / 180;
    var lat2 = end['latitude'] * Math.PI / 180;
    var lon1 = start['longitude'] * Math.PI / 180;
    var lon2 = end['longitude'] * Math.PI / 180;
    var deltaLat = lat2 - lat1;
    var deltaLon = (end['longitude'] - start['longitude']) * Math.PI / 180;
    var a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    var d = earthrad * c;
    var y = Math.sin(lon2 - lon1) * Math.cos(lat2);
    var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
    var brng = Math.round((360 + Math.atan2(y, x) * 180 / Math.PI) % 360);
    return {
      distance: d,
      bearing: brng
    };
  }
<|endoftext|>


<commit_before>exports.jsGlobals = "Infinity undefined NaN Object Function Array String Number Boolean RegExp Date Error SyntaxError ReferenceError URIError EvalError RangeError TypeError parseInt parseFloat isNaN isFinite eval encodeURI encodeURIComponent decodeURI decodeURIComponent Math JSON require console exports module Symbol Promise Map WeakMap".split(" ");

exports.browserGlobals = "location Node Element Text Document document XMLDocument HTMLElement HTMLAnchorElement HTMLAreaElement HTMLAudioElement HTMLBaseElement HTMLBodyElement HTMLBRElement HTMLButtonElement HTMLCanvasElement HTMLDataElement HTMLDataListElement HTMLDivElement HTMLDListElement HTMLDocument HTMLEmbedElement HTMLFieldSetElement HTMLFormControlsCollection HTMLFormElement HTMLHeadElement HTMLHeadingElement HTMLHRElement HTMLHtmlElement HTMLIFrameElement HTMLImageElement HTMLInputElement HTMLKeygenElement HTMLLabelElement HTMLLegendElement HTMLLIElement HTMLLinkElement HTMLMapElement HTMLMediaElement HTMLMetaElement HTMLMeterElement HTMLModElement HTMLObjectElement HTMLOListElement HTMLOptGroupElement HTMLOptionElement HTMLOptionsCollection HTMLOutputElement HTMLParagraphElement HTMLParamElement HTMLPreElement HTMLProgressElement HTMLQuoteElement HTMLScriptElement HTMLSelectElement HTMLSourceElement HTMLSpanElement HTMLStyleElement HTMLTableCaptionElement HTMLTableCellElement HTMLTableColElement HTMLTableDataCellElement HTMLTableElement HTMLTableHeaderCellElement HTMLTableRowElement HTMLTableSectionElement HTMLTextAreaElement HTMLTimeElement HTMLTitleElement HTMLTrackElement HTMLUListElement HTMLUnknownElement HTMLVideoElement Attr NodeList HTMLCollection NamedNodeMap DocumentFragment DOMTokenList XPathResult ClientRect Event TouchEvent WheelEvent MouseEvent KeyboardEvent HashChangeEvent ErrorEvent CustomEvent BeforeLoadEvent WebSocket Worker localStorage sessionStorage FileList File Blob FileReader URL Range XMLHttpRequest DOMParser Selection console top parent window opener self devicePixelRatio name closed pageYOffset pageXOffset scrollY scrollX screenTop screenLeft screenY screenX innerWidth innerHeight outerWidth outerHeight frameElement crypto navigator history screen postMessage close blur focus onload onunload onscroll onresize ononline onoffline onmousewheel onmouseup onmouseover onmouseout onmousemove onmousedown onclick ondblclick onmessage onkeyup onkeypress onkeydown oninput onpopstate onhashchange onfocus onblur onerror ondrop ondragstart ondragover ondragleave ondragenter ondragend ondrag oncontextmenu onchange onbeforeunload onabort getSelection alert confirm prompt scrollBy scrollTo scroll setTimeout clearTimeout setInterval clearInterval atob btoa addEventListener removeEventListener dispatchEvent getComputedStyle CanvasRenderingContext2D importScripts MutationObserver indexedDB open requestAnimationFrame cancelAnimationFrame requestIdleCallback cancelIdleCallback customElements performance applicationCache".split(" ");

<commit_msg>Add DOMException to globals<commit_after>exports.jsGlobals = "Infinity undefined NaN Object Function Array String Number Boolean RegExp Date Error SyntaxError ReferenceError URIError EvalError RangeError TypeError parseInt parseFloat isNaN isFinite eval encodeURI encodeURIComponent decodeURI decodeURIComponent Math JSON require console exports module Symbol Promise Map WeakMap".split(" ");

exports.browserGlobals = "location Node Element Text Document document XMLDocument HTMLElement HTMLAnchorElement HTMLAreaElement HTMLAudioElement HTMLBaseElement HTMLBodyElement HTMLBRElement HTMLButtonElement HTMLCanvasElement HTMLDataElement HTMLDataListElement HTMLDivElement HTMLDListElement HTMLDocument HTMLEmbedElement HTMLFieldSetElement HTMLFormControlsCollection HTMLFormElement HTMLHeadElement HTMLHeadingElement HTMLHRElement HTMLHtmlElement HTMLIFrameElement HTMLImageElement HTMLInputElement HTMLKeygenElement HTMLLabelElement HTMLLegendElement HTMLLIElement HTMLLinkElement HTMLMapElement HTMLMediaElement HTMLMetaElement HTMLMeterElement HTMLModElement HTMLObjectElement HTMLOListElement HTMLOptGroupElement HTMLOptionElement HTMLOptionsCollection HTMLOutputElement HTMLParagraphElement HTMLParamElement HTMLPreElement HTMLProgressElement HTMLQuoteElement HTMLScriptElement HTMLSelectElement HTMLSourceElement HTMLSpanElement HTMLStyleElement HTMLTableCaptionElement HTMLTableCellElement HTMLTableColElement HTMLTableDataCellElement HTMLTableElement HTMLTableHeaderCellElement HTMLTableRowElement HTMLTableSectionElement HTMLTextAreaElement HTMLTimeElement HTMLTitleElement HTMLTrackElement HTMLUListElement HTMLUnknownElement HTMLVideoElement Attr NodeList HTMLCollection NamedNodeMap DocumentFragment DOMException DOMTokenList XPathResult ClientRect Event TouchEvent WheelEvent MouseEvent KeyboardEvent HashChangeEvent ErrorEvent CustomEvent BeforeLoadEvent WebSocket Worker localStorage sessionStorage FileList File Blob FileReader URL Range XMLHttpRequest DOMParser Selection console top parent window opener self devicePixelRatio name closed pageYOffset pageXOffset scrollY scrollX screenTop screenLeft screenY screenX innerWidth innerHeight outerWidth outerHeight frameElement crypto navigator history screen postMessage close blur focus onload onunload onscroll onresize ononline onoffline onmousewheel onmouseup onmouseover onmouseout onmousemove onmousedown onclick ondblclick onmessage onkeyup onkeypress onkeydown oninput onpopstate onhashchange onfocus onblur onerror ondrop ondragstart ondragover ondragleave ondragenter ondragend ondrag oncontextmenu onchange onbeforeunload onabort getSelection alert confirm prompt scrollBy scrollTo scroll setTimeout clearTimeout setInterval clearInterval atob btoa addEventListener removeEventListener dispatchEvent getComputedStyle CanvasRenderingContext2D importScripts MutationObserver indexedDB open requestAnimationFrame cancelAnimationFrame requestIdleCallback cancelIdleCallback customElements performance applicationCache".split(" ");

<|endoftext|>