<commit_before>// todo: put this in its own file and require it
// HTML Escape helper utility
var util = (function() {
  // Thanks to Andrea Giammarchi
  var
    reEscape = /[&<>'"]/g,
    reUnescape = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g,
    oEscape = {
      '&': '&',
      '<': '<',
      '>': '>',
      "'": ''',
      '"': '"'
    },
    oUnescape = {
      '&': '&',
      '&': '&',
      '<': '<',
      '<': '<',
      '>': '>',
      '>': '>',
      ''': "'",
      ''': "'",
      '"': '"',
      '"': '"'
    },
    fnEscape = function(m) {
      return oEscape[m];
    },
    fnUnescape = function(m) {
      return oUnescape[m];
    },
    replace = String.prototype.replace;
  return (Object.freeze || Object)({
    escape: function escape(s) {
      return replace.call(s, reEscape, fnEscape);
    },
    unescape: function unescape(s) {
      return replace.call(s, reUnescape, fnUnescape);
    }
  });
}());

// Tagged template function
var html = function(pieces) {
  var result = pieces[0];
  var substitutions = [].slice.call(arguments, 1);
  for (var i = 0; i < substitutions.length; ++i) {
    result += util.escape(substitutions[i]) + pieces[i + 1];
  }
  return result;
};

let Firebase = require('firebase');
let base = new Firebase('https://scorching-inferno-1467.firebaseio.com/');

let auth = base.getAuth();

const setTitle = () => {
  document.getElementById('company-name').innerHTML = JSON.parse(localStorage.punchtime).company.name;
};

// if the localstorage hasn't been set, go to settings
if (!localStorage.punchtime) {
  location.href = 'settings.html';
}

if (auth) {
  console.log('logged in with: '+auth.uid);
  setTitle();
} else {
  console.warn('not logged in');
  location.href = '/login/';
}

let pulses = []; // the graph is made from this object

const addEmployee = (employee,id) => {
  let employeePulses = [];
  for (let pulse in employee.pulses) {
    if (employee.pulses.hasOwnProperty(pulse)) {
      base.child('pulses').child(pulse).once('value',snap=>{
        try {
          let name = employee.name,
              checkin = new Date(parseInt(snap.val().checkin)),
              note = snap.val().note,
              checkout;
          if (snap.val().checkout && snap.val().checkout !== 0) {
            checkout = new Date(parseInt(snap.val().checkout));
          } else {
            checkout = new Date();
          }
          pulses.push([name, note, checkin, checkout]);
          employeePulses.push({
            id: snap.key(),
            latitude: snap.val().latitude,
            longitude: snap.val().longitude,
            checkin: snap.val().checkin,
            checkout: snap.val().checkout,
            note: snap.val().note,
            confirmed: snap.val().confirmed,
          });
          drawChart();
        } catch(e) {
          console.log(`/pulses/${snap.key()} in /users/${employee.name} has a problem.`);
        }
      });
    }
  }

  let image = employee.image || '/src/img/icons/empty.svg',
      name = employee.name,
      status = 'good';

  // todo: async/await the fetching
  for (let i in employeePulses) {
    if (employeePulses.hasOwnProperty(i)) {
      console.log(employeePulses[i]);
    }
  }

  let empl = document.createElement('div');
  empl.classList.add('employee');
  // empl.dataset.id = id;
  empl.innerHTML = html`

${name}

${status}`;
  document.querySelector('.employee-container').appendChild(empl);
  empl.addEventListener('click',e=>{
    let overview = document.createElement('div');
    overview.classList.add('overview');
    // todo: get the right pulses out of the user
    overview.innerHTML = html`

${employee.name}

Thursay, 28 April

Werfstraat 131

there is some note

        <time datetime="2016-04-28T15:15:24+00:00">15:15</time>→
        <time datetime="2016-04-28T15:30:24+00:00">15:30</time>

30 min

Oudeheerweg 13

this is also a note

        <time datetime="2016-04-28T16:00:24+00:00">16:00</time>→
        <time datetime="2016-04-28T16:30:24+00:00">16:30</time>

2 h 30 min

Gebroeders Desmetstraat 1

Hello, there's a bit written here, because this person kinda liked writing at the point of his checkin.

        <time datetime="2016-04-28T18:00:24+00:00">18:00</time>→
        <time datetime="2016-04-28T18:32:24+00:00">18:32</time>

    `;
    document.body.insertBefore(overview,document.querySelector('.employees'));
    overview.addEventListener('click',e=>{
      if (e.target.classList.contains('overview')) {
        // todo: make fade out animation work
        //e.target.parentNode.style.webkitAnimation = 'fadeOut .3s';
        // e.target.parentNode.style.animation = 'fadeOut .3s';
        // e.target.parentNode.addEventListener('webkitAnimationEnd',()=>{
          e.target.parentNode.removeChild(e.target);
        // });
        // e.target.parentNode.addEventListener('animationEnd',()=>{
          // e.target.parentNode.removeChild(e.target);
        // });
        console.log('removed');
      } else if (e.target.className.indexOf('__unconfirmed') !== -1 || e.target.parentNode.className.indexOf('__unconfirmed') !== -1) {
        // todo: actually do this action
        console.log('confirmed');
      } else if (e.target.className.indexOf('__confirmed') !== -1 || e.target.parentNode.className.indexOf('__confirmed') !== -1) {
        // todo: actually do this action
        console.log('unconfirmed');
      }
    });
  })
  let flexfix = document.createElement('div');
  flexfix.classList.add('👻');
  document.querySelector('.employee-container').appendChild(flexfix);
};

let getEmployees = (id,callback) => {
  base.child('companies').child(id).child('employees').on('child_added',snapshot=>{
    base.child('users').child(snapshot.key()).once('value',snap=>{
      callback(snap.val(),snap.key());
    });
  });
};

getEmployees(JSON.parse(localStorage.punchtime).company.id,addEmployee);

const drawChart = () => {
  let container = document.getElementById('timeline');
  let chart = new google.visualization.Timeline(container);
  let dataTable = new google.visualization.DataTable();

  dataTable.addColumn({ type: 'string', id: 'employee' });
  dataTable.addColumn({ type: 'string', id: 'note' });
  dataTable.addColumn({ type: 'date', id: 'Start' });
  dataTable.addColumn({ type: 'date', id: 'End' });
  dataTable.addRows(pulses);

  chart.draw(dataTable,{
    timeline: { colorByRowLabel: true }
  });
};
google.charts.load('current', {'packages':['timeline']});
google.charts.setOnLoadCallback(drawChart);

/*
 * listener on resize to reload graph
 * Will refresh the graph if there is 250 ms after a resize event
 */
var resizeTimer;
window.addEventListener('resize', function(){
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(function() {
    drawChart();
  }, 250);
});
<commit_msg>comments<commit_after>// todo: put this in its own file and require it
// HTML Escape helper utility
var util = (function() {
  // Thanks to Andrea Giammarchi
  var
    reEscape = /[&<>'"]/g,
    reUnescape = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g,
    oEscape = {
      '&': '&',
      '<': '<',
      '>': '>',
      "'": ''',
      '"': '"'
    },
    oUnescape = {
      '&': '&',
      '&': '&',
      '<': '<',
      '<': '<',
      '>': '>',
      '>': '>',
      ''': "'",
      ''': "'",
      '"': '"',
      '"': '"'
    },
    fnEscape = function(m) {
      return oEscape[m];
    },
    fnUnescape = function(m) {
      return oUnescape[m];
    },
    replace = String.prototype.replace;
  return (Object.freeze || Object)({
    escape: function escape(s) {
      return replace.call(s, reEscape, fnEscape);
    },
    unescape: function unescape(s) {
      return replace.call(s, reUnescape, fnUnescape);
    }
  });
}());

// Tagged template function
var html = function(pieces) {
  var result = pieces[0];
  var substitutions = [].slice.call(arguments, 1);
  for (var i = 0; i < substitutions.length; ++i) {
    result += util.escape(substitutions[i]) + pieces[i + 1];
  }
  return result;
};

let Firebase = require('firebase');
let base = new Firebase('https://scorching-inferno-1467.firebaseio.com/');

let auth = base.getAuth();

const setTitle = () => {
  document.getElementById('company-name').innerHTML = JSON.parse(localStorage.punchtime).company.name;
};

// if the localstorage hasn't been set, go to settings
if (!localStorage.punchtime) {
  location.href = 'settings.html';
}

if (auth) {
  console.log('logged in with: '+auth.uid);
  setTitle();
} else {
  console.warn('not logged in');
  location.href = '/login/';
}

let pulses = []; // the graph is made from this object

const addEmployee = (employee,id) => {
  let employeePulses = [];
  for (let pulse in employee.pulses) {
    if (employee.pulses.hasOwnProperty(pulse)) {
      base.child('pulses').child(pulse).once('value',snap=>{
        try {
          let name = employee.name,
              checkin = new Date(parseInt(snap.val().checkin)),
              note = snap.val().note,
              checkout;
          if (snap.val().checkout && snap.val().checkout !== 0) {
            checkout = new Date(parseInt(snap.val().checkout));
          } else {
            checkout = new Date();
          }
          pulses.push([name, note, checkin, checkout]);
          employeePulses.push({
            id: snap.key(),
            latitude: snap.val().latitude,
            longitude: snap.val().longitude,
            checkin: snap.val().checkin,
            checkout: snap.val().checkout,
            note: snap.val().note,
            confirmed: snap.val().confirmed,
          });
          drawChart();
        } catch(e) {
          console.log(`/pulses/${snap.key()} in /users/${employee.name} has a problem.`);
        }
      });
    }
  }

  let image = employee.image || '/src/img/icons/empty.svg',
      name = employee.name,
      status = 'good';

  // todo: async/await the fetching
  for (let i in employeePulses) {
    if (employeePulses.hasOwnProperty(i)) {
      console.log(employeePulses[i]);
    }
  }

  let empl = document.createElement('div');
  empl.classList.add('employee');
  // empl.dataset.id = id;
  empl.innerHTML = html`

${name}

${status}`;
  document.querySelector('.employee-container').appendChild(empl);
  empl.addEventListener('click',e=>{
    let overview = document.createElement('div');
    overview.classList.add('overview');
    // todo: get the right pulses out of the user
    overview.innerHTML = html`

${employee.name}

Thursay, 28 April

Werfstraat 131

there is some note

        <time datetime="2016-04-28T15:15:24+00:00">15:15</time>→
        <time datetime="2016-04-28T15:30:24+00:00">15:30</time>

30 min

Oudeheerweg 13

this is also a note

        <time datetime="2016-04-28T16:00:24+00:00">16:00</time>→
        <time datetime="2016-04-28T16:30:24+00:00">16:30</time>

2 h 30 min

Gebroeders Desmetstraat 1

Hello, there's a bit written here, because this person kinda liked writing at the point of his checkin.

        <time datetime="2016-04-28T18:00:24+00:00">18:00</time>→
        <time datetime="2016-04-28T18:32:24+00:00">18:32</time>

    `;
    document.body.insertBefore(overview,document.querySelector('.employees'));
    overview.addEventListener('click',e=>{
      if (e.target.classList.contains('overview')) {
        e.target.parentNode.removeChild(e.target);
        console.log('removed');
      } else if (e.target.className.indexOf('__unconfirmed') !== -1 || e.target.parentNode.className.indexOf('__unconfirmed') !== -1) {
        // todo: actually do this action
        // 1. toggle the class
        // send to firebase
        console.log('confirmed');
      } else if (e.target.className.indexOf('__confirmed') !== -1 || e.target.parentNode.className.indexOf('__confirmed') !== -1) {
        // todo: actually do this action
        // 1. toggle the class
        // send to firebase
        console.log('unconfirmed');
      }
    });
  })
  let flexfix = document.createElement('div');
  flexfix.classList.add('👻');
  document.querySelector('.employee-container').appendChild(flexfix);
};

let getEmployees = (id,callback) => {
  base.child('companies').child(id).child('employees').on('child_added',snapshot=>{
    base.child('users').child(snapshot.key()).once('value',snap=>{
      callback(snap.val(),snap.key());
    });
  });
};

getEmployees(JSON.parse(localStorage.punchtime).company.id,addEmployee);

const drawChart = () => {
  let container = document.getElementById('timeline');
  let chart = new google.visualization.Timeline(container);
  let dataTable = new google.visualization.DataTable();

  dataTable.addColumn({ type: 'string', id: 'employee' });
  dataTable.addColumn({ type: 'string', id: 'note' });
  dataTable.addColumn({ type: 'date', id: 'Start' });
  dataTable.addColumn({ type: 'date', id: 'End' });
  dataTable.addRows(pulses);

  chart.draw(dataTable,{
    timeline: { colorByRowLabel: true }
  });
};
google.charts.load('current', {'packages':['timeline']});
google.charts.setOnLoadCallback(drawChart);

/*
 * listener on resize to reload graph
 * Will refresh the graph if there is 250 ms after a resize event
 */
var resizeTimer;
window.addEventListener('resize', function(){
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(function() {
    drawChart();
  }, 250);
});
<|endoftext|>


<commit_before>let Firebase = require('firebase');
let html = require('./html-escaping');
require('./welcome')();
let base = new Firebase('https://scorching-inferno-1467.firebaseio.com/');

let auth = base.getAuth();

const setTitle = () => {
  document.getElementById('company-name').innerHTML = JSON.parse(localStorage.punchtime).company.name;
};

// if the localstorage hasn't been set, go to settings
if (!localStorage.punchtime) {
  location.href = 'settings.html';
}

if (auth) {
  console.log('logged in with: ' + auth.uid);
  setTitle();
} else {
  console.warn('not logged in');
  location.href = '/login/';
}

let pulses = []; // the graph is made from this object

const addEmployee = (employee) => {
  let employeePulses = [];
  for (let pulse in employee.pulses) {
    if (employee.pulses.hasOwnProperty(pulse)) {
      base.child('pulses').child(pulse).once('value', (snap) => {
        try {
          let name = employee.name,
            checkin = new Date(parseInt(snap.val().checkin)),
            note = snap.val().note,
            address = snap.val().addressStreet,
            checkout;
          if (snap.val().checkout && snap.val().checkout !== 0) {
            checkout = new Date(parseInt(snap.val().checkout));
          } else {
            checkout = new Date();
          }
          pulses.push([name, address, checkin, checkout]);
          employeePulses.push({
            id: snap.key(),
            latitude: snap.val().latitude,
            longitude: snap.val().longitude,
            checkin: new Date(parseInt(snap.val().checkin)),
            checkout: new Date(parseInt(snap.val().checkout)),
            address: snap.val().addressStreet,
            city: snap.val().addressCityCountry,
            note: snap.val().note,
            confirmed: parseBool(snap.val().confirmed)
          });
          drawChart();
        } catch (e) {
          console.log(`/pulses/${snap.key()} in /users/${employee.name} has a problem.`);
        }
      });
    }
  }

  let image = employee.image || '/src/img/icons/empty.svg',
    name = employee.name,
    status = 'good';

  let empl = document.createElement('div');
  empl.classList.add('employee');
  // empl.dataset.id = id;
  empl.innerHTML = html `

${name}

${status}`;
  document.querySelector('.employee-container').appendChild(empl);

  empl.addEventListener('click', () => {
    addOverview(employeePulses,employee);
  });

  let flexfix = document.createElement('div');
  flexfix.classList.add('👻');
  document.querySelector('.employee-container').appendChild(flexfix);
};

let addOverview = (pulses,employee) => {
  let overview = document.createElement('div');
  overview.classList.add('overview');
  let overviewContent = document.createElement('div');
  overviewContent.classList.add('overview--content');
  overviewContent.innerHTML = html `<h2 class="overview--title">${employee.name}</h2>`;
  let timeline = document.createElement('div');
  timeline.classList.add('timeline');
  // todo: take day in account
//   timeline.innerHTML = html `
//

//
Thursay, 28 April

//
`;

  if (pulses.length === 0) {
    timeline.innerHTML += `

No checkins

    `;
  }
  for (let i in pulses) {
    addToTimeline(pulses[i],pulses[i-1],timeline);
  }

  overviewContent.appendChild(timeline);
  overview.appendChild(overviewContent);

  document.body.insertBefore(overview, document.querySelector('.employees'));
  overview.addEventListener('click', (e) => {
    if (e.target.classList.contains('overview')) {
      e.target.parentNode.removeChild(e.target);
    } else if (e.target.parentNode.classList.contains('timeline--item__still')) {
      toggleStatus(e.target.parentNode);
    } else if (e.target.classList.contains('timeline--item__still')) {
      toggleStatus(e.target);
    }
  });
};

let parseBool = (string) => {
  if (string === 'true' || string === true) {
    return true;
  } else {
    return false;
  }
};

let addToTimeline = (current, previous, timeline) => {
  if (previous) {
    let diff = new Date(current.checkin - previous.checkout);
    timeline.innerHTML += html `

${diff.getHours > 0 ? (diff.getHours() - 1)+' h' : ''} ${diff.getMinutes() + Math.round(diff.getSeconds() / 60)} min

`;
  }
  timeline.innerHTML += html `

${current.address}

${current.city}

${current.note}

    <time datetime="${current.checkin.toISOString()}">${current.checkin.getHours()}:${current.checkin.getMinutes() < 10 ? 0 : ''}${current.checkin.getMinutes()}</time>→
    <time datetime="${current.checkout.toISOString()}">${current.checkout.getHours()}:${current.checkout.getMinutes() < 10 ? 0 : ''}${current.checkout.getMinutes()}</time>

`;
};

let toggleStatus = (element) => {
  if (element.classList.contains('timeline--item__unconfirmed')) {
    element.classList.remove('timeline--item__unconfirmed');
    element.classList.add('timeline--item__confirmed');
    base.child('pulses')
      .child(element.dataset.pulse)
      .child('confirmed')
      .set('true');
  } else if (element.classList.contains('timeline--item__confirmed')) {
    element.classList.remove('timeline--item__confirmed');
    element.classList.add('timeline--item__unconfirmed');
    base.child('pulses')
      .child(element.dataset.pulse)
      .child('confirmed')
      .set('false');
  }
};

let getEmployees = (id, callback) => {
  base.child('companies').child(id).child('employees').on('child_added', (snapshot) => {
    base.child('users').child(snapshot.key()).once('value', (snap) => {
      callback(snap.val());
    });
  });
};

getEmployees(JSON.parse(localStorage.punchtime).company.id, addEmployee);

const drawChart = () => {
  let chart = new google.visualization.Timeline(document.getElementById('timeline'));
  let dataTable = new google.visualization.DataTable();

  dataTable.addColumn({
    type: 'string',
    id: 'employee'
  });
  dataTable.addColumn({
    type: 'string',
    id: 'note'
  });
  dataTable.addColumn({
    type: 'date',
    id: 'Start'
  });
  dataTable.addColumn({
    type: 'date',
    id: 'End'
  });
  dataTable.addRows(pulses);

  chart.draw(dataTable, {
    timeline: {
      singleColor: '#3B4358'
    }
  });
};
google.charts.load('current', {
  'packages': ['timeline']
});
google.charts.setOnLoadCallback(drawChart);

/*
 * listener on resize to reload graph
 * Will refresh the graph if there is 250 ms after a resize event
 */
var resizeTimer;
window.addEventListener('resize', () => {
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(() => {
    drawChart();
  }, 250);
});
<commit_msg>fix hours<commit_after>let Firebase = require('firebase');
let html = require('./html-escaping');
require('./welcome')();
let base = new Firebase('https://scorching-inferno-1467.firebaseio.com/');

let auth = base.getAuth();

const setTitle = () => {
  document.getElementById('company-name').innerHTML = JSON.parse(localStorage.punchtime).company.name;
};

// if the localstorage hasn't been set, go to settings
if (!localStorage.punchtime) {
  location.href = 'settings.html';
}

if (auth) {
  console.log('logged in with: ' + auth.uid);
  setTitle();
} else {
  console.warn('not logged in');
  location.href = '/login/';
}

let pulses = []; // the graph is made from this object

const addEmployee = (employee) => {
  let employeePulses = [];
  for (let pulse in employee.pulses) {
    if (employee.pulses.hasOwnProperty(pulse)) {
      base.child('pulses').child(pulse).once('value', (snap) => {
        try {
          let name = employee.name,
            checkin = new Date(parseInt(snap.val().checkin)),
            note = snap.val().note,
            address = snap.val().addressStreet,
            checkout;
          if (snap.val().checkout && snap.val().checkout !== 0) {
            checkout = new Date(parseInt(snap.val().checkout));
          } else {
            checkout = new Date();
          }
          pulses.push([name, address, checkin, checkout]);
          employeePulses.push({
            id: snap.key(),
            latitude: snap.val().latitude,
            longitude: snap.val().longitude,
            checkin: new Date(parseInt(snap.val().checkin)),
            checkout: new Date(parseInt(snap.val().checkout)),
            address: snap.val().addressStreet,
            city: snap.val().addressCityCountry,
            note: snap.val().note,
            confirmed: parseBool(snap.val().confirmed)
          });
          drawChart();
        } catch (e) {
          console.log(`/pulses/${snap.key()} in /users/${employee.name} has a problem.`);
        }
      });
    }
  }

  let image = employee.image || '/src/img/icons/empty.svg',
    name = employee.name,
    status = 'good';

  let empl = document.createElement('div');
  empl.classList.add('employee');
  // empl.dataset.id = id;
  empl.innerHTML = html `

${name}

${status}`;
  document.querySelector('.employee-container').appendChild(empl);

  empl.addEventListener('click', () => {
    addOverview(employeePulses,employee);
  });

  let flexfix = document.createElement('div');
  flexfix.classList.add('👻');
  document.querySelector('.employee-container').appendChild(flexfix);
};

let addOverview = (pulses,employee) => {
  let overview = document.createElement('div');
  overview.classList.add('overview');
  let overviewContent = document.createElement('div');
  overviewContent.classList.add('overview--content');
  overviewContent.innerHTML = html `<h2 class="overview--title">${employee.name}</h2>`;
  let timeline = document.createElement('div');
  timeline.classList.add('timeline');
  // todo: take day in account
//   timeline.innerHTML = html `
//

//
Thursay, 28 April

//
`;

  if (pulses.length === 0) {
    timeline.innerHTML += `

No checkins

    `;
  }
  for (let i in pulses) {
    addToTimeline(pulses[i],pulses[i-1],timeline);
  }

  overviewContent.appendChild(timeline);
  overview.appendChild(overviewContent);

  document.body.insertBefore(overview, document.querySelector('.employees'));
  overview.addEventListener('click', (e) => {
    if (e.target.classList.contains('overview')) {
      e.target.parentNode.removeChild(e.target);
    } else if (e.target.parentNode.classList.contains('timeline--item__still')) {
      toggleStatus(e.target.parentNode);
    } else if (e.target.classList.contains('timeline--item__still')) {
      toggleStatus(e.target);
    }
  });
};

let parseBool = (string) => {
  if (string === 'true' || string === true) {
    return true;
  } else {
    return false;
  }
};

let addToTimeline = (current, previous, timeline) => {
  if (previous) {
    let diff = new Date(current.checkin - previous.checkout);
    let diffHours = Math.round(diff.getTime() / 3600000);
    timeline.innerHTML += html `

${diffHours > 0 ? diffHours+' h' : ''} ${diff.getMinutes() + Math.round(diff.getSeconds() / 60)} min

`;
  }
  timeline.innerHTML += html `

${current.address}

${current.city}

${current.note}

    <time datetime="${current.checkin.toISOString()}">${current.checkin.getHours()}:${current.checkin.getMinutes() < 10 ? 0 : ''}${current.checkin.getMinutes()}</time>→
    <time datetime="${current.checkout.toISOString()}">${current.checkout.getHours()}:${current.checkout.getMinutes() < 10 ? 0 : ''}${current.checkout.getMinutes()}</time>

`;
};

let toggleStatus = (element) => {
  if (element.classList.contains('timeline--item__unconfirmed')) {
    element.classList.remove('timeline--item__unconfirmed');
    element.classList.add('timeline--item__confirmed');
    base.child('pulses')
      .child(element.dataset.pulse)
      .child('confirmed')
      .set('true');
  } else if (element.classList.contains('timeline--item__confirmed')) {
    element.classList.remove('timeline--item__confirmed');
    element.classList.add('timeline--item__unconfirmed');
    base.child('pulses')
      .child(element.dataset.pulse)
      .child('confirmed')
      .set('false');
  }
};

let getEmployees = (id, callback) => {
  base.child('companies').child(id).child('employees').on('child_added', (snapshot) => {
    base.child('users').child(snapshot.key()).once('value', (snap) => {
      callback(snap.val());
    });
  });
};

getEmployees(JSON.parse(localStorage.punchtime).company.id, addEmployee);

const drawChart = () => {
  let chart = new google.visualization.Timeline(document.getElementById('timeline'));
  let dataTable = new google.visualization.DataTable();

  dataTable.addColumn({
    type: 'string',
    id: 'employee'
  });
  dataTable.addColumn({
    type: 'string',
    id: 'note'
  });
  dataTable.addColumn({
    type: 'date',
    id: 'Start'
  });
  dataTable.addColumn({
    type: 'date',
    id: 'End'
  });
  dataTable.addRows(pulses);

  chart.draw(dataTable, {
    timeline: {
      singleColor: '#3B4358'
    }
  });
};
google.charts.load('current', {
  'packages': ['timeline']
});
google.charts.setOnLoadCallback(drawChart);

/*
 * listener on resize to reload graph
 * Will refresh the graph if there is 250 ms after a resize event
 */
var resizeTimer;
window.addEventListener('resize', () => {
  clearTimeout(resizeTimer);
  resizeTimer = setTimeout(() => {
    drawChart();
  }, 250);
});
<|endoftext|>


<commit_before>var HtmlReporter = require('protractor-beautiful-reporter');
var path = require('path');

// ----- Config example for Jasmine 2 -----

exports.config = {
    // ----- How to setup Selenium -----
    //
    // There are three ways to specify how to use Selenium. Specify one of the
    // following:
    //
    // 1. seleniumServerJar - to start Selenium Standalone locally.
    // 2. seleniumAddress - to connect to a Selenium server which is already
    //    running.
    // 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.

    // The location of the selenium standalone server .jar file.
    //seleniumServerJar: 'node_modules/protractor/selenium/selenium-server-standalone-2.37.0.jar',

    // Address of selenium server (before running protractor, run "webdriver-manager start" to start the Selenium server)
    seleniumAddress: process.env.SELENIUMSERVER ? process.env.SELENIUMSERVER : 'http://localhost:4444/wd/hub',

    // The port to start the selenium server on, or null if the server should
    // find its own unused port.
    seleniumPort: null,

    // Chromedriver location is used to help the selenium standalone server
    // find chromedriver. This will be passed to the selenium jar as
    // the system property webdriver.chrome.driver. If null, selenium will
    // attempt to find chromedriver using PATH.
    //chromeDriver: 'node_modules/protractor/selenium/chromedriver',

    // Additional command line options to pass to selenium. For example,
    // if you need to change the browser timeout, use
    // seleniumArgs: ['-browserTimeout=60'],
    seleniumArgs: [],

    // If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
    // The tests will be run remotely using SauceLabs.
    sauceUser: null,
    sauceKey: null,

    // ----- What tests to run -----
    //
    // Spec patterns are relative to the location of this config.
    specs: [
        './specs/*.js'
    ],

    // ----- Capabilities to be passed to the webdriver instance ----
    //
    // For a full list of available capabilities, see
    // https://code.google.com/p/selenium/wiki/DesiredCapabilities
    // and
    // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
    capabilities: {
        browserName: 'chrome',
        logName: 'Chrome - English',
        version: '',
        platform: 'ANY',
        shardTestFiles: false,
        maxInstances: 2,

        chromeOptions: {
            args: ["--window-size=1680,1000"]
            // commented out but needed to keep it for local testing
            //,  binary: process.env.CHROMIUM_BIN
            //
            //     args:["--headless","--disable-gpu","--window-size=1680,1680"]
            //     args:["--headless","--disable-gpu","--force-device-scale-factor=1.75","--high-dpi-support=1.75","--window-size=1400,1680"]
        }
    },

    // A base URL for your application under test. Calls to protractor.get()
    // with relative paths will be prepended with this.
    baseUrl: 'http://localhost:9999',

    // Set the framework
    framework: 'jasmine',

    // Selector for the element housing the angular app - this defaults to
    // body, but is necessary if ng-app is on a descendant of <body>
    rootElement: 'body',

    onPrepare: function () {
        // Add a screenshot reporter:
        jasmine.getEnv().addReporter(new HtmlReporter({
            preserveDirectory: false,
            takeScreenShotsOnlyForFailedSpecs: true,
            screenshotsSubfolder: 'images',
            jsonsSubfolder: 'jsons',
            baseDirectory: 'reports-tmp',

            pathBuilder: function pathBuilder(spec, descriptions, results, capabilities) {
                // Return '<30-12-2016>/<browser>/<specname>' as path for screenshots:
                // Example: '30-12-2016/firefox/list-should work'.
                var currentDate = new Date(),
                    day = currentDate.getDate(),
                    month = currentDate.getMonth() + 1,
                    year = currentDate.getFullYear();

                var validDescriptions = descriptions.map(function (description) {
                    return description.replace('/', '@');
                });

                return path.join(
                    day + "-" + month + "-" + year,
                    // capabilities.get('browserName'),
                    validDescriptions.join('-'));
            }
        }).getJasmine2Reporter());
    },

    jasmineNodeOpts: {
        // If true, print colors to the terminal.
        showColors: true,
        // Default time to wait in ms before a test fails.
        defaultTimeoutInterval: 30000,
    }
};

<commit_msg>use directconnection if chromium binary is set in environmentvariable CHROMIUM_BIN<commit_after>var HtmlReporter = require('protractor-beautiful-reporter');
var path = require('path');

// ----- Config example for Jasmine 2 -----

exports.config = {
    // ----- How to setup Selenium -----
    //
    // There are three ways to specify how to use Selenium. Specify one of the
    // following:
    //
    // 1. seleniumServerJar - to start Selenium Standalone locally.
    // 2. seleniumAddress - to connect to a Selenium server which is already
    //    running.
    // 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.

    // The location of the selenium standalone server .jar file.
    //seleniumServerJar: 'node_modules/protractor/selenium/selenium-server-standalone-2.37.0.jar',

    // Address of selenium server (before running protractor, run "webdriver-manager start" to start the Selenium server)
    seleniumAddress: process.env.SELENIUMSERVER ? process.env.SELENIUMSERVER : 'http://localhost:4444/wd/hub',

    // The port to start the selenium server on, or null if the server should
    // find its own unused port.
    seleniumPort: null,

    /* direct connect on my local system*/
    directConnect:!!process.env.CHROMIUM_BIN,

    // Chromedriver location is used to help the selenium standalone server
    // find chromedriver. This will be passed to the selenium jar as
    // the system property webdriver.chrome.driver. If null, selenium will
    // attempt to find chromedriver using PATH.
    //chromeDriver: 'node_modules/protractor/selenium/chromedriver',

    // Additional command line options to pass to selenium. For example,
    // if you need to change the browser timeout, use
    // seleniumArgs: ['-browserTimeout=60'],
    seleniumArgs: [],

    // If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
    // The tests will be run remotely using SauceLabs.
    sauceUser: null,
    sauceKey: null,

    // ----- What tests to run -----
    //
    // Spec patterns are relative to the location of this config.
    specs: [
        './specs/*.js'
    ],

    // ----- Capabilities to be passed to the webdriver instance ----
    //
    // For a full list of available capabilities, see
    // https://code.google.com/p/selenium/wiki/DesiredCapabilities
    // and
    // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
    capabilities:!!process.env.CHROMIUM_BIN?{
        'browserName': 'chrome',
        'applicationName': 'chromium',
        'chromeOptions': {
            'binary': process.env.CHROMIUM_BIN,
            'args': ['--lang=en',
                '--window-size=1680,1050']
        }
    }:{
        browserName: 'chrome',
        logName: 'Chrome - English',
        version: '',
        platform: 'ANY',
        shardTestFiles: false,
        maxInstances: 2,

        chromeOptions: {
            args: ["--window-size=1680,1000"]
            // commented out but needed to keep it for local testing
            //,  binary: process.env.CHROMIUM_BIN
            //
            //     args:["--headless","--disable-gpu","--window-size=1680,1680"]
            //     args:["--headless","--disable-gpu","--force-device-scale-factor=1.75","--high-dpi-support=1.75","--window-size=1400,1680"]
        }
    },

    // A base URL for your application under test. Calls to protractor.get()
    // with relative paths will be prepended with this.
    baseUrl: 'http://localhost:9999',

    // Set the framework
    framework: 'jasmine',

    // Selector for the element housing the angular app - this defaults to
    // body, but is necessary if ng-app is on a descendant of <body>
    rootElement: 'body',

    onPrepare: function () {
        // Add a screenshot reporter:
        jasmine.getEnv().addReporter(new HtmlReporter({
            preserveDirectory: false,
            takeScreenShotsOnlyForFailedSpecs: true,
            screenshotsSubfolder: 'images',
            jsonsSubfolder: 'jsons',
            baseDirectory: 'reports-tmp',

            pathBuilder: function pathBuilder(spec, descriptions, results, capabilities) {
                // Return '<30-12-2016>/<browser>/<specname>' as path for screenshots:
                // Example: '30-12-2016/firefox/list-should work'.
                var currentDate = new Date(),
                    day = currentDate.getDate(),
                    month = currentDate.getMonth() + 1,
                    year = currentDate.getFullYear();

                var validDescriptions = descriptions.map(function (description) {
                    return description.replace('/', '@');
                });

                return path.join(
                    day + "-" + month + "-" + year,
                    // capabilities.get('browserName'),
                    validDescriptions.join('-'));
            }
        }).getJasmine2Reporter());
    },

    jasmineNodeOpts: {
        // If true, print colors to the terminal.
        showColors: true,
        // Default time to wait in ms before a test fails.
        defaultTimeoutInterval: 30000,
    }
};

<|endoftext|>


<commit_before>//.dragがドラッグされている際の動き
	$(function() {
    	$('.drag').draggable({
        	connectToSortable : '.drop',
  			//helper: 'clone',
        	helper : function() {
            	return $(this).clone().addClass('drag');
        	},
       	revert : 'invalid',
    	});

		$('.drop').droppable({
			accept: '.drag',
			drop: function(ev, ui) {
        		// ドロップされたDraggable要素を追加
        		ui.draggable.clone().appendTo(this);
    		},
		});

		//.dropがドラッグされている際の動き

    	$('.drop').draggable({
        	connectToSortable : '.cspace',
      		//helper: 'clone',
        	helper : function() {
            	return $(this).clone().addClass('drop');
        	},
        	revert : 'invalid',
    	});

		$('.cspace').droppable({
	 		accept: '.drop',
	  		drop: function(ev, ui) {
				$('.drop').sortable({
					connectWith: '.drop',
				});
        		// ドロップされたDraggable要素を追加

				ui.draggable.clone().appendTo(this).attr({name:drop_name,id:"a"+ drop_name});
				drop_name = drop_name + 10;
				$(document).ready(function(){

					$('.drop').droppable({
		  				accept: '.drag',
	  					drop: function(ev, ui) {
        					//ドロップされたDraggable要素を追加
        					ui.draggable.clone().appendTo(this).removeClass('drag');

							var data1 = $(this).attr('name');
							var data = Number(data1);

        					$("*[name=" + data + "] > div > *[name=culum]").attr('name' ,"a" + (data + 1));
        					$("*[name=" + data + "] > div > *[name=type]").attr('name',"a"+ (data + 2));
        					$("*[name=" + data + "] > div > *[name=num]").attr('name',"a"+ (data + 3));
        					var limit_name ="a"+ (limit_count[data] + data);
        					$("*[name=" + data + "] > div > *[name=limit]").attr('name',limit_name);
        					if($("*[name="+limit_name+"]").length > 0){
								limit_count[data]++;
							}
    					},
					});
				});
    		},
		});
	});<commit_msg>no message<commit_after>//.dragがドラッグされている際の動き
	$(function() {
    	$('#drag1').draggable({
        	connectToSortable : '.drop',
  			helper: 'clone',
			revert : 'invalid',
    	});
		$('#drag2').draggable({
			connectToSortable : '.drop',
			helper: 'clone',
			revert : 'invalid',
		});
		$('#drag3').draggable({
			connectToSortable : '.drop',
			helper: 'clone',
			revert : 'invalid',
		});

    	$('.drop').draggable({
			connectToSortable : '.cspace',
			helper: 'clone',
			revert : 'invalid',
    	});

		$('.cspace').sortable({
	  		stop: function(ev, ui) {

        		// ドロップされたDraggable要素を追加

				ui.item.attr({name:drop_name,id:"a"+ drop_name});
				drop_name = drop_name + 10;
				$(document).ready(function(){

					$('.drop').sortable({
	  					stop: function(ev, ui) {
        					//ドロップされたDraggable要素を追加

        					ui.item.removeClass('drag');

							var data1 = $(this).attr('name');
							var data = Number(data1);

        					$("*[name=" + data + "] > div > *[name=culum]").attr('name' ,"a" + (data + 1));
        					$("*[name=" + data + "] > div > *[name=type]").attr('name',"a"+ (data + 2));
        					$("*[name=" + data + "] > div > *[name=num]").attr('name',"a"+ (data + 3));
        					var limit_name ="a"+ (limit_count[data] + data);
        					$("*[name=" + data + "] > div > *[name=limit]").attr('name',limit_name);
        					if($("*[name="+limit_name+"]").length > 0){
								limit_count[data]++;
							}
    					},
					});
				});
    		},
		});
	});
	<|endoftext|>


<commit_before>export function addWrapper (htmlData, params) {
  let bodyStyle = 'font-family:' + params.font + ' !important; font-size: ' + params.font_size + ' !important; width:100%;'
  return '
' + htmlData + '
'
}

export function capitalizePrint (string) {
  return string.charAt(0).toUpperCase() + string.slice(1)
}

export function collectStyles (element, params) {
  let win = document.defaultView || window

  // String variable to hold styling for each element
  let elementStyle = ''

  // Loop over computed styles
  let styles = win.getComputedStyle(element, '')

  Object.keys(styles).map(key => {
    // Check if style should be processed
    if (params.targetStyles.indexOf('*') !== -1 || params.targetStyle.indexOf(styles[key]) !== -1 || targetStylesMatch(params.targetStyles, styles[key])) {
      if (styles.getPropertyValue(styles[key])) elementStyle += styles[key] + ':' + styles.getPropertyValue(styles[key]) + ';'
    }
  })

  // Print friendly defaults
  elementStyle += 'max-width: ' + params.maxWidth + 'px !important;' + params.font_size + ' !important;'

  return elementStyle
}

function targetStylesMatch (styles, value) {
  for (let i = 0; i < styles.length; i++) {
    if (value.indexOf(styles[i]) !== -1) return true
  }
  return false
}

export function loopNodesCollectStyles (elements, params) {
  for (let n = 0; n < elements.length; n++) {
    let currentElement = elements[n]

    // Check if we are skiping this element
    if (params.ignoreElements.indexOf(currentElement.getAttribute('id')) !== -1) {
      currentElement.parentNode.removeChild(currentElement)
      continue
    }

    // Form Printing - check if is element Input
    let tag = currentElement.tagName
    if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') {
      // Save style to variable
      let textStyle = collectStyles(currentElement, params)

      // Remove INPUT element and insert a text node
      let parent = currentElement.parentNode

      // Get text value
      let textNode = tag === 'SELECT'
                ? document.createTextNode(currentElement.options[currentElement.selectedIndex].text)
                : document.createTextNode(currentElement.value)

      // Create text element
      let textElement = document.createElement('div')
      textElement.appendChild(textNode)

      // Add style to text
      textElement.setAttribute('style', textStyle)

      // Add text
      parent.appendChild(textElement)

      // Remove input
      parent.removeChild(currentElement)
    } else {
      // Get all styling for print element
      currentElement.setAttribute('style', collectStyles(currentElement, params))
    }

    // Check if more elements in tree
    let children = currentElement.children

    if (children && children.length) {
      loopNodesCollectStyles(children, params)
    }
  }
}

export function addHeader (printElement, header, headerStyle) {
  // Create header element
  let headerElement = document.createElement('h1')

  // Create header text node
  let headerNode = document.createTextNode(header)

  // Build and style
  headerElement.appendChild(headerNode)
  headerElement.setAttribute('style', headerStyle)

  printElement.insertBefore(headerElement, printElement.childNodes[0])
}
<commit_msg>- Modifying the function parameter to comply with the new properties type.<commit_after>export function addWrapper (htmlData, params) {
  let bodyStyle = 'font-family:' + params.font + ' !important; font-size: ' + params.font_size + ' !important; width:100%;'
  return '
' + htmlData + '
'
}

export function capitalizePrint (obj) {
  return obj.charAt(0).toUpperCase() + obj.slice(1)
}

export function collectStyles (element, params) {
  let win = document.defaultView || window

  // String variable to hold styling for each element
  let elementStyle = ''

  // Loop over computed styles
  let styles = win.getComputedStyle(element, '')

  Object.keys(styles).map(key => {
    // Check if style should be processed
    if (params.targetStyles.indexOf('*') !== -1 || params.targetStyle.indexOf(styles[key]) !== -1 || targetStylesMatch(params.targetStyles, styles[key])) {
      if (styles.getPropertyValue(styles[key])) elementStyle += styles[key] + ':' + styles.getPropertyValue(styles[key]) + ';'
    }
  })

  // Print friendly defaults
  elementStyle += 'max-width: ' + params.maxWidth + 'px !important;' + params.font_size + ' !important;'

  return elementStyle
}

function targetStylesMatch (styles, value) {
  for (let i = 0; i < styles.length; i++) {
    if (value.indexOf(styles[i]) !== -1) return true
  }
  return false
}

export function loopNodesCollectStyles (elements, params) {
  for (let n = 0; n < elements.length; n++) {
    let currentElement = elements[n]

    // Check if we are skiping this element
    if (params.ignoreElements.indexOf(currentElement.getAttribute('id')) !== -1) {
      currentElement.parentNode.removeChild(currentElement)
      continue
    }

    // Form Printing - check if is element Input
    let tag = currentElement.tagName
    if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') {
      // Save style to variable
      let textStyle = collectStyles(currentElement, params)

      // Remove INPUT element and insert a text node
      let parent = currentElement.parentNode

      // Get text value
      let textNode = tag === 'SELECT'
                ? document.createTextNode(currentElement.options[currentElement.selectedIndex].text)
                : document.createTextNode(currentElement.value)

      // Create text element
      let textElement = document.createElement('div')
      textElement.appendChild(textNode)

      // Add style to text
      textElement.setAttribute('style', textStyle)

      // Add text
      parent.appendChild(textElement)

      // Remove input
      parent.removeChild(currentElement)
    } else {
      // Get all styling for print element
      currentElement.setAttribute('style', collectStyles(currentElement, params))
    }

    // Check if more elements in tree
    let children = currentElement.children

    if (children && children.length) {
      loopNodesCollectStyles(children, params)
    }
  }
}

export function addHeader (printElement, header, headerStyle) {
  // Create header element
  let headerElement = document.createElement('h1')

  // Create header text node
  let headerNode = document.createTextNode(header)

  // Build and style
  headerElement.appendChild(headerNode)
  headerElement.setAttribute('style', headerStyle)

  printElement.insertBefore(headerElement, printElement.childNodes[0])
}
<|endoftext|>


<commit_before>/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
*/

/**
 * Parse an int to a more readable string
 *
 * @param int duration
 * @return string
 */
var parseDuration = function (duration) {
    var min = parseInt(duration / 60); // If the output has miliseconds, it does not matter, modulo is not important
    var sec = duration - 60 * min;

    sec = parseInt(sec);
    if(sec < 10) sec = ('0' + sec).slice(-2);

    return min + ':' + sec;
};

/**
 * Sort an array of int by ASC or DESC, then remove all duplicates
 *
 * @param array  array of int to be sorted
 * @param string 'asc' or 'desc' depending of the sort needed
 * @return array
 */

var simpleSort = function(array, sorting) {

    if(sorting == 'asc') {
        array.sort(function(a, b) {
            return a - b;
        });
    }
    else if (sorting == 'desc') {
        array.sort(function(a, b) {
            return b - a;
        });
    }

    var result = [];
    array.forEach(function(item) {
        if(result.indexOf(item) < 0) {
            result.push(item);
        }
    });

    return result;
};
<commit_msg>Add a shuffle() function<commit_after>/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
*/

/**
 * Parse an int to a more readable string
 *
 * @param int duration
 * @return string
 */
var parseDuration = function (duration) {
    var min = parseInt(duration / 60); // If the output has miliseconds, it does not matter, modulo is not important
    var sec = duration - 60 * min;

    sec = parseInt(sec);
    if(sec < 10) sec = ('0' + sec).slice(-2);

    return min + ':' + sec;
};

/**
 * Sort an array of int by ASC or DESC, then remove all duplicates
 *
 * @param array  array of int to be sorted
 * @param string 'asc' or 'desc' depending of the sort needed
 * @return array
 */

var simpleSort = function(array, sorting) {

    if(sorting == 'asc') {
        array.sort(function(a, b) {
            return a - b;
        });
    }
    else if (sorting == 'desc') {
        array.sort(function(a, b) {
            return b - a;
        });
    }

    var result = [];
    array.forEach(function(item) {
        if(result.indexOf(item) < 0) {
            result.push(item);
        }
    });

    return result;
};

/**
 * Suffle an array with the Fisher–Yates Shuffle algorythm (http://bost.ocks.org/mike/shuffle/)
 *
 * @param array the array to be shuffled
 * @return array
 */
function shuffle(array) {
    var m = array.length, t, i;

    // While there remain elements to shuffle…
    while (m) {

        // Pick a remaining element…
        i = Math.floor(Math.random() * m--);

        // And swap it with the current element.
        t = array[m];
        array[m] = array[i];
        array[i] = t;
    }

    return array;
}
<|endoftext|>


<commit_before>const svarUt = require('../index')
const fs = require('fs')

const options = {
  config: {
    url: process.env.SVARUT_URL || 'https://username:password@test.svarut.ks.no/tjenester/forsendelseservice/ForsendelsesServiceV7'
  },
  query: {
    dokumenter: [
      {
        data: fs.readFileSync('../test/data/skoleskyss_avslag_vedtak.pdf').toString('base64'), // Must be base64
        filnavn: 'skoleskyss_avslag_vedtak.pdf',
        mimetype: 'application/pdf'
      }
    ],
    tittel: 'SvarUt testdokument',
    avgivendeSystem: 'node-svarut test',
    konteringskode: '1111',
    krevNiva4Innlogging: false,
    kryptert: false,
    kunDigitalLevering: false,
    printkonfigurasjon: {
      brevtype: 'BPOST',
      fargePrint: true,
      tosidig: false
    },
    eksternref: 11,
    forsendelseType: 'string',
    lenker: [
      {
        ledetekst: 'string',
        urlLenke: 'string',
        urlTekst: 'string'
      }
    ],
    metadataForImport: {
      dokumentetsDato: new Date().toISOString(),
      journalposttype: 'string',
      journalstatus: 'string',
      saksaar: 11,
      sakssekvensnummer: 11,
      tittel: 'string'
    },
    metadataFraAvleverendeSystem: {
      dokumentetsDato: new Date().toISOString(),
      ekstraMetadata: [
        {
          key: 'string',
          value: 'string'
        }
      ],
      journalaar: 11,
      journaldato: new Date().toISOString(),
      journalpostnummer: 11,
      journalposttype: 'string',
      journalsekvensnummer: 11,
      journalstatus: 'string',
      saksaar: 111,
      saksbehandler: 111,
      sakssekvensnummer: 111,
      tittel: 'string'
    },
    signaturtype: 'AUTENTISERT_SIGNATUR',
    signeringUtloper: new Date().toISOString(),
    svarPaForsendelse: 'http://google.no',
    svarPaForsendelseLink: false,
    svarSendesTil: {
      navn: 'Terje Tverrtryne',
      adresse1: 'Skogsveien 42',
      adresse2: '',
      adresse3: '',
      postnr: '3710',
      poststed: 'Skien',
      fodselsnr: '01029400470'
    }
  },
  mottaker: [
    {
      navn: 'Terje Tverrtryne',
      adresse1: 'Skogsveien 42',
      adresse2: '',
      adresse3: '',
      postnr: '3710',
      poststed: 'Skien',
      fodselsnr: '01029400470'
      // orgnr: '940192226' // Hvis organisasjon
    }
  ]
}

svarUt.sendForsendelse(options)
  .then(data => console.log(JSON.stringify(data, null, 2)))
  .catch(error => console.error(error))
<commit_msg>Fix wrong object<commit_after>const svarUt = require('../index')
const fs = require('fs')

const options = {
  config: {
    url: process.env.SVARUT_URL || 'https://username:password@test.svarut.ks.no/tjenester/forsendelseservice/ForsendelsesServiceV7'
  },
  query: {
    dokumenter: [
      {
        data: fs.readFileSync('../test/data/skoleskyss_avslag_vedtak.pdf').toString('base64'), // Must be base64
        filnavn: 'skoleskyss_avslag_vedtak.pdf',
        mimetype: 'application/pdf'
      }
    ],
    tittel: 'SvarUt testdokument',
    avgivendeSystem: 'node-svarut test',
    konteringskode: '1111',
    krevNiva4Innlogging: false,
    kryptert: false,
    kunDigitalLevering: false,
    printkonfigurasjon: {
      brevtype: 'BPOST',
      fargePrint: true,
      tosidig: false
    },
    eksternref: 11,
    forsendelseType: 'string',
    lenker: [
      {
        ledetekst: 'string',
        urlLenke: 'string',
        urlTekst: 'string'
      }
    ],
    metadataForImport: {
      dokumentetsDato: new Date().toISOString(),
      journalposttype: 'string',
      journalstatus: 'string',
      saksaar: 11,
      sakssekvensnummer: 11,
      tittel: 'string'
    },
    metadataFraAvleverendeSystem: {
      dokumentetsDato: new Date().toISOString(),
      ekstraMetadata: [
        {
          key: 'string',
          value: 'string'
        }
      ],
      journalaar: 11,
      journaldato: new Date().toISOString(),
      journalpostnummer: 11,
      journalposttype: 'string',
      journalsekvensnummer: 11,
      journalstatus: 'string',
      saksaar: 111,
      saksbehandler: 111,
      sakssekvensnummer: 111,
      tittel: 'string'
    },
    signaturtype: 'AUTENTISERT_SIGNATUR',
    signeringUtloper: new Date().toISOString(),
    svarPaForsendelse: 'http://google.no',
    svarPaForsendelseLink: false,
    svarSendesTil: {
      navn: 'Terje Tverrtryne',
      adresse1: 'Skogsveien 42',
      adresse2: '',
      adresse3: '',
      postnr: '3710',
      poststed: 'Skien',
      fodselsnr: '01029400470'
    },
    mottaker: [
      {
        navn: 'Terje Tverrtryne',
        adresse1: 'Skogsveien 42',
        adresse2: '',
        adresse3: '',
        postnr: '3710',
        poststed: 'Skien',
        fodselsnr: '01029400470'
        // orgnr: '940192226' // Hvis organisasjon
      }
    ]
  }
}

svarUt.sendForsendelse(options)
  .then(data => console.log(JSON.stringify(data, null, 2)))
  .catch(error => console.error(error))
<|endoftext|>


<commit_before>const Cron = require('../util/Cron');

/**
 * The structure for future tasks to be run
 */
class ScheduledTask {

	/**
	 * @typedef  {Object} ScheduledTaskOptions
	 * @property {string} [id] The ID for the task. By default, it generates one in base36
	 * @property {boolean} [catchUp=true] If the task should try to catch up if the bot is down
	 * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution
	 */

	/**
	 * @typedef  {Object} ScheduledTaskUpdateOptions
	 * @property {string} [repeat] The {@link Cron} pattern
	 * @property {Date} [time] The time the current task ends at
	 * @property {boolean} [catchUp] If the task should try to catch up if the bot is down
	 * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution
	 */

	/**
	 * @typedef  {Object} ScheduledTaskJSON
	 * @property {string} id The task's ID
	 * @property {string} taskName The name of the Task piece this will execute
	 * @property {number} time The UNIX timestamp for when this task ends at
	 * @property {boolean} catchUp If the task should try to catch up if the bot is down
	 * @property {string} [repeat] The {@link Cron} pattern
	 * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution
	 */

	/**
	 * Initializes a new ScheduledTask
	 * @since 0.5.0
	 * @param {KlasaClient} client The client that initialized this instance
	 * @param {string} taskName The name of the task this ScheduledTask is for
	 * @param {(Date|number|string)} time The time or Cron pattern
	 * @param {ScheduledTaskOptions} [options={}] The options for this ScheduledTask instance
	 */
	constructor(client, taskName, time, options = {}) {
		const [_time, _recurring] = this.constructor._resolveTime(time);

		/**
		 * The Client instance that initialized this instance
		 * @since 0.5.0
		 * @name ScheduledTask#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: client });

		/**
		 * The name of the Task this scheduled task will run
		 * @since 0.5.0
		 * @type {string}
		 */
		this.taskName = taskName;

		/**
		 * Whether this scheduled task is scheduled with the {@link Cron} pattern
		 * @since 0.5.0
		 * @type {?Cron}
		 */
		this.recurring = _recurring;

		/**
		 * The Date when this scheduled task ends
		 * @since 0.5.0
		 * @type {?Date}
		 */
		this.time = 'time' in options ? new Date(options.time) : _time;

		/**
		 * The id for this scheduled task
		 * @since 0.5.0
		 * @type {string}
		 */
		this.id = options.id || this.constructor._generateID(this.client);

		/**
		 * If the task should catch up in the event the bot is down
		 * @since 0.5.0
		 * @type {string}
		 */
		this.catchUp = 'catchUp' in options ? options.catchUp : true;

		/**
		 * The stored metadata to send to the Task
		 * @since 0.5.0
		 * @type {*}
		 */
		this.data = 'data' in options ? options.data : null;

		this.constructor._validate(this);
	}

	/**
	 * The Schedule class that manages all scheduled tasks
	 * @since 0.5.0
	 * @name ScheduledTask#store
	 * @type {Schedule}
	 * @readonly
	 */
	get store() {
		return this.client.schedule;
	}

	/**
	 * The Task instance this scheduled task will run
	 * @since 0.5.0
	 * @type {?Task}
	 * @readonly
	 */
	get task() {
		return this.client.tasks.get(this.taskName) || null;
	}

	/**
	 * Run the current task and bump it if needed
	 * @since 0.5.0
	 * @returns {this}
	 */
	async run() {
		if (!this.task || !this.task.enabled) return this;
		try {
			this.task.disable();
			await this.task.run(this.data);
			this.task.enable();
		} catch (err) {
			this.client.emit('taskError', this, this.task, err);
		}
		if (!this.recurring) return this.delete();
		return this.update({ time: this.recurring });
	}

	/**
	 * Update the task
	 * @since 0.5.0
	 * @param {ScheduledTaskUpdateOptions} options The options to update
	 * @returns {this}
	 * @example
	 * // Update the data from the current scheduled task. Let's say I want to change the reminder content to remind me
	 * // another thing
	 * ScheduledTask.update({ data: { content: 'Woo! I edited this reminder\'s content!' } });
	 *
	 * // But you can also update the time this will end at, for example, to change it so it ends in 1 hour:
	 * ScheduledTask.update({ time: Date.now() + 60000 * 60 });
	 */
	async update({ time, data, catchUp } = {}) {
		if (time) {
			const [_time, _cron] = this.constructor._resolveTime(time);
			this.time = _time;
			this.store.tasks.splice(this.store.tasks.indexOf(this), 1);
			this.store._insert(this);
			this.recurring = _cron;
		}
		if (data) this.data = data;
		if (typeof catchUp !== 'undefined') this.catchUp = catchUp;

		// Sync the database if some of the properties changed or the time changed manually
		// (recurring tasks bump the time automatically)
		const _index = this.store._tasks.findIndex(entry => entry.id === this.id);
		if (_index !== -1) await this.client.configs.update('schedules', this.toJSON(), { arrayPosition: _index });

		return this;
	}

	/**
	 * Delete the task
	 * @since 0.5.0
	 * @returns {Promise<Schedule>}
	 * @example
	 * ScheduledTask.delete()
	 *     .then(() => console.log('Successfully deleted the task'))
	 *     .catch(console.error);
	 */
	delete() {
		return this.store.delete(this.id);
	}

	/**
	 * Override for JSON.stringify
	 * @since 0.5.0
	 * @returns {ScheduledTaskJSON}
	 */
	toJSON() {
		const object = { id: this.id, taskName: this.taskName, time: this.time.getTime(), catchUp: this.catchUp };
		if (this.recurring) object.repeat = this.recurring.cron;
		if (typeof this.data !== 'undefined') object.data = this.data;

		return object;
	}

	/**
	 * Resolve the time and cron
	 * @since 0.5.0
	 * @param {(Date|number|Cron|string)} time The time or Cron pattern
	 * @returns {any[]}
	 * @private
	 */
	static _resolveTime(time) {
		if (time instanceof Date) return [time, null];
		if (time instanceof Cron) return [time.next(), time];
		if (typeof time === 'number') return [new Date(time), null];
		if (typeof time === 'string') {
			const cron = new Cron(time);
			return [cron.next(), cron];
		}
		throw new Error('invalid time passed');
	}

	/**
	 * Generate a new ID based on timestamp and shard
	 * @since 0.5.0
	 * @param {KlasaClient} client The Discord client
	 * @returns {string}
	 * @private
	 */
	static _generateID(client) {
		return Date.now().toString(36) + (client.shard ? client.shard.id.toString(36) : '') + String.fromCharCode((1 % 26) + 97);
	}

	/**
	 * Validate a task
	 * @since 0.5.0
	 * @param {ScheduledTask} st The task to validate
	 * @private
	 */
	static _validate(st) {
		if (!st.task) throw new Error('invalid task');
		if (!st.time) throw new Error('time or repeat option required');
	}

}

module.exports = ScheduledTask;
<commit_msg>Updated ScheduledTask to also pass ID (#252)<commit_after>const Cron = require('../util/Cron');

/**
 * The structure for future tasks to be run
 */
class ScheduledTask {

	/**
	 * @typedef  {Object} ScheduledTaskOptions
	 * @property {string} [id] The ID for the task. By default, it generates one in base36
	 * @property {boolean} [catchUp=true] If the task should try to catch up if the bot is down
	 * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution
	 */

	/**
	 * @typedef  {Object} ScheduledTaskUpdateOptions
	 * @property {string} [repeat] The {@link Cron} pattern
	 * @property {Date} [time] The time the current task ends at
	 * @property {boolean} [catchUp] If the task should try to catch up if the bot is down
	 * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution
	 */

	/**
	 * @typedef  {Object} ScheduledTaskJSON
	 * @property {string} id The task's ID
	 * @property {string} taskName The name of the Task piece this will execute
	 * @property {number} time The UNIX timestamp for when this task ends at
	 * @property {boolean} catchUp If the task should try to catch up if the bot is down
	 * @property {string} [repeat] The {@link Cron} pattern
	 * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution
	 */

	/**
	 * Initializes a new ScheduledTask
	 * @since 0.5.0
	 * @param {KlasaClient} client The client that initialized this instance
	 * @param {string} taskName The name of the task this ScheduledTask is for
	 * @param {(Date|number|string)} time The time or Cron pattern
	 * @param {ScheduledTaskOptions} [options={}] The options for this ScheduledTask instance
	 */
	constructor(client, taskName, time, options = {}) {
		const [_time, _recurring] = this.constructor._resolveTime(time);

		/**
		 * The Client instance that initialized this instance
		 * @since 0.5.0
		 * @name ScheduledTask#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: client });

		/**
		 * The name of the Task this scheduled task will run
		 * @since 0.5.0
		 * @type {string}
		 */
		this.taskName = taskName;

		/**
		 * Whether this scheduled task is scheduled with the {@link Cron} pattern
		 * @since 0.5.0
		 * @type {?Cron}
		 */
		this.recurring = _recurring;

		/**
		 * The Date when this scheduled task ends
		 * @since 0.5.0
		 * @type {?Date}
		 */
		this.time = 'time' in options ? new Date(options.time) : _time;

		/**
		 * The id for this scheduled task
		 * @since 0.5.0
		 * @type {string}
		 */
		this.id = options.id || this.constructor._generateID(this.client);

		/**
		 * If the task should catch up in the event the bot is down
		 * @since 0.5.0
		 * @type {string}
		 */
		this.catchUp = 'catchUp' in options ? options.catchUp : true;

		/**
		 * The stored metadata to send to the Task
		 * @since 0.5.0
		 * @type {*}
		 */
		this.data = 'data' in options ? options.data : null;

		this.constructor._validate(this);
	}

	/**
	 * The Schedule class that manages all scheduled tasks
	 * @since 0.5.0
	 * @name ScheduledTask#store
	 * @type {Schedule}
	 * @readonly
	 */
	get store() {
		return this.client.schedule;
	}

	/**
	 * The Task instance this scheduled task will run
	 * @since 0.5.0
	 * @type {?Task}
	 * @readonly
	 */
	get task() {
		return this.client.tasks.get(this.taskName) || null;
	}

	/**
	 * Run the current task and bump it if needed
	 * @since 0.5.0
	 * @returns {this}
	 */
	async run() {
		if (!this.task || !this.task.enabled) return this;
		try {
			this.task.disable();
			await this.task.run({ id: this.id, ...(this.data || {}) });
			this.task.enable();
		} catch (err) {
			this.client.emit('taskError', this, this.task, err);
		}
		if (!this.recurring) return this.delete();
		return this.update({ time: this.recurring });
	}

	/**
	 * Update the task
	 * @since 0.5.0
	 * @param {ScheduledTaskUpdateOptions} options The options to update
	 * @returns {this}
	 * @example
	 * // Update the data from the current scheduled task. Let's say I want to change the reminder content to remind me
	 * // another thing
	 * ScheduledTask.update({ data: { content: 'Woo! I edited this reminder\'s content!' } });
	 *
	 * // But you can also update the time this will end at, for example, to change it so it ends in 1 hour:
	 * ScheduledTask.update({ time: Date.now() + 60000 * 60 });
	 */
	async update({ time, data, catchUp } = {}) {
		if (time) {
			const [_time, _cron] = this.constructor._resolveTime(time);
			this.time = _time;
			this.store.tasks.splice(this.store.tasks.indexOf(this), 1);
			this.store._insert(this);
			this.recurring = _cron;
		}
		if (data) this.data = data;
		if (typeof catchUp !== 'undefined') this.catchUp = catchUp;

		// Sync the database if some of the properties changed or the time changed manually
		// (recurring tasks bump the time automatically)
		const _index = this.store._tasks.findIndex(entry => entry.id === this.id);
		if (_index !== -1) await this.client.configs.update('schedules', this.toJSON(), { arrayPosition: _index });

		return this;
	}

	/**
	 * Delete the task
	 * @since 0.5.0
	 * @returns {Promise<Schedule>}
	 * @example
	 * ScheduledTask.delete()
	 *     .then(() => console.log('Successfully deleted the task'))
	 *     .catch(console.error);
	 */
	delete() {
		return this.store.delete(this.id);
	}

	/**
	 * Override for JSON.stringify
	 * @since 0.5.0
	 * @returns {ScheduledTaskJSON}
	 */
	toJSON() {
		const object = { id: this.id, taskName: this.taskName, time: this.time.getTime(), catchUp: this.catchUp };
		if (this.recurring) object.repeat = this.recurring.cron;
		if (typeof this.data !== 'undefined') object.data = this.data;

		return object;
	}

	/**
	 * Resolve the time and cron
	 * @since 0.5.0
	 * @param {(Date|number|Cron|string)} time The time or Cron pattern
	 * @returns {any[]}
	 * @private
	 */
	static _resolveTime(time) {
		if (time instanceof Date) return [time, null];
		if (time instanceof Cron) return [time.next(), time];
		if (typeof time === 'number') return [new Date(time), null];
		if (typeof time === 'string') {
			const cron = new Cron(time);
			return [cron.next(), cron];
		}
		throw new Error('invalid time passed');
	}

	/**
	 * Generate a new ID based on timestamp and shard
	 * @since 0.5.0
	 * @param {KlasaClient} client The Discord client
	 * @returns {string}
	 * @private
	 */
	static _generateID(client) {
		return Date.now().toString(36) + (client.shard ? client.shard.id.toString(36) : '') + String.fromCharCode((1 % 26) + 97);
	}

	/**
	 * Validate a task
	 * @since 0.5.0
	 * @param {ScheduledTask} st The task to validate
	 * @private
	 */
	static _validate(st) {
		if (!st.task) throw new Error('invalid task');
		if (!st.time) throw new Error('time or repeat option required');
	}

}

module.exports = ScheduledTask;
<|endoftext|>


<commit_before>
var _ = require('underscore')
, EventEmitter = require('events').EventEmitter
;

var RedisEmitter = exports.RedisEmitter = function(redisSub) {
    this._redisSub = redisSub;
    var emitter = this._emitter = new EventEmitter();
    redisSub.on('message', function (channel, message) {
	emitter.emit(channel, _.toArray(arguments));
    });
};

_.extend(RedisEmitter.prototype, {
    subscribe: function (channel, listener) {
	var _this = this
	, emitter = _this._emitter;
	;
	emitter.on(channel, listener);
	if (_this._numListeners(channel) === 1) {
	    _this._redisSub.subscribe(channel);
	}

    }
    , unsubscribe: function (channel, listener) {
	var _this = this
	, emitter = _this._emitter
	;
	emitter.removeListener(channel, listener);
	if (_this._numListeners(channel) < 1) {
	    _this._redisSub.unsubscribe(channel);
	}
    }
    , _numListeners: function (channel) {
	return this._emitter.listeners(channel).length;
    }
})<commit_msg>Update redisEmitter so that complex messages are transmitted.<commit_after>
var _ = require('underscore')
, EventEmitter = require('events').EventEmitter
;

var RedisEmitter = exports.RedisEmitter = function(redisSub) {
    this._redisSub = redisSub;
    var emitter = this._emitter = new EventEmitter();
    redisSub.on('message', function (channel) {
	var args = [channel].concat(_.map(_.toArray(arguments).slice(1), JSON.parse));
	emitter.emit(channel, args);
    });
};

_.extend(RedisEmitter.prototype, {
    subscribe: function (channel, listener) {
	var _this = this
	, emitter = _this._emitter;
	;
	emitter.on(channel, listener);
	if (_this._numListeners(channel) === 1) {
	    _this._redisSub.subscribe(channel);
	}

    }
    , unsubscribe: function (channel, listener) {
	var _this = this
	, emitter = _this._emitter
	;
	emitter.removeListener(channel, listener);
	if (_this._numListeners(channel) < 1) {
	    _this._redisSub.unsubscribe(channel);
	}
    }
    , _numListeners: function (channel) {
	return this._emitter.listeners(channel).length;
    }
})<|endoftext|>


<commit_before>// npm modules
var React = require('react');

var TopBar = require('./TopBar');
var UserData = require('./UserData');
var Content = require('./Content');

var Issue = React.createClass({
  render: function () {
    return (

        <TopBar issueURL={this.props.issueURL} issueNumber={this.props.issueNumber}/>
        <UserData avatarURL={this.props.user.avatar_url} userURL={this.props.user.html_url} username={this.props.user.login} />
        <Content url={this.props.url} title={this.props.title} labels={this.props.labels}>
          {this.props.children}
        </Content>

    );
  }
});

module.exports = Issue;
<commit_msg>fix URL<commit_after>// npm modules
var React = require('react');

var TopBar = require('./TopBar');
var UserData = require('./UserData');
var Content = require('./Content');

var Issue = React.createClass({
  render: function () {
    return (

        <TopBar issueURL={this.props.url} issueNumber={this.props.issueNumber}/>
        <UserData avatarURL={this.props.user.avatar_url} userURL={this.props.user.html_url} username={this.props.user.login} />
        <Content url={this.props.url} title={this.props.title} labels={this.props.labels}>
          {this.props.children}
        </Content>

    );
  }
});

module.exports = Issue;
<|endoftext|>


<commit_before>var Gaffa = require('gaffa'),
    crel = require('crel'),
    doc = require('doc-js'),
    statham = require('statham'),
    viewType = "select",
    cachedElement;

function Select(){}
Select = Gaffa.createSpec(Select, Gaffa.View);
Select.prototype.type = viewType;

Select.prototype.render = function(){
    var viewModel = this,
        select,
        renderedElement = crel('span',
            select = crel('select')
        );

    renderedElement.className = 'select';

    doc.on(this.updateEventName || "change", select, function(event){
        var option,
            data;

        for(var i = 0; i < select.childNodes.length; i++){
            if(select.childNodes[i].value === select.value){
                option = select.childNodes[i];
            }
        }

        data = option && option.data || undefined;

        viewModel.value.set(data);
    });

    this.renderedElement = renderedElement;

};

Select.prototype.options = new Gaffa.Property({
    elements: [],
    update: function(viewModel, value) {
        var property = this,
            element = viewModel.renderedElement.childNodes[0];

        if(!element){
            return;
        }

        element.innerHTML = '';
        property.elements = [];

        if(!value){
            return;
        }

        if(viewModel.showBlank.value)
        {
            element.appendChild(document.createElement("option"));
        }

        for(var key in value){
            var optionData = value[key];
            if(optionData !== undefined){
                var option = document.createElement('option');

                option.value = option.data = property.valueBinding ? gaffa.gedi.get(property.valueBinding, property.getPath(), {option: optionData}) : optionData;
                option.textContent = property.textBinding ? gaffa.gedi.get(property.textBinding, property.getPath(), {option: optionData}) : optionData;

                element.appendChild(option);
                property.elements.push(option);
            }
        }

        element.value = null;
        viewModel.value.update(viewModel, viewModel.value.value);
    }
});

Select.prototype.value = new Gaffa.Property({
    update: function(viewModel, value) {
        viewModel.renderedElement.childNodes[0].value = value;
        for(var i = 0; i < viewModel.options.elements.length; i++){
            if(viewModel.options.elements[i].data === value){
                viewModel.options.elements[i].selected = true;
                break;
            }
        }
    },
    sameAsPrevious: function(){
        var oldHash = this.getPreviousHash(),
            newHash = statham.stringify(this.value);

        this.setPreviousHash(newHash);

        return oldHash === newHash;
    }
});

Select.prototype.showBlank = new Gaffa.Property();

module.exports = Select;<commit_msg>Added required property<commit_after>var Gaffa = require('gaffa'),
    crel = require('crel'),
    doc = require('doc-js'),
    statham = require('statham'),
    viewType = "select",
    cachedElement;

function Select(){}
Select = Gaffa.createSpec(Select, Gaffa.View);
Select.prototype.type = viewType;

Select.prototype.render = function(){
    var view = this,
        select,
        renderedElement = crel('span',
            select = crel('select')
        );

    renderedElement.className = 'select';

    doc.on(this.updateEventName || "change", select, function(event){
        var option,
            data;

        for(var i = 0; i < select.childNodes.length; i++){
            if(select.childNodes[i].value === select.value){
                option = select.childNodes[i];
            }
        }

        data = option && option.data || undefined;

        view.value.set(data);
    });

    this.renderedElement = renderedElement;

};

Select.prototype.options = new Gaffa.Property({
    elements: [],
    update: function(view, value) {
        var property = this,
            element = view.renderedElement.childNodes[0];

        if(!element){
            return;
        }

        element.innerHTML = '';
        property.elements = [];

        if(!value){
            return;
        }

        if(view.showBlank.value)
        {
            element.appendChild(document.createElement("option"));
        }

        for(var key in value){
            var optionData = value[key];
            if(optionData !== undefined){
                var option = document.createElement('option');

                option.value = option.data = property.valueBinding ? gaffa.gedi.get(property.valueBinding, property.getPath(), {option: optionData}) : optionData;
                option.textContent = property.textBinding ? gaffa.gedi.get(property.textBinding, property.getPath(), {option: optionData}) : optionData;

                element.appendChild(option);
                property.elements.push(option);
            }
        }

        element.value = null;
        view.value.update(view, view.value.value);
    }
});

Select.prototype.value = new Gaffa.Property({
    update: function(view, value) {
        view.renderedElement.childNodes[0].value = value;
        for(var i = 0; i < view.options.elements.length; i++){
            if(view.options.elements[i].data === value){
                view.options.elements[i].selected = true;
                break;
            }
        }
    },
    sameAsPrevious: function(){
        var oldHash = this.getPreviousHash(),
            newHash = statham.stringify(this.value);

        this.setPreviousHash(newHash);

        return oldHash === newHash;
    }
});

Select.prototype.showBlank = new Gaffa.Property();

Select.prototype.required = new Gaffa.Property(function(view, value){
    if (value){
        view.renderedElement.setAttribute('required', 'required');
    }else{
        view.renderedElement.removeAttribute('required');
    }
});

module.exports = Select;<|endoftext|>


<commit_before>/* eslint-disable no-continue */
/* eslint-disable no-restricted-syntax */

const got = require('got');
const cheerio = require('cheerio');
const fuzz = require('fuzzball');
const moment = require('moment-timezone');
const { logger } = require('../middleware/logger');

const REDDIT_WIKI = 'https://old.reddit.com/r/spacex/wiki/launches/manifest';
const API = process.env.SPACEX_API;
const KEY = process.env.SPACEX_KEY;
const HEALTHCHECK = process.env.UPCOMING_HEALTHCHECK;

/**
 * This script gathers dates and payload names from the subreddit launch wiki,
 * fuzzy checks them against existing upcoming mission names and updates the date if a
 * change is made in the wiki. The proper time zone is calculated from the launch site
 * id of the launch. It also corrects the flight number order based on the launch wiki order.
 * @return {Promise<void>}
 */
module.exports = async () => {
  try {
    const flightNumbers = [];
    const rawLaunches = await got.post(`${API}/launches/query`, {
      json: {
        options: {
          pagination: false,
          sort: {
            flight_number: 'asc',
          },
        },
      },
      resolveBodyOnly: true,
      responseType: 'json',
    });

    // Past launches needed to set new flight number order
    const upcoming = rawLaunches.docs.filter((doc) => doc.upcoming === true);
    const past = rawLaunches.docs.filter((doc) => doc.upcoming === false);

    // Grab subreddit wiki
    const rawWiki = await got(REDDIT_WIKI, {
      resolveBodyOnly: true,
    });
    const $ = cheerio.load(rawWiki);
    const wiki = $('body > div.content > div > div > table:nth-child(7) > tbody').text();

    if (!wiki) {
      throw new Error(`Broken wiki selector: ${wiki}`);
    }

    const wikiRow = wiki.split('\n').filter((v) => v !== '');

    const allWikiDates = wikiRow.filter((_, index) => index % 7 === 0);
    const wikiDates = allWikiDates.slice(0, 30).map((date) => date
      // eslint-disable-next-line security/detect-unsafe-regex
      .replace(/(?<=\[[0-9]{2}:[0-9]{2}\])(\[[0-9]{1,3}\]|\[[0-9]{1,3}|[0-9]{1,3}\])*/gi, '')
      .replace(/~|(\[|\[\[)[0-9]{1,3}\]/gi, '')
      .replace(/~|(\[|\])/gi, '')
      .replace(/(early|mid|late|end|tbd|tba|net)/gi, ' ')
      .split('/')[0].trim());
    console.log(wikiDates);
    const rawWikiDates = allWikiDates.slice(0, 30);

    const allWikiPayloads = wikiRow.filter((_, index) => (index + 2) % 7 === 0);
    const wikiPayloads = allWikiPayloads.slice(0, 30).map((payload) => payload.replace(/\[[0-9]{1,3}\]/gi, ''));

    const allWikiLaunchpads = wikiRow.filter((_, index) => (index + 5) % 7 === 0);
    const wikiLaunchpads = allWikiLaunchpads.slice(0, 30).map((launchpad) => launchpad.replace(/\[[0-9]{1,3}\]/gi, ''));

    // Set base flight number to automatically reorder launches on the wiki
    // If the most recent past launch is still on the wiki, don't offset the flight number
    let baseFlightNumber;
    if (fuzz.partial_ratio(past[past.length - 1].name, wikiPayloads[0]) === 100) {
      baseFlightNumber = past[past.length - 1].flight_number;
    } else {
      baseFlightNumber = past[past.length - 1].flight_number + 1;
    }

    // Compare each mission name against entire list of wiki payloads, and fuzzy match the
    // mission name against the wiki payload name. The partial match must be 100%, to avoid
    // conflicts like SSO-A and SSO-B, where a really close match would produce wrong results.
    for await (const [, launch] of upcoming.entries()) {
      // Allow users to pause auto updates from wiki, while still preserving
      // flight reordering feature
      if (!launch.auto_update) {
        continue;
      }
      for await (const [wikiIndex, wikiPayload] of wikiPayloads.entries()) {
        if (fuzz.partial_ratio(launch.name, wikiPayload) === 100) {
          // Special check for starlink / smallsat launches, because Starlink 2 and Starlink 23
          // both pass the partial ratio check, so they are checked strictly below
          if (/starlink/i.test(launch.name) && fuzz.ratio(launch.name, wikiPayload) !== 100) {
            continue;
          }

          // Check and see if dates match a certain pattern depending on the length of the
          // date given. This sets the amount of precision needed for the date.
          // Allows for long months or short months ex: September vs Sep
          // Allows for time with or without brackets ex: [23:45] vs 23:45

          // Anything with NET in date
          const netPattern = /^.*(net).*$/i;

          // Anything with TBD/TBA in date
          const tbdPattern = /^.*(tbd|tba).*$/i;

          // 2020
          const yearPattern = /^\s*[0-9]{4}\s*$/i;

          // 2020 [14:10]
          const yearHourPattern = /^\s*[0-9]{4}\s*(\[?\s*[0-9]{2}:[0-9]{2}\s*\]?)\s*$/i;

          // 2020 Nov
          const monthPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*$/i;

          // 2020 Nov 4
          const dayPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*[0-9]{1,2}\s*$/i;

          // 2020 Nov [14:10]
          const vagueHourPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*(\[?\s*[0-9]{2}:[0-9]{2}\s*\]?)\s*$/i;

          // 2020 Nov 4 [14:10]
          const hourPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*[0-9]{1,2}\s*(\[?\s*[0-9]{2}:[0-9]{2}\s*\]?)\s*$/i;

          let precision;
          let wikiDate = wikiDates[parseInt(wikiIndex, 10)];
          const rawWikiDate = rawWikiDates[parseInt(wikiIndex, 10)];

          // Check if date is NET
          const net = netPattern.test(rawWikiDate);

          // Check if date contains TBD
          const tbd = tbdPattern.test(rawWikiDate);

          // Remove extra stuff humans might add
          // NOTE: Add to this when people add unexpected things to dates in the wiki
          const cleanedwikiDate = wikiDate;

          // Set date precision
          if (cleanedwikiDate.includes('Q')) {
            // Quarter is first because moment.js does not make
            // a distinction between half vs quarter. Therefore
            // the first half starts at the beginning Q1, and the
            // second half starts at the beginning of Q3
            wikiDate = wikiDate.replace('Q', '');
            precision = 'quarter';
          } else if (cleanedwikiDate.includes('H1')) {
            wikiDate = wikiDate.replace('H1', '1');
            precision = 'half';
          } else if (cleanedwikiDate.includes('H2')) {
            wikiDate = wikiDate.replace('H2', '3');
            precision = 'half';
          } else if (yearPattern.test(cleanedwikiDate)) {
            precision = 'year';
          } else if (yearHourPattern.test(cleanedwikiDate)) {
            precision = 'year';
          } else if (monthPattern.test(cleanedwikiDate)) {
            precision = 'month';
          } else if (dayPattern.test(cleanedwikiDate)) {
            precision = 'day';
          } else if (vagueHourPattern.test(cleanedwikiDate)) {
            precision = 'month';
          } else if (hourPattern.test(cleanedwikiDate)) {
            precision = 'hour';
          } else {
            throw new Error(`No date match: ${cleanedwikiDate}`);
          }

          // Add flight numbers to array to check for duplicates
          flightNumbers.push(baseFlightNumber + wikiIndex);

          // Wiki launchpad matchers
          const slc40Pattern = /^SLC-40.*$/i;
          const lc39aPattern = /^LC-39A.*$/i;
          const slc4ePattern = /^SLC-4E.*$/i;
          const bcPattern = /^BC.*$/i;

          // Calculate launch site depending on wiki manifest
          const launchpad = wikiLaunchpads[parseInt(wikiIndex, 10)];
          let queryName;
          if (slc40Pattern.test(launchpad)) {
            queryName = 'CCSFS SLC 40';
          } else if (lc39aPattern.test(launchpad)) {
            queryName = 'KSC LC 39A';
          } else if (slc4ePattern.test(launchpad)) {
            queryName = 'VAFB SLC 4E';
          } else if (bcPattern.test(launchpad)) {
            queryName = 'STLS';
          } else {
            throw new Error(`No launchpad match: ${launchpad}`);
          }
          const launchpads = await got.post(`${API}/launchpads/query`, {
            json: {
              query: {
                name: queryName,
              },
              options: {
                limit: 1,
              },
            },
            resolveBodyOnly: true,
            responseType: 'json',
          });
          const launchpadId = launchpads.docs[0].id;
          const { timezone } = launchpads.docs[0];

          // Clean wiki date, set timezone
          const parsedDate = `${wikiDates[parseInt(wikiIndex, 10)].replace(/(-|\[|\]|~|early|mid|late|end|net)/gi, ' ').split('/')[0].trim()}`;
          const time = moment(parsedDate, ['YYYY MMM HH:mm', 'YYYY MMM D HH:mm', 'YYYY MMM D', 'YYYY MMM', 'YYYY HH:mm', 'YYYY Q', 'YYYY']);
          const zone = moment.tz(time, 'UTC');
          const localTime = time.tz(timezone).format();

          const rawUpdate = {
            flight_number: (baseFlightNumber + wikiIndex),
            date_unix: zone.unix(),
            date_utc: zone.toISOString(),
            date_local: localTime,
            date_precision: precision,
            launchpad: launchpadId,
            tbd,
            net,
          };

          logger.info({
            launch: launch.name,
            ...rawUpdate,
          });

          await got.patch(`${API}/launches/${launch.id}`, {
            json: {
              ...rawUpdate,
            },
            headers: {
              'spacex-key': KEY,
            },
          });
        }
      }
    }

    if (HEALTHCHECK) {
      await got(HEALTHCHECK);
    }
  } catch (error) {
    console.log(`Upcoming Launch Error: ${error.message}`);
  }
};
<commit_msg>Remove date ranges when parsing upcoming dates<commit_after>/* eslint-disable no-continue */
/* eslint-disable no-restricted-syntax */

const got = require('got');
const cheerio = require('cheerio');
const fuzz = require('fuzzball');
const moment = require('moment-timezone');
const { logger } = require('../middleware/logger');

const REDDIT_WIKI = 'https://old.reddit.com/r/spacex/wiki/launches/manifest';
const API = process.env.SPACEX_API;
const KEY = process.env.SPACEX_KEY;
const HEALTHCHECK = process.env.UPCOMING_HEALTHCHECK;

/**
 * This script gathers dates and payload names from the subreddit launch wiki,
 * fuzzy checks them against existing upcoming mission names and updates the date if a
 * change is made in the wiki. The proper time zone is calculated from the launch site
 * id of the launch. It also corrects the flight number order based on the launch wiki order.
 * @return {Promise<void>}
 */
module.exports = async () => {
  try {
    const flightNumbers = [];
    const rawLaunches = await got.post(`${API}/launches/query`, {
      json: {
        options: {
          pagination: false,
          sort: {
            flight_number: 'asc',
          },
        },
      },
      resolveBodyOnly: true,
      responseType: 'json',
    });

    // Past launches needed to set new flight number order
    const upcoming = rawLaunches.docs.filter((doc) => doc.upcoming === true);
    const past = rawLaunches.docs.filter((doc) => doc.upcoming === false);

    // Grab subreddit wiki
    const rawWiki = await got(REDDIT_WIKI, {
      resolveBodyOnly: true,
    });
    const $ = cheerio.load(rawWiki);
    const wiki = $('body > div.content > div > div > table:nth-child(7) > tbody').text();

    if (!wiki) {
      throw new Error(`Broken wiki selector: ${wiki}`);
    }

    const wikiRow = wiki.split('\n').filter((v) => v !== '');

    const allWikiDates = wikiRow.filter((_, index) => index % 7 === 0);
    const wikiDates = allWikiDates.slice(0, 30).map((date) => date
      // eslint-disable-next-line security/detect-unsafe-regex
      .replace(/(?<=\[[0-9]{2}:[0-9]{2}\])(\[[0-9]{1,3}\]|\[[0-9]{1,3}|[0-9]{1,3}\])*/gi, '')
      .replace(/~|(\[|\[\[)[0-9]{1,3}\]/gi, '')
      .replace(/~|(\[|\])/gi, '')
      .replace(/(early|mid|late|end|tbd|tba|net)/gi, ' ')
      .replace(/-[0-9]{2}:[0-9]{2}/gi, ' ')
      .split('/')[0].trim());
    const rawWikiDates = allWikiDates.slice(0, 30);

    const allWikiPayloads = wikiRow.filter((_, index) => (index + 2) % 7 === 0);
    const wikiPayloads = allWikiPayloads.slice(0, 30).map((payload) => payload.replace(/\[[0-9]{1,3}\]/gi, ''));

    const allWikiLaunchpads = wikiRow.filter((_, index) => (index + 5) % 7 === 0);
    const wikiLaunchpads = allWikiLaunchpads.slice(0, 30).map((launchpad) => launchpad.replace(/\[[0-9]{1,3}\]/gi, ''));

    // Set base flight number to automatically reorder launches on the wiki
    // If the most recent past launch is still on the wiki, don't offset the flight number
    let baseFlightNumber;
    if (fuzz.partial_ratio(past[past.length - 1].name, wikiPayloads[0]) === 100) {
      baseFlightNumber = past[past.length - 1].flight_number;
    } else {
      baseFlightNumber = past[past.length - 1].flight_number + 1;
    }

    // Compare each mission name against entire list of wiki payloads, and fuzzy match the
    // mission name against the wiki payload name. The partial match must be 100%, to avoid
    // conflicts like SSO-A and SSO-B, where a really close match would produce wrong results.
    for await (const [, launch] of upcoming.entries()) {
      // Allow users to pause auto updates from wiki, while still preserving
      // flight reordering feature
      if (!launch.auto_update) {
        continue;
      }
      for await (const [wikiIndex, wikiPayload] of wikiPayloads.entries()) {
        if (fuzz.partial_ratio(launch.name, wikiPayload) === 100) {
          // Special check for starlink / smallsat launches, because Starlink 2 and Starlink 23
          // both pass the partial ratio check, so they are checked strictly below
          if (/starlink/i.test(launch.name) && fuzz.ratio(launch.name, wikiPayload) !== 100) {
            continue;
          }

          // Check and see if dates match a certain pattern depending on the length of the
          // date given. This sets the amount of precision needed for the date.
          // Allows for long months or short months ex: September vs Sep
          // Allows for time with or without brackets ex: [23:45] vs 23:45

          // Anything with NET in date
          const netPattern = /^.*(net).*$/i;

          // Anything with TBD/TBA in date
          const tbdPattern = /^.*(tbd|tba).*$/i;

          // 2020
          const yearPattern = /^\s*[0-9]{4}\s*$/i;

          // 2020 [14:10]
          const yearHourPattern = /^\s*[0-9]{4}\s*(\[?\s*[0-9]{2}:[0-9]{2}\s*\]?)\s*$/i;

          // 2020 Nov
          const monthPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*$/i;

          // 2020 Nov 4
          const dayPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*[0-9]{1,2}\s*$/i;

          // 2020 Nov [14:10]
          const vagueHourPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*(\[?\s*[0-9]{2}:[0-9]{2}\s*\]?)\s*$/i;

          // 2020 Nov 4 [14:10]
          const hourPattern = /^\s*[0-9]{4}\s*([a-z]{3}|[a-z]{3,9})\s*[0-9]{1,2}\s*(\[?\s*[0-9]{2}:[0-9]{2}\s*\]?)\s*$/i;

          let precision;
          let wikiDate = wikiDates[parseInt(wikiIndex, 10)];
          const rawWikiDate = rawWikiDates[parseInt(wikiIndex, 10)];

          // Check if date is NET
          const net = netPattern.test(rawWikiDate);

          // Check if date contains TBD
          const tbd = tbdPattern.test(rawWikiDate);

          // Remove extra stuff humans might add
          // NOTE: Add to this when people add unexpected things to dates in the wiki
          const cleanedwikiDate = wikiDate;

          // Set date precision
          if (cleanedwikiDate.includes('Q')) {
            // Quarter is first because moment.js does not make
            // a distinction between half vs quarter. Therefore
            // the first half starts at the beginning Q1, and the
            // second half starts at the beginning of Q3
            wikiDate = wikiDate.replace('Q', '');
            precision = 'quarter';
          } else if (cleanedwikiDate.includes('H1')) {
            wikiDate = wikiDate.replace('H1', '1');
            precision = 'half';
          } else if (cleanedwikiDate.includes('H2')) {
            wikiDate = wikiDate.replace('H2', '3');
            precision = 'half';
          } else if (yearPattern.test(cleanedwikiDate)) {
            precision = 'year';
          } else if (yearHourPattern.test(cleanedwikiDate)) {
            precision = 'year';
          } else if (monthPattern.test(cleanedwikiDate)) {
            precision = 'month';
          } else if (dayPattern.test(cleanedwikiDate)) {
            precision = 'day';
          } else if (vagueHourPattern.test(cleanedwikiDate)) {
            precision = 'month';
          } else if (hourPattern.test(cleanedwikiDate)) {
            precision = 'hour';
          } else {
            throw new Error(`No date match: ${cleanedwikiDate}`);
          }

          // Add flight numbers to array to check for duplicates
          flightNumbers.push(baseFlightNumber + wikiIndex);

          // Wiki launchpad matchers
          const slc40Pattern = /^SLC-40.*$/i;
          const lc39aPattern = /^LC-39A.*$/i;
          const slc4ePattern = /^SLC-4E.*$/i;
          const bcPattern = /^BC.*$/i;

          // Calculate launch site depending on wiki manifest
          const launchpad = wikiLaunchpads[parseInt(wikiIndex, 10)];
          let queryName;
          if (slc40Pattern.test(launchpad)) {
            queryName = 'CCSFS SLC 40';
          } else if (lc39aPattern.test(launchpad)) {
            queryName = 'KSC LC 39A';
          } else if (slc4ePattern.test(launchpad)) {
            queryName = 'VAFB SLC 4E';
          } else if (bcPattern.test(launchpad)) {
            queryName = 'STLS';
          } else {
            throw new Error(`No launchpad match: ${launchpad}`);
          }
          const launchpads = await got.post(`${API}/launchpads/query`, {
            json: {
              query: {
                name: queryName,
              },
              options: {
                limit: 1,
              },
            },
            resolveBodyOnly: true,
            responseType: 'json',
          });
          const launchpadId = launchpads.docs[0].id;
          const { timezone } = launchpads.docs[0];

          // Clean wiki date, set timezone
          const parsedDate = `${wikiDates[parseInt(wikiIndex, 10)].replace(/(-|\[|\]|~|early|mid|late|end|net)/gi, ' ').split('/')[0].trim()}`;
          const time = moment(parsedDate, ['YYYY MMM HH:mm', 'YYYY MMM D HH:mm', 'YYYY MMM D', 'YYYY MMM', 'YYYY HH:mm', 'YYYY Q', 'YYYY']);
          const zone = moment.tz(time, 'UTC');
          const localTime = time.tz(timezone).format();

          const rawUpdate = {
            flight_number: (baseFlightNumber + wikiIndex),
            date_unix: zone.unix(),
            date_utc: zone.toISOString(),
            date_local: localTime,
            date_precision: precision,
            launchpad: launchpadId,
            tbd,
            net,
          };

          logger.info({
            launch: launch.name,
            ...rawUpdate,
          });

          await got.patch(`${API}/launches/${launch.id}`, {
            json: {
              ...rawUpdate,
            },
            headers: {
              'spacex-key': KEY,
            },
          });
        }
      }
    }

    if (HEALTHCHECK) {
      await got(HEALTHCHECK);
    }
  } catch (error) {
    console.log(`Upcoming Launch Error: ${error.message}`);
  }
};
<|endoftext|>