<commit_before>$.fn.romoTooltip = function() {
  return $.map(this, function(element) {
    return new RomoTooltip(element)
  })
}

var RomoTooltip = function(element) {
  this.elem = $(element)
  this.popupElem = $('

')
  this.popupElem.appendTo('body')
  this.arrowElem = this.popupElem.find('> .romo-tooltip-arrow')
  this.bodyElem  = this.popupElem.find('> .romo-tooltip-body')

  this.hoverState = 'out'
  this.delayEnter = 0
  this.delayLeave = 0
  if (this.elem.data('romo-tooltip-delay') !== undefined && this.elem.data('romo-tooltip-delay') !== '') {
    this.delayEnter = this.elem.data('romo-tooltip-delay')
    this.delayLeave = this.elem.data('romo-tooltip-delay')
  }
  if (this.elem.data('romo-tooltip-delay-enter') !== undefined && this.elem.data('romo-tooltip-delay-enter') !== '') {
    this.delayEnter = this.elem.data('romo-tooltip-delay-enter')
  }
  if (this.elem.data('romo-tooltip-delay-leave') !== undefined && this.elem.data('romo-tooltip-delay-leave') !== '') {
    this.delayLeave = this.elem.data('romo-tooltip-delay-leave')
  }

  this.popupPosition = this.elem.data('romo-tooltip-position') || 'top'
  this.popupElem.attr('data-romo-tooltip-position', this.popupPosition)
  // don't propagate click events on the popup elem.  this prevents the popup
  // from closing when clicked (see body click event bind on popup open)
  this.popupElem.on('click', function(e) {
    if (e !== undefined) {
      e.stopPropagation()
    }
  })
  this.bodyElem.addClass(this.elem.data('romo-tooltip-style-class'))
  this.bodyElem.css({
    'min-width':  this.elem.data('romo-tooltip-min-width'),
    'max-width':  this.elem.data('romo-tooltip-max-width'),
    'width':      this.elem.data('romo-tooltip-width'),
    'min-height': this.elem.data('romo-tooltip-min-height'),
    'max-height': this.elem.data('romo-tooltip-max-height'),
    'height':     this.elem.data('romo-tooltip-height')
  })
  this.bodyElem.html(this.elem.data('romo-tooltip-content') || '')

  // TODO: tooltip-href handling
  this.elem.on('mouseenter', $.proxy(this.onToggleEnter, this))
  this.elem.on('mouseleave', $.proxy(this.onToggleLeave, this))
  $(window).on('resize', $.proxy(this.onResizeWindow, this))

  this.doInit()
  this.elem.trigger('tooltip:ready', [this])
}

RomoTooltip.prototype.doInit = function() {
  // override as needed
}

RomoTooltip.prototype.onToggleEnter = function(e) {
  if (e !== undefined) {
    e.preventDefault()
  }

  this.hoverState = 'in'
  if (this.elem.hasClass('disabled') === false) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout($.proxy(function() {
      if (this.hoverState ==='in') {
        this.doPopupOpen()
      }
    }, this), this.delayEnter)
  }
}

RomoTooltip.prototype.onToggleLeave = function(e) {
  if (e !== undefined) {
    e.preventDefault()
  }

  this.hoverState = 'out'
  if (this.elem.hasClass('disabled') === false) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout($.proxy(function() {
      if (this.hoverState === 'out') {
        this.doPopupClose()
      }
    }, this), this.delayLeave)
  }
}

RomoTooltip.prototype.onResizeWindow = function(e) {
  if (this.elem.hasClass('disabled') === false && this.hoverState === 'in') {
    this.doPlacePopupElem()
  }
}

RomoTooltip.prototype.doPopupOpen = function() {
  this.popupElem.addClass('romo-tooltip-open')
  this.doPlacePopupElem()

  this.elem.trigger('tooltip:popupOpen', [this])
}

RomoTooltip.prototype.doPopupClose = function() {
  this.popupElem.removeClass('romo-tooltip-open')

  this.elem.trigger('tooltip:popupClose', [this])
}

RomoTooltip.prototype.doPlacePopupElem = function() {
  var pos = $.extend({}, this.elem[0].getBoundingClientRect(), this.elem.offset())
  var w = this.popupElem[0].offsetWidth
  var h = this.popupElem[0].offsetHeight
  var pad = 6 + 1 // arrow size + spacing
  var offset = {}

  switch (this.popupPosition) {
    case 'top':
      $.extend(offset, { top: pos.top - h - pad, left: pos.left + pos.width / 2 - w / 2 })
      break
    case 'bottom':
      $.extend(offset, { top: pos.top + pos.height + pad, left: pos.left + pos.width / 2 - w / 2 })
      break
    case 'left':
      $.extend(offset, { top: pos.top + pos.height / 2 - h / 2, left: pos.left - w - pad })
      break
    case 'right':
      $.extend(offset, { top: pos.top + pos.height / 2 - h / 2, left: pos.left + pos.width + pad })
      break
  }

  this.popupElem.offset(offset)
}

Romo.onInitUI(function(e) {
  $(e.target).find('[data-romo-tooltip-auto="true"]').romoTooltip()
})
<commit_msg>tooltip: trigger events<commit_after>$.fn.romoTooltip = function() {
  return $.map(this, function(element) {
    return new RomoTooltip(element)
  })
}

var RomoTooltip = function(element) {
  this.elem = $(element)
  this.popupElem = $('

')
  this.popupElem.appendTo('body')
  this.arrowElem = this.popupElem.find('> .romo-tooltip-arrow')
  this.bodyElem  = this.popupElem.find('> .romo-tooltip-body')

  this.hoverState = 'out'
  this.delayEnter = 0
  this.delayLeave = 0
  if (this.elem.data('romo-tooltip-delay') !== undefined && this.elem.data('romo-tooltip-delay') !== '') {
    this.delayEnter = this.elem.data('romo-tooltip-delay')
    this.delayLeave = this.elem.data('romo-tooltip-delay')
  }
  if (this.elem.data('romo-tooltip-delay-enter') !== undefined && this.elem.data('romo-tooltip-delay-enter') !== '') {
    this.delayEnter = this.elem.data('romo-tooltip-delay-enter')
  }
  if (this.elem.data('romo-tooltip-delay-leave') !== undefined && this.elem.data('romo-tooltip-delay-leave') !== '') {
    this.delayLeave = this.elem.data('romo-tooltip-delay-leave')
  }

  this.popupPosition = this.elem.data('romo-tooltip-position') || 'top'
  this.popupElem.attr('data-romo-tooltip-position', this.popupPosition)
  // don't propagate click events on the popup elem.  this prevents the popup
  // from closing when clicked (see body click event bind on popup open)
  this.popupElem.on('click', function(e) {
    if (e !== undefined) {
      e.stopPropagation()
    }
  })
  this.bodyElem.addClass(this.elem.data('romo-tooltip-style-class'))
  this.bodyElem.css({
    'min-width':  this.elem.data('romo-tooltip-min-width'),
    'max-width':  this.elem.data('romo-tooltip-max-width'),
    'width':      this.elem.data('romo-tooltip-width'),
    'min-height': this.elem.data('romo-tooltip-min-height'),
    'max-height': this.elem.data('romo-tooltip-max-height'),
    'height':     this.elem.data('romo-tooltip-height')
  })
  this.bodyElem.html(this.elem.data('romo-tooltip-content') || '')

  // TODO: tooltip-href handling
  this.elem.on('mouseenter', $.proxy(this.onToggleEnter, this))
  this.elem.on('mouseleave', $.proxy(this.onToggleLeave, this))
  this.elem.on('tooltip:triggerPopupOpen', $.proxy(this.onPopupOpen, this))
  this.elem.on('tooltip:triggerPopupClose', $.proxy(this.onPopupClose, this))
  $(window).on('resize', $.proxy(this.onResizeWindow, this))

  this.doInit()
  this.elem.trigger('tooltip:ready', [this])
}

RomoTooltip.prototype.doInit = function() {
  // override as needed
}

RomoTooltip.prototype.onToggleEnter = function(e) {
  if (e !== undefined) {
    e.preventDefault()
  }

  this.hoverState = 'in'
  if (this.elem.hasClass('disabled') === false) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout($.proxy(function() {
      if (this.hoverState ==='in') {
        this.doPopupOpen()
      }
    }, this), this.delayEnter)
  }
}

RomoTooltip.prototype.onToggleLeave = function(e) {
  if (e !== undefined) {
    e.preventDefault()
  }

  this.hoverState = 'out'
  if (this.elem.hasClass('disabled') === false) {
    clearTimeout(this.timeout)
    this.timeout = setTimeout($.proxy(function() {
      if (this.hoverState === 'out') {
        this.doPopupClose()
      }
    }, this), this.delayLeave)
  }
}

RomoTooltip.prototype.onResizeWindow = function(e) {
  if (this.elem.hasClass('disabled') === false && this.hoverState === 'in') {
    this.doPlacePopupElem()
  }
}

RomoTooltip.prototype.onPopupOpen = function(e) {
  if (e !== undefined) {
    e.preventDefault()
  }

  if (this.elem.hasClass('disabled') === false) {
    this.doPopupOpen()
  }
}

RomoTooltip.prototype.doPopupOpen = function() {
  this.popupElem.addClass('romo-tooltip-open')
  this.doPlacePopupElem()

  this.elem.trigger('tooltip:popupOpen', [this])
}

RomoTooltip.prototype.onPopupClose = function(e) {
  if (e !== undefined) {
    e.preventDefault()
  }

  if (this.elem.hasClass('disabled') === false) {
    this.doPopupClose()
  }
}

RomoTooltip.prototype.doPopupClose = function() {
  this.popupElem.removeClass('romo-tooltip-open')

  this.elem.trigger('tooltip:popupClose', [this])
}

RomoTooltip.prototype.doPlacePopupElem = function() {
  var pos = $.extend({}, this.elem[0].getBoundingClientRect(), this.elem.offset())
  var w = this.popupElem[0].offsetWidth
  var h = this.popupElem[0].offsetHeight
  var pad = 6 + 1 // arrow size + spacing
  var offset = {}

  switch (this.popupPosition) {
    case 'top':
      $.extend(offset, { top: pos.top - h - pad, left: pos.left + pos.width / 2 - w / 2 })
      break
    case 'bottom':
      $.extend(offset, { top: pos.top + pos.height + pad, left: pos.left + pos.width / 2 - w / 2 })
      break
    case 'left':
      $.extend(offset, { top: pos.top + pos.height / 2 - h / 2, left: pos.left - w - pad })
      break
    case 'right':
      $.extend(offset, { top: pos.top + pos.height / 2 - h / 2, left: pos.left + pos.width + pad })
      break
  }

  this.popupElem.offset(offset)
}

Romo.onInitUI(function(e) {
  $(e.target).find('[data-romo-tooltip-auto="true"]').romoTooltip()
})
<|endoftext|>


<commit_before>var client = new Dropbox.Client({ key: '9a666eiuctz1yh4' });

var cursorTag = null;
var layers = Array();

// Init map
$('#map').height($(window).height());

var map = L.map('map').setView([51.505, -0.09], 13);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

// Init Dropbox
client.authenticate({interactive: false}, function(error, client) {
  if (error) {
    return showError(error);
  }

  if (client.isAuthenticated()) {
  	run(client);
  }
  else {
  	client.authenticate(function(error, client) {
  		if (error) {
  			return showError(error);
  		}
  		run(client);
  	});
  }

});

function pullChanges() {
  client.pullChanges(function(error, changes) {
    if (error) {
      return showError(error);
    }

    cursorTag = changes.cursorTag;

    processChanges(changes.changes);
  });
}

function processChanges(changes) {
  changes.forEach(function(change) {
    if (change.wasRemoved) {
      console.log('Removed: ' + change.path);
      layers.forEach(function(layer) {
        if (layer.path == change.path) {
          map.removeLayer(layer.layer);
        }
      })
    }
    else {
      console.log(change.path);
      if (change.path.substr(-4) == '.gpx') {
        client.readFile(change.path, function(error, data) {
          if(error) {
            return showError(error);
          }

          layer = omnivore.gpx.parse(data);
          layer.addTo(map);
          map.fitBounds(layer.getBounds());
          layers.push({'layer': layer, 'path': change.path});
        });
      }
    }
  });

  if (changes.shouldBackOff) {
    interval = 5000;
  }
  else {
    interval = 0;
  }

  setTimeout(poll, interval);
}

function poll() {
  console.log('Polling for changes...');
  client.pollForChanges(cursorTag, function(error, result) {
    if (error) {
      return showError(error);
    }

    if (result.hasChanges) {
      console.log('Changes detected.');
      pullChanges();
    }
    else {
      console.log('No changes detected.');
      setTimeout(poll, result.retryAfter * 1000);
    }
  });
}

function run(client) {
	console.log('running');

  pullChanges();
}

function showError(error) {
	console.log(error);
}<commit_msg>Fix pullChanges bug<commit_after>var client = new Dropbox.Client({ key: '9a666eiuctz1yh4' });

var cursorTag = null;
var layers = Array();

// Init map
$('#map').height($(window).height());

var map = L.map('map').setView([51.505, -0.09], 13);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

// Init Dropbox
client.authenticate({interactive: false}, function(error, client) {
  if (error) {
    return showError(error);
  }

  if (client.isAuthenticated()) {
  	run(client);
  }
  else {
  	client.authenticate(function(error, client) {
  		if (error) {
  			return showError(error);
  		}
  		run(client);
  	});
  }

});

function pullChanges() {
  client.pullChanges(cursorTag, function(error, changes) {
    if (error) {
      return showError(error);
    }

    cursorTag = changes.cursorTag;

    processChanges(changes.changes);
  });
}

function processChanges(changes) {
  changes.forEach(function(change) {
    if (change.wasRemoved) {
      console.log('Removed: ' + change.path);
      layers.forEach(function(layer) {
        if (layer.path == change.path) {
          map.removeLayer(layer.layer);
        }
      })
    }
    else {
      console.log(change.path);
      if (change.path.substr(-4) == '.gpx') {
        client.readFile(change.path, function(error, data) {
          if(error) {
            return showError(error);
          }

          layer = omnivore.gpx.parse(data);
          layer.addTo(map);
          map.fitBounds(layer.getBounds());
          layers.push({'layer': layer, 'path': change.path});
        });
      }
    }
  });

  if (changes.shouldBackOff) {
    interval = 5000;
  }
  else {
    interval = 0;
  }

  setTimeout(poll, interval);
}

function poll() {
  console.log('Polling for changes...');
  client.pollForChanges(cursorTag, function(error, result) {
    if (error) {
      return showError(error);
    }

    if (result.hasChanges) {
      console.log('Changes detected.');
      pullChanges();
    }
    else {
      console.log('No changes detected.');
      setTimeout(poll, result.retryAfter * 1000);
    }
  });
}

function run(client) {
	console.log('running');

  pullChanges();
}

function showError(error) {
	console.log(error);
}<|endoftext|>


<commit_before>/**
 * @fileoverview Helper functions for generating Arduino blocks.
 * @author author@email.com (Name)
 */
'use strict';

//define blocks
if (!Blockly.Language) Blockly.Language = {};

//define read block
Blockly.Language.custom_read = {
  category: 'Custom',
  helpUrl: '',
  init: function() {
    this.setColour(230);
	this.appendDummyInput("")
	    .appendTitle("CustomRead PIN#")
	    .appendTitle(new Blockly.FieldDropdown(profile.default.digital), "PIN");
    this.setOutput(true, Boolean);
    this.setTooltip('input block');
  }
};

//define write block
Blockly.Language.custom_write = {
  category: 'Custom',
  helpUrl: '',
  init: function() {
    this.setColour(230);
	this.appendDummyInput("")
	    .appendTitle("CustomWrite PIN#")
	    .appendTitle(new Blockly.FieldImage("http://www.seeedstudio.com/wiki/images/thumb/e/e0/LED1.jpg/400px-LED1.jpg", 64, 64))
	    .appendTitle(new Blockly.FieldDropdown(profile.default.analog), "PIN")
	    .appendTitle("value");
	this.appendValueInput("NUM", Number);
	this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setTooltip('Output block');
  }
};

//*********************************************************************************************************************
// define generators
Blockly.Arduino.custom_write = function() {
  var dropdown_pin = this.getTitleValue('PIN');
  var value_num = Blockly.Arduino.valueToCode(this, 'NUM', Blockly.Arduino.ORDER_ATOMIC);

  var code = 'analogWrite('+dropdown_pin+','+value_num+');\n';
  return code;
};

Blockly.Arduino.custom_read = function() {
  var dropdown_pin = this.getTitleValue('PIN');

  // Blockly.Arduino.definitions_['define_custom_read'] = '#include <Servo.h>\n';
  // Blockly.Arduino.definitions_['var_custom_read'+dropdown_pin] = 'Servo servo_'+dropdown_pin+';\n';
  // Blockly.Arduino.setups_['setup_custom_read_'+dropdown_pin] = 'servo_'+dropdown_pin+'.attach('+dropdown_pin+');\n';

  var code = 'analogRead('+dropdown_pin+')';
  return [code, Blockly.Arduino.ORDER_ATOMIC];
};

<commit_msg>Fix to analog write.<commit_after>/**
 * @fileoverview Helper functions for generating Arduino blocks.
 * @author author@email.com (Name)
 */
'use strict';

//define blocks
if (!Blockly.Language) Blockly.Language = {};

//define read block
Blockly.Language.custom_read = {
  category: 'Custom',
  helpUrl: '',
  init: function() {
    this.setColour(230);
	this.appendDummyInput("")
	    .appendTitle("CustomRead PIN#")
	    .appendTitle(new Blockly.FieldDropdown(profile.default.digital), "PIN");
    this.setOutput(true, Boolean);
    this.setTooltip('input block');
  }
};

//define write block
Blockly.Language.custom_write = {
  category: 'Custom',
  helpUrl: '',
  init: function() {
    this.setColour(230);
	this.appendDummyInput("")
	    .appendTitle("CustomWrite PIN#")
	    .appendTitle(new Blockly.FieldImage("http://www.seeedstudio.com/wiki/images/thumb/e/e0/LED1.jpg/400px-LED1.jpg", 64, 64))
	    .appendTitle(new Blockly.FieldDropdown(profile.default.digital), "PIN")
	    .appendTitle("value");
	this.appendValueInput("NUM", Number);
	this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setTooltip('Output block');
  }
};

//*********************************************************************************************************************
// define generators
Blockly.Arduino.custom_write = function() {
  var dropdown_pin = this.getTitleValue('PIN');
  var value_num = Blockly.Arduino.valueToCode(this, 'NUM', Blockly.Arduino.ORDER_ATOMIC);

  var code = 'analogWrite('+dropdown_pin+','+value_num+');\n';
  return code;
};

Blockly.Arduino.custom_read = function() {
  var dropdown_pin = this.getTitleValue('PIN');

  // Blockly.Arduino.definitions_['define_custom_read'] = '#include <Servo.h>\n';
  // Blockly.Arduino.definitions_['var_custom_read'+dropdown_pin] = 'Servo servo_'+dropdown_pin+';\n';
  // Blockly.Arduino.setups_['setup_custom_read_'+dropdown_pin] = 'servo_'+dropdown_pin+'.attach('+dropdown_pin+');\n';

  var code = 'analogRead('+dropdown_pin+')';
  return [code, Blockly.Arduino.ORDER_ATOMIC];
};

<|endoftext|>


<commit_before>'use strict';
// Call spamassassin via spamd

const sock  = require('./line_socket');
const utils = require('haraka-utils');

exports.register = function () {
    const plugin = this;
    plugin.load_spamassassin_ini();
};

exports.load_spamassassin_ini = function () {
    const plugin = this;
    plugin.cfg = plugin.config.get('spamassassin.ini', {
        booleans: [
            '+add_headers',
        ],
    }, function () {
        plugin.load_spamassassin_ini();
    });

    const defaults = {
        spamd_socket: 'localhost:783',
        max_size:     500000,
        old_headers_action: "rename",
        subject_prefix: "*** SPAM ***",
    };

    for (const key in defaults) {
        if (plugin.cfg.main[key]) continue;
        plugin.cfg.main[key] = defaults[key];
    }

    [
        'reject_threshold', 'relay_reject_threshold',
        'munge_subject_threshold', 'max_size'
    ].forEach(function (item) {
        if (!plugin.cfg.main[item]) return;
        plugin.cfg.main[item] = Number(plugin.cfg.main[item]);
    });
};

exports.hook_data_post = function (next, connection) {
    const plugin = this;
    if (plugin.msg_too_big(connection)) return next();

    const username        = plugin.get_spamd_username(connection);
    const headers         = plugin.get_spamd_headers(connection, username);
    const socket          = plugin.get_spamd_socket(next, connection, headers);

    const spamd_response = { headers: {} };
    let state = 'line0';
    let last_header;
    const start = Date.now();

    socket.on('line', function (line) {
        connection.logprotocol(plugin, "Spamd C: " + line + ' state=' + state);
        line = line.replace(/\r?\n/, '');
        if (state === 'line0') {
            spamd_response.line0 = line;
            state = 'response';
        }
        else if (state === 'response') {
            if (line.match(/\S/)) {
                const matches = line.match(/Spam: (True|False) ; (-?\d+\.\d) \/ (-?\d+\.\d)/);
                if (matches) {
                    spamd_response.flag = matches[1];
                    spamd_response.score = matches[2];
                    spamd_response.hits = matches[2];  // backwards compat
                    spamd_response.reqd = matches[3];
                    spamd_response.flag = spamd_response.flag === 'True' ? 'Yes' : 'No';
                }
            }
            else {
                state = 'headers';
            }
        }
        else if (state === 'headers') {
            const m = line.match(/^X-Spam-([\x21-\x39\x3B-\x7E]+):\s*(.*)/);
            if (m) {
                connection.logdebug(plugin, "header: " + line);
                last_header = m[1];
                spamd_response.headers[m[1]] = m[2];
                return;
            }
            let fold;
            if (last_header && (fold = line.match(/^(\s+.*)/))) {
                spamd_response.headers[last_header] += "\r\n" + fold[1];
                return;
            }
            last_header = '';
        }
    });

    socket.once('end', function () {
        // Abort if the transaction is gone
        if (!connection.transaction) return next();

        if (spamd_response.headers && spamd_response.headers.Tests) {
            spamd_response.tests = spamd_response.headers.Tests;
        }
        if (spamd_response.tests === undefined) {
            // strip the 'tests' from the X-Spam-Status header
            if (spamd_response.headers && spamd_response.headers.Status) {
                // SpamAssassin appears to have a bug that causes a space not to
                // be added before autolearn= when the header line has been folded.
                // So we modify the regexp here not to match autolearn onwards.
                const tests = /tests=((?:(?!autolearn)[^ ])+)/.exec(
                    spamd_response.headers.Status.replace(/\r?\n\t/g,'')
                );
                if (tests) { spamd_response.tests = tests[1]; }
            }
        }

        // do stuff with the results...
        connection.transaction.notes.spamassassin = spamd_response;
        connection.results.add(plugin, {
            time: (Date.now() - start)/1000,
            hits: spamd_response.hits,
            flag: spamd_response.flag,
        });

        plugin.fixup_old_headers(connection.transaction);
        plugin.do_header_updates(connection, spamd_response);
        plugin.log_results(connection, spamd_response);

        const exceeds_err = plugin.score_too_high(connection, spamd_response);
        if (exceeds_err) return next(DENY, exceeds_err);

        plugin.munge_subject(connection, spamd_response.score);

        return next();
    });
};

exports.fixup_old_headers = function (transaction) {
    const plugin = this;
    const action = plugin.cfg.main.old_headers_action;
    const headers = transaction.notes.spamassassin.headers;

    let key;
    switch (action) {
        case "keep":
            break;
        case "drop":
            for (key in headers) {
                if (!key) continue;
                transaction.remove_header('X-Spam-' + key);
            }
            break;
        // case 'rename':
        default:
            for (key in headers) {
                if (!key) continue;
                key = 'X-Spam-' + key;
                const old_val = transaction.header.get(key);
                transaction.remove_header(key);
                if (old_val) {
                    // plugin.logdebug(plugin, "header: " + key + ', ' + old_val);
                    transaction.add_header(key.replace(/^X-/, 'X-Old-'), old_val);
                }
            }
            break;
    }
};

exports.munge_subject = function (connection, score) {
    const plugin = this;
    const munge = plugin.cfg.main.munge_subject_threshold;
    if (!munge) return;
    if (parseFloat(score) < parseFloat(munge)) return;

    const subj = connection.transaction.header.get('Subject');
    const subject_re = new RegExp('^' + utils.regexp_escape(plugin.cfg.main.subject_prefix));
    if (subject_re.test(subj)) return;    // prevent double munge

    connection.transaction.remove_header('Subject');
    connection.transaction.add_header('Subject', plugin.cfg.main.subject_prefix + " " + subj);
};

exports.do_header_updates = function (connection, spamd_response) {
    const plugin = this;
    if (spamd_response.flag === 'Yes') {
        // X-Spam-Flag is added by SpamAssassin
        connection.transaction.remove_header('precedence');
        connection.transaction.add_header('Precedence', 'junk');
    }

    const modern = plugin.cfg.main.modern_status_syntax;
    if ( !plugin.cfg.main.add_headers ) return;

    for (const key in spamd_response.headers) {
        if (!key || key === '' || key === undefined) continue;
        let val = spamd_response.headers[key];
        if (val === undefined) { val = ''; }

        if (key === 'Status' && !modern) {
            const legacy = spamd_response.headers[key].replace(/ score=/,' hits=');
            connection.transaction.add_header('X-Spam-Status', legacy);
            continue;
        }
        if (val === '') continue;
        connection.transaction.add_header('X-Spam-' + key, val);
    }
};

exports.score_too_high = function (connection, spamd_response) {
    const plugin = this;
    const score = spamd_response.score;
    if (connection.relaying) {
        const rmax = plugin.cfg.main.relay_reject_threshold;
        if (rmax && (score >= rmax)) {
            return "spam score exceeded relay threshold";
        }
    }

    const max = plugin.cfg.main.reject_threshold;
    if (max && (score >= max)) {
        return "spam score exceeded threshold";
    }

    return false;
};

exports.get_spamd_username = function (connection) {
    const plugin = this;

    let user = connection.transaction.notes.spamd_user;  // 1st priority
    if (user && user !== undefined) return user;

    if (!plugin.cfg.main.spamd_user) return 'default';   // when not defined
    user = plugin.cfg.main.spamd_user;

    // Enable per-user SA prefs
    if (user === 'first-recipient') {                // special cases
        return connection.transaction.rcpt_to[0].address();
    }
    if (user === 'all-recipients') {
        throw "Unimplemented";
        // TODO: pass the message through SA for each recipient. Then apply
        // the least strict result to the connection. That is useful when
        // one user blacklists a sender that another user wants to get mail
        // from. If this is something you care about, this is the spot.
    }
    return user;
};

exports.get_spamd_headers = function (connection, username) {
    // http://svn.apache.org/repos/asf/spamassassin/trunk/spamd/PROTOCOL
    const headers = [
        'HEADERS SPAMC/1.3',
        'User: ' + username,
        '',
        'X-Envelope-From: ' + connection.transaction.mail_from.address(),
        'X-Haraka-UUID: ' + connection.transaction.uuid,
    ];
    if (connection.relaying) {
        headers.push('X-Haraka-Relay: true');
    }
    return headers;
};

exports.get_spamd_socket = function (next, connection, headers) {
    const plugin = this;
    // TODO: support multiple spamd backends

    const socket = new sock.Socket();
    socket.is_connected = false;
    const results_timeout = parseInt(plugin.cfg.main.results_timeout) || 300;

    socket.on('connect', function () {
        if (!connection.transaction) {
            socket.end();
            return;
        }
        this.is_connected = true;
        // Reset timeout
        this.setTimeout(results_timeout * 1000);
        socket.write(headers.join("\r\n") + "\r\n");
        connection.transaction.message_stream.pipe(socket);
    });

    socket.on('error', function (err) {
        connection.logerror(plugin, 'connection failed: ' + err);
        // TODO: optionally DENYSOFT
        // TODO: add a transaction note
        return next();
    });

    socket.on('timeout', function () {
        if (!this.is_connected) {
            connection.logerror(plugin, 'spamd connection timed out');
        }
        else {
            connection.logerror(plugin, 'timeout waiting for results');
        }
        socket.end();
        return next();
    });

    const connect_timeout = parseInt(plugin.cfg.main.connect_timeout) || 30;
    socket.setTimeout(connect_timeout * 1000);

    if (plugin.cfg.main.spamd_socket.match(/\//)) {    // assume unix socket
        socket.connect(plugin.cfg.main.spamd_socket);
    }
    else {
        const hostport = plugin.cfg.main.spamd_socket.split(/:/);
        socket.connect((hostport[1] || 783), hostport[0]);
    }

    return socket;
};

exports.msg_too_big = function (connection) {
    const plugin = this;
    if (!plugin.cfg.main.max_size) return false;

    const size = connection.transaction.data_bytes;

    const max = plugin.cfg.main.max_size;
    if (size <= max) { return false; }
    connection.loginfo(plugin, 'skipping, size ' + utils.prettySize(size) +
            ' exceeds max: ' + utils.prettySize(max));
    return true;
};

exports.log_results = function (connection, spamd_response) {
    const plugin = this;
    const cfg = plugin.cfg.main;
    connection.loginfo(plugin, "status=" + spamd_response.flag +
          ', score=' + spamd_response.score +
          ', required=' + spamd_response.reqd +
          ', reject=' + ((connection.relaying) ?
            (cfg.relay_reject_threshold || cfg.reject_threshold) : cfg.reject_threshold) +
          ', tests="' + spamd_response.tests + '"');
};
<commit_msg>Replace string concatenations with template literals in spamassassin.js (#2256)<commit_after>'use strict';
// Call spamassassin via spamd

const sock  = require('./line_socket');
const utils = require('haraka-utils');

exports.register = function () {
    const plugin = this;
    plugin.load_spamassassin_ini();
};

exports.load_spamassassin_ini = function () {
    const plugin = this;
    plugin.cfg = plugin.config.get('spamassassin.ini', {
        booleans: [
            '+add_headers',
        ],
    }, function () {
        plugin.load_spamassassin_ini();
    });

    const defaults = {
        spamd_socket: 'localhost:783',
        max_size:     500000,
        old_headers_action: "rename",
        subject_prefix: "*** SPAM ***",
    };

    for (const key in defaults) {
        if (plugin.cfg.main[key]) continue;
        plugin.cfg.main[key] = defaults[key];
    }

    [
        'reject_threshold', 'relay_reject_threshold',
        'munge_subject_threshold', 'max_size'
    ].forEach(function (item) {
        if (!plugin.cfg.main[item]) return;
        plugin.cfg.main[item] = Number(plugin.cfg.main[item]);
    });
};

exports.hook_data_post = function (next, connection) {
    const plugin = this;
    if (plugin.msg_too_big(connection)) return next();

    const username        = plugin.get_spamd_username(connection);
    const headers         = plugin.get_spamd_headers(connection, username);
    const socket          = plugin.get_spamd_socket(next, connection, headers);

    const spamd_response = { headers: {} };
    let state = 'line0';
    let last_header;
    const start = Date.now();

    socket.on('line', function (line) {
        connection.logprotocol(plugin, `Spamd C: ${line} state=${state}`);
        line = line.replace(/\r?\n/, '');
        if (state === 'line0') {
            spamd_response.line0 = line;
            state = 'response';
        }
        else if (state === 'response') {
            if (line.match(/\S/)) {
                const matches = line.match(/Spam: (True|False) ; (-?\d+\.\d) \/ (-?\d+\.\d)/);
                if (matches) {
                    spamd_response.flag = matches[1];
                    spamd_response.score = matches[2];
                    spamd_response.hits = matches[2];  // backwards compat
                    spamd_response.reqd = matches[3];
                    spamd_response.flag = spamd_response.flag === 'True' ? 'Yes' : 'No';
                }
            }
            else {
                state = 'headers';
            }
        }
        else if (state === 'headers') {
            const m = line.match(/^X-Spam-([\x21-\x39\x3B-\x7E]+):\s*(.*)/);
            if (m) {
                connection.logdebug(plugin, "header: " + line);
                last_header = m[1];
                spamd_response.headers[m[1]] = m[2];
                return;
            }
            let fold;
            if (last_header && (fold = line.match(/^(\s+.*)/))) {
                spamd_response.headers[last_header] += `\r\n${fold[1]}`;
                return;
            }
            last_header = '';
        }
    });

    socket.once('end', function () {
        // Abort if the transaction is gone
        if (!connection.transaction) return next();

        if (spamd_response.headers && spamd_response.headers.Tests) {
            spamd_response.tests = spamd_response.headers.Tests;
        }
        if (spamd_response.tests === undefined) {
            // strip the 'tests' from the X-Spam-Status header
            if (spamd_response.headers && spamd_response.headers.Status) {
                // SpamAssassin appears to have a bug that causes a space not to
                // be added before autolearn= when the header line has been folded.
                // So we modify the regexp here not to match autolearn onwards.
                const tests = /tests=((?:(?!autolearn)[^ ])+)/.exec(
                    spamd_response.headers.Status.replace(/\r?\n\t/g,'')
                );
                if (tests) { spamd_response.tests = tests[1]; }
            }
        }

        // do stuff with the results...
        connection.transaction.notes.spamassassin = spamd_response;
        connection.results.add(plugin, {
            time: (Date.now() - start)/1000,
            hits: spamd_response.hits,
            flag: spamd_response.flag,
        });

        plugin.fixup_old_headers(connection.transaction);
        plugin.do_header_updates(connection, spamd_response);
        plugin.log_results(connection, spamd_response);

        const exceeds_err = plugin.score_too_high(connection, spamd_response);
        if (exceeds_err) return next(DENY, exceeds_err);

        plugin.munge_subject(connection, spamd_response.score);

        return next();
    });
};

exports.fixup_old_headers = function (transaction) {
    const plugin = this;
    const action = plugin.cfg.main.old_headers_action;
    const headers = transaction.notes.spamassassin.headers;

    let key;
    switch (action) {
        case "keep":
            break;
        case "drop":
            for (key in headers) {
                if (!key) continue;
                transaction.remove_header(`X-Spam-${key}`);
            }
            break;
        // case 'rename':
        default:
            for (key in headers) {
                if (!key) continue;
                key = 'X-Spam-' + key;
                const old_val = transaction.header.get(key);
                transaction.remove_header(key);
                if (old_val) {
                    // plugin.logdebug(plugin, `header: ${key}, ${old_val}`);
                    transaction.add_header(key.replace(/^X-/, 'X-Old-'), old_val);
                }
            }
            break;
    }
};

exports.munge_subject = function (connection, score) {
    const plugin = this;
    const munge = plugin.cfg.main.munge_subject_threshold;
    if (!munge) return;
    if (parseFloat(score) < parseFloat(munge)) return;

    const subj = connection.transaction.header.get('Subject');
    const subject_re = new RegExp('^' + utils.regexp_escape(plugin.cfg.main.subject_prefix));
    if (subject_re.test(subj)) return;    // prevent double munge

    connection.transaction.remove_header('Subject');
    connection.transaction.add_header('Subject', `${plugin.cfg.main.subject_prefix} ${subj}`);
};

exports.do_header_updates = function (connection, spamd_response) {
    const plugin = this;
    if (spamd_response.flag === 'Yes') {
        // X-Spam-Flag is added by SpamAssassin
        connection.transaction.remove_header('precedence');
        connection.transaction.add_header('Precedence', 'junk');
    }

    const modern = plugin.cfg.main.modern_status_syntax;
    if ( !plugin.cfg.main.add_headers ) return;

    for (const key in spamd_response.headers) {
        if (!key || key === '' || key === undefined) continue;
        let val = spamd_response.headers[key];
        if (val === undefined) { val = ''; }

        if (key === 'Status' && !modern) {
            const legacy = spamd_response.headers[key].replace(/ score=/,' hits=');
            connection.transaction.add_header('X-Spam-Status', legacy);
            continue;
        }
        if (val === '') continue;
        connection.transaction.add_header(`X-Spam-${key}`, val);
    }
};

exports.score_too_high = function (connection, spamd_response) {
    const plugin = this;
    const score = spamd_response.score;
    if (connection.relaying) {
        const rmax = plugin.cfg.main.relay_reject_threshold;
        if (rmax && (score >= rmax)) {
            return "spam score exceeded relay threshold";
        }
    }

    const max = plugin.cfg.main.reject_threshold;
    if (max && (score >= max)) {
        return "spam score exceeded threshold";
    }

    return false;
};

exports.get_spamd_username = function (connection) {
    const plugin = this;

    let user = connection.transaction.notes.spamd_user;  // 1st priority
    if (user && user !== undefined) return user;

    if (!plugin.cfg.main.spamd_user) return 'default';   // when not defined
    user = plugin.cfg.main.spamd_user;

    // Enable per-user SA prefs
    if (user === 'first-recipient') {                // special cases
        return connection.transaction.rcpt_to[0].address();
    }
    if (user === 'all-recipients') {
        throw "Unimplemented";
        // TODO: pass the message through SA for each recipient. Then apply
        // the least strict result to the connection. That is useful when
        // one user blacklists a sender that another user wants to get mail
        // from. If this is something you care about, this is the spot.
    }
    return user;
};

exports.get_spamd_headers = function (connection, username) {
    // http://svn.apache.org/repos/asf/spamassassin/trunk/spamd/PROTOCOL
    const headers = [
        'HEADERS SPAMC/1.3',
        `User: ${username}`,
        '',
        `X-Envelope-From: ${connection.transaction.mail_from.address()}`,
        `X-Haraka-UUID: ${connection.transaction.uuid}`,
    ];
    if (connection.relaying) {
        headers.push('X-Haraka-Relay: true');
    }
    return headers;
};

exports.get_spamd_socket = function (next, connection, headers) {
    const plugin = this;
    // TODO: support multiple spamd backends

    const socket = new sock.Socket();
    socket.is_connected = false;
    const results_timeout = parseInt(plugin.cfg.main.results_timeout) || 300;

    socket.on('connect', function () {
        if (!connection.transaction) {
            socket.end();
            return;
        }
        this.is_connected = true;
        // Reset timeout
        this.setTimeout(results_timeout * 1000);
        socket.write(`${headers.join("\r\n")}\r\n`);
        connection.transaction.message_stream.pipe(socket);
    });

    socket.on('error', function (err) {
        connection.logerror(plugin, `connection failed: ${err}`);
        // TODO: optionally DENYSOFT
        // TODO: add a transaction note
        return next();
    });

    socket.on('timeout', function () {
        if (!this.is_connected) {
            connection.logerror(plugin, 'spamd connection timed out');
        }
        else {
            connection.logerror(plugin, 'timeout waiting for results');
        }
        socket.end();
        return next();
    });

    const connect_timeout = parseInt(plugin.cfg.main.connect_timeout) || 30;
    socket.setTimeout(connect_timeout * 1000);

    if (plugin.cfg.main.spamd_socket.match(/\//)) {    // assume unix socket
        socket.connect(plugin.cfg.main.spamd_socket);
    }
    else {
        const hostport = plugin.cfg.main.spamd_socket.split(/:/);
        socket.connect((hostport[1] || 783), hostport[0]);
    }

    return socket;
};

exports.msg_too_big = function (connection) {
    const plugin = this;
    if (!plugin.cfg.main.max_size) return false;

    const size = connection.transaction.data_bytes;

    const max = plugin.cfg.main.max_size;
    if (size <= max) { return false; }
    connection.loginfo(plugin, `skipping, size ${utils.prettySize(size)} exceeds max: ${utils.prettySize(max)}`);
    return true;
};

exports.log_results = function (connection, spamd_response) {
    const plugin = this;
    const cfg = plugin.cfg.main;
    connection.loginfo(plugin, `status=${spamd_response.flag}` +
          `, score=${spamd_response.score}` +
          `, required=${spamd_response.reqd}` +
          `, reject=${((connection.relaying) ? (cfg.relay_reject_threshold || cfg.reject_threshold) : cfg.reject_threshold)}` +
          `, tests="${spamd_response.tests}"`);
};

<|endoftext|>


<commit_before>/**@
* #DOM
* @category Graphics
* Draws entities as DOM nodes, specifically `<DIV>`s.
*/
Crafty.c("DOM", {
	/**@
	* #._element
	* @comp DOM
	* The DOM element used to represent the entity.
	*/
	_element: null,

	init: function() {
		this._element = document.createElement("div");
		Crafty.stage.inner.appendChild(this._element);
		this._element.style.position = "absolute";
		this._element.id = "ent" + this[0];

		this.bind("Change", function() {
			if(!this._changed) {
				this._changed = true;
				Crafty.DrawManager.add(this);
			}
		});

		function updateClass() {
			var i = 0, c = this.__c, str = "";
			for(i in c) {
				str += ' ' + i;
			}
			str = str.substr(1);
			this._element.className = str;
		}

		this.bind("NewComponent", updateClass).bind("RemoveComponent", updateClass);

		if(Crafty.support.prefix === "ms" && Crafty.support.version < 9) {
			this._filters = {};

			this.bind("Rotate", function(e) {
				var m = e.matrix,
					elem = this._element.style,
					M11 = m.M11.toFixed(8),
					M12 = m.M12.toFixed(8),
					M21 = m.M21.toFixed(8),
					M22 = m.M22.toFixed(8);

				this._filters.rotation = "progid:DXImageTransform.Microsoft.Matrix(M11="+M11+", M12="+M12+", M21="+M21+", M22="+M22+",sizingMethod='auto expand')";
			});
		}

		this.bind("Remove", this.undraw);
	},

	/**@
	* #.DOM
	* @comp DOM
	* @sign public this .DOM(HTMLElement elem)
	* @param elem - HTML element that will replace the dynamically created one
	* Pass a DOM element to use rather than one created. Will set `._element` to this value. Removes the old element.
	*/
	DOM: function(elem) {
		if(elem && elem.nodeType) {
			this.undraw();
			this._element = elem;
			this._element.style.position = 'absolute';
		}
		return this;
	},

	/**@
	* #.draw
	* @comp DOM
	* @sign public this .draw(void)
	* @triggers Draw
	* Updates the CSS properties of the node to draw on the stage.
	*/
	draw: function() {
		var style = this._element.style,
			coord = this.__coord || [0,0,0,0],
			co = {x: coord[0], y: coord[1] },
			prefix = Crafty.support.prefix,
			trans = [];

		if(!this._visible) style.visibility = "hidden";
		else style.visibility = "visible";

		//utilize CSS3 if supported
		if(Crafty.support.css3dtransform) {
			trans.push("translate3d("+(~~this._x)+"px,"+(~~this._y)+"px,0)");
		} else {
			style.left = ~~(this._x) + "px";
			style.top = ~~(this._y) + "px";
		}

		style.width = ~~(this._w) + "px";
		style.height = ~~(this._h) + "px";
		style.zIndex = this._z;

		style.opacity = this._alpha;
		style[prefix+"Opacity"] = this._alpha;

		//if not version 9 of IE
		if(prefix === "ms" && Crafty.support.version < 9) {
			//for IE version 8, use ImageTransform filter
			if(Crafty.support.version === 8) {
				this._filters.alpha = "progid:DXImageTransform.Microsoft.Alpha(Opacity="+(this._alpha * 100)+")"; // first!
			//all other versions use filter
			} else {
				this._filters.alpha = "alpha(opacity="+(this._alpha*100)+")";
			}
		}

		if(this._mbr) {
			var origin = this._origin.x + "px " + this._origin.y + "px";
			style.transformOrigin = origin;
			style[prefix+"TransformOrigin"] = origin;
			if(Crafty.support.css3dtransform) trans.push("rotateZ("+this._rotation+"deg)");
			else trans.push("rotate("+this._rotation+"deg)");
		}

		if(this._flipX) {
			trans.push("scaleX(-1)");
			if(prefix === "ms" && Crafty.support.version < 9) {
				this._filters.flipX = "fliph";
			}
		}

		if(this._flipY) {
			trans.push("scaleY(-1)");
			if(prefix === "ms" && Crafty.support.version < 9) {
				this._filters.flipY = "flipv";
			}
		}

		//apply the filters if IE
		if(prefix === "ms" && Crafty.support.version < 9) {
			this.applyFilters();
		}

		style.transform = trans.join(" ");
		style[prefix+"Transform"] = trans.join(" ");

		this.trigger("Draw", {style: style, type: "DOM", co: co});

		return this;
	},

	applyFilters: function() {
		this._element.style.filter = "";
		var str = "";

		for(var filter in this._filters) {
			if(!this._filters.hasOwnProperty(filter)) continue;
			str += this._filters[filter] + " ";
		}

		this._element.style.filter = str;
	},

	/**@
	* #.undraw
	* @comp DOM
	* @sign public this .undraw(void)
	* Removes the element from the stage.
	*/
	undraw: function() {
		Crafty.stage.inner.removeChild(this._element);
		return this;
	},

	/**@
	* #.css
	* @comp DOM
	* @sign public * css(String property, String value)
	* @param property - CSS property to modify
	* @param value - Value to give the CSS property
	* @sign public * css(Object map)
	* @param map - Object where the key is the CSS property and the value is CSS value
	* Apply CSS styles to the element.
	*
	* Can pass an object where the key is the style property and the value is style value.
	*
	* For setting one style, simply pass the style as the first argument and the value as the second.
	*
	* The notation can be CSS or JS (e.g. `text-align` or `textAlign`).
	*
	* To return a value, pass the property.
	* @example
	* ~~~
	* this.css({'text-align', 'center', font: 'Arial'});
	* this.css("textAlign", "center");
	* this.css("text-align"); //returns center
	* ~~~
	*/
	css: function(obj, value) {
		var key,
			elem = this._element,
			val,
			style = elem.style;

		//if an object passed
		if(typeof obj === "object") {
			for(key in obj) {
				if(!obj.hasOwnProperty(key)) continue;
				val = obj[key];
				if(typeof val === "number") val += 'px';

				style[Crafty.DOM.camelize(key)] = val;
			}
		} else {
			//if a value is passed, set the property
			if(value) {
				if(typeof value === "number") value += 'px';
				style[Crafty.DOM.camelize(obj)] = value;
			} else { //otherwise return the computed property
				return Crafty.DOM.getStyle(elem, obj);
			}
		}

		this.trigger("Change");

		return this;
	}
});

/**
* Fix IE6 background flickering
*/
try {
    document.execCommand("BackgroundImageCache", false, true);
} catch(e) {}

Crafty.extend({
	/**@
	* #Crafty.DOM
	* @category Graphics
	* Collection of utilities for using the DOM.
	*/
	DOM: {
		/**@
		* #Crafty.DOM.window
		* @comp Crafty.DOM
		* Object with `width` and `height` values representing the width
		* and height of the `window`.
		*/
		window: {
			init: function() {
				this.width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);
				this.height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);
			},

			width: 0,
			height: 0
		},

		/**@
		* #Crafty.DOM.inner
		* @comp Crafty.DOM
		* @sign public Object Crafty.DOM.inner(HTMLElement obj)
		* @param obj - HTML element to calculate the position
		* @returns Object with `x` key being the `x` position, `y` being the `y` position
		* Find a DOM elements position including
		* padding and border.
		*/
		inner: function(obj) {
			var rect = obj.getBoundingClientRect(),
				x = rect.left + (window.pageXOffset ? window.pageXOffset : document.body.scrollTop),
				y = rect.top + (window.pageYOffset ? window.pageYOffset : document.body.scrollLeft),
				borderX,
				borderY;

			//border left
			borderX = parseInt(this.getStyle(obj, 'border-left-width') || 0, 10);
			borderY = parseInt(this.getStyle(obj, 'border-top-width') || 0, 10);
			if(!borderX || !borderY) { //JS notation for IE
				borderX = parseInt(this.getStyle(obj, 'borderLeftWidth') || 0, 10);
				borderY = parseInt(this.getStyle(obj, 'borderTopWidth') || 0, 10);
			}

			x += borderX;
			y += borderY;

			return {x: x, y: y};
		},

		/**@
		* #Crafty.DOM.getStyle
		* @comp Crafty.DOM
		* @sign public Object Crafty.DOM.getStyle(HTMLElement obj, String property)
		* @param obj - HTML element to find the style
		* @param property - Style to return
		* Determine the value of a style on an HTML element. Notation can be
		* in either CSS or JS.
		*/
		getStyle: function(obj,prop) {
			var result;
			if(obj.currentStyle)
				result = obj.currentStyle[this.camelize(prop)];
			else if(window.getComputedStyle)
				result = document.defaultView.getComputedStyle(obj,null).getPropertyValue(this.csselize(prop));
			return result;
		},

		/**
		* Used in the Zepto framework
		*
		* Converts CSS notation to JS notation
		*/
		camelize: function(str) {
			return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' });
		},

		/**
		* Converts JS notation to CSS notation
		*/
		csselize: function(str) {
			return str.replace(/[A-Z]/g, function(chr){ return chr ? '-' + chr.toLowerCase() : '' });
		},

		/**@
		* #Crafty.DOM.translate
		* @comp Crafty.DOM
		* @sign public Object Crafty.DOM.translate(Number x, Number y)
		* @param x - x position to translate
		* @param y - y position to translate
		* @return Object with x and y as keys and translated values
		*
		* Method will translate x and y positions to positions on the
		* stage. Useful for mouse events with `e.clientX` and `e.clientY`.
		*/
		translate: function(x,y) {
			return {
				x: x - Crafty.stage.x + document.body.scrollLeft + document.documentElement.scrollLeft - Crafty.viewport._x,
				y: y - Crafty.stage.y + document.body.scrollTop + document.documentElement.scrollTop - Crafty.viewport._y
			}
		}
	}
});<commit_msg>borderX and borderY was set to NaN in IE and Opera in some cases. This led to bug #132<commit_after>/**@
* #DOM
* @category Graphics
* Draws entities as DOM nodes, specifically `<DIV>`s.
*/
Crafty.c("DOM", {
	/**@
	* #._element
	* @comp DOM
	* The DOM element used to represent the entity.
	*/
	_element: null,

	init: function() {
		this._element = document.createElement("div");
		Crafty.stage.inner.appendChild(this._element);
		this._element.style.position = "absolute";
		this._element.id = "ent" + this[0];

		this.bind("Change", function() {
			if(!this._changed) {
				this._changed = true;
				Crafty.DrawManager.add(this);
			}
		});

		function updateClass() {
			var i = 0, c = this.__c, str = "";
			for(i in c) {
				str += ' ' + i;
			}
			str = str.substr(1);
			this._element.className = str;
		}

		this.bind("NewComponent", updateClass).bind("RemoveComponent", updateClass);

		if(Crafty.support.prefix === "ms" && Crafty.support.version < 9) {
			this._filters = {};

			this.bind("Rotate", function(e) {
				var m = e.matrix,
					elem = this._element.style,
					M11 = m.M11.toFixed(8),
					M12 = m.M12.toFixed(8),
					M21 = m.M21.toFixed(8),
					M22 = m.M22.toFixed(8);

				this._filters.rotation = "progid:DXImageTransform.Microsoft.Matrix(M11="+M11+", M12="+M12+", M21="+M21+", M22="+M22+",sizingMethod='auto expand')";
			});
		}

		this.bind("Remove", this.undraw);
	},

	/**@
	* #.DOM
	* @comp DOM
	* @sign public this .DOM(HTMLElement elem)
	* @param elem - HTML element that will replace the dynamically created one
	* Pass a DOM element to use rather than one created. Will set `._element` to this value. Removes the old element.
	*/
	DOM: function(elem) {
		if(elem && elem.nodeType) {
			this.undraw();
			this._element = elem;
			this._element.style.position = 'absolute';
		}
		return this;
	},

	/**@
	* #.draw
	* @comp DOM
	* @sign public this .draw(void)
	* @triggers Draw
	* Updates the CSS properties of the node to draw on the stage.
	*/
	draw: function() {
		var style = this._element.style,
			coord = this.__coord || [0,0,0,0],
			co = {x: coord[0], y: coord[1] },
			prefix = Crafty.support.prefix,
			trans = [];

		if(!this._visible) style.visibility = "hidden";
		else style.visibility = "visible";

		//utilize CSS3 if supported
		if(Crafty.support.css3dtransform) {
			trans.push("translate3d("+(~~this._x)+"px,"+(~~this._y)+"px,0)");
		} else {
			style.left = ~~(this._x) + "px";
			style.top = ~~(this._y) + "px";
		}

		style.width = ~~(this._w) + "px";
		style.height = ~~(this._h) + "px";
		style.zIndex = this._z;

		style.opacity = this._alpha;
		style[prefix+"Opacity"] = this._alpha;

		//if not version 9 of IE
		if(prefix === "ms" && Crafty.support.version < 9) {
			//for IE version 8, use ImageTransform filter
			if(Crafty.support.version === 8) {
				this._filters.alpha = "progid:DXImageTransform.Microsoft.Alpha(Opacity="+(this._alpha * 100)+")"; // first!
			//all other versions use filter
			} else {
				this._filters.alpha = "alpha(opacity="+(this._alpha*100)+")";
			}
		}

		if(this._mbr) {
			var origin = this._origin.x + "px " + this._origin.y + "px";
			style.transformOrigin = origin;
			style[prefix+"TransformOrigin"] = origin;
			if(Crafty.support.css3dtransform) trans.push("rotateZ("+this._rotation+"deg)");
			else trans.push("rotate("+this._rotation+"deg)");
		}

		if(this._flipX) {
			trans.push("scaleX(-1)");
			if(prefix === "ms" && Crafty.support.version < 9) {
				this._filters.flipX = "fliph";
			}
		}

		if(this._flipY) {
			trans.push("scaleY(-1)");
			if(prefix === "ms" && Crafty.support.version < 9) {
				this._filters.flipY = "flipv";
			}
		}

		//apply the filters if IE
		if(prefix === "ms" && Crafty.support.version < 9) {
			this.applyFilters();
		}

		style.transform = trans.join(" ");
		style[prefix+"Transform"] = trans.join(" ");

		this.trigger("Draw", {style: style, type: "DOM", co: co});

		return this;
	},

	applyFilters: function() {
		this._element.style.filter = "";
		var str = "";

		for(var filter in this._filters) {
			if(!this._filters.hasOwnProperty(filter)) continue;
			str += this._filters[filter] + " ";
		}

		this._element.style.filter = str;
	},

	/**@
	* #.undraw
	* @comp DOM
	* @sign public this .undraw(void)
	* Removes the element from the stage.
	*/
	undraw: function() {
		Crafty.stage.inner.removeChild(this._element);
		return this;
	},

	/**@
	* #.css
	* @comp DOM
	* @sign public * css(String property, String value)
	* @param property - CSS property to modify
	* @param value - Value to give the CSS property
	* @sign public * css(Object map)
	* @param map - Object where the key is the CSS property and the value is CSS value
	* Apply CSS styles to the element.
	*
	* Can pass an object where the key is the style property and the value is style value.
	*
	* For setting one style, simply pass the style as the first argument and the value as the second.
	*
	* The notation can be CSS or JS (e.g. `text-align` or `textAlign`).
	*
	* To return a value, pass the property.
	* @example
	* ~~~
	* this.css({'text-align', 'center', font: 'Arial'});
	* this.css("textAlign", "center");
	* this.css("text-align"); //returns center
	* ~~~
	*/
	css: function(obj, value) {
		var key,
			elem = this._element,
			val,
			style = elem.style;

		//if an object passed
		if(typeof obj === "object") {
			for(key in obj) {
				if(!obj.hasOwnProperty(key)) continue;
				val = obj[key];
				if(typeof val === "number") val += 'px';

				style[Crafty.DOM.camelize(key)] = val;
			}
		} else {
			//if a value is passed, set the property
			if(value) {
				if(typeof value === "number") value += 'px';
				style[Crafty.DOM.camelize(obj)] = value;
			} else { //otherwise return the computed property
				return Crafty.DOM.getStyle(elem, obj);
			}
		}

		this.trigger("Change");

		return this;
	}
});

/**
* Fix IE6 background flickering
*/
try {
    document.execCommand("BackgroundImageCache", false, true);
} catch(e) {}

Crafty.extend({
	/**@
	* #Crafty.DOM
	* @category Graphics
	* Collection of utilities for using the DOM.
	*/
	DOM: {
		/**@
		* #Crafty.DOM.window
		* @comp Crafty.DOM
		* Object with `width` and `height` values representing the width
		* and height of the `window`.
		*/
		window: {
			init: function() {
				this.width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);
				this.height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);
			},

			width: 0,
			height: 0
		},

		/**@
		* #Crafty.DOM.inner
		* @comp Crafty.DOM
		* @sign public Object Crafty.DOM.inner(HTMLElement obj)
		* @param obj - HTML element to calculate the position
		* @returns Object with `x` key being the `x` position, `y` being the `y` position
		* Find a DOM elements position including
		* padding and border.
		*/
		inner: function(obj) {
			var rect = obj.getBoundingClientRect(),
				x = rect.left + (window.pageXOffset ? window.pageXOffset : document.body.scrollTop),
				y = rect.top + (window.pageYOffset ? window.pageYOffset : document.body.scrollLeft),

			//border left
				borderX = parseInt(this.getStyle(obj, 'border-left-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderLeftWidth') || 0, 10) || 0,
				borderY = parseInt(this.getStyle(obj, 'border-top-width') || 0, 10) || parseInt(this.getStyle(obj, 'borderTopWidth') || 0, 10) || 0;

			x += borderX;
			y += borderY;

			return {x: x, y: y};
		},

		/**@
		* #Crafty.DOM.getStyle
		* @comp Crafty.DOM
		* @sign public Object Crafty.DOM.getStyle(HTMLElement obj, String property)
		* @param obj - HTML element to find the style
		* @param property - Style to return
		* Determine the value of a style on an HTML element. Notation can be
		* in either CSS or JS.
		*/
		getStyle: function(obj,prop) {
			var result;
			if(obj.currentStyle)
				result = obj.currentStyle[this.camelize(prop)];
			else if(window.getComputedStyle)
				result = document.defaultView.getComputedStyle(obj,null).getPropertyValue(this.csselize(prop));
			return result;
		},

		/**
		* Used in the Zepto framework
		*
		* Converts CSS notation to JS notation
		*/
		camelize: function(str) {
			return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' });
		},

		/**
		* Converts JS notation to CSS notation
		*/
		csselize: function(str) {
			return str.replace(/[A-Z]/g, function(chr){ return chr ? '-' + chr.toLowerCase() : '' });
		},

		/**@
		* #Crafty.DOM.translate
		* @comp Crafty.DOM
		* @sign public Object Crafty.DOM.translate(Number x, Number y)
		* @param x - x position to translate
		* @param y - y position to translate
		* @return Object with x and y as keys and translated values
		*
		* Method will translate x and y positions to positions on the
		* stage. Useful for mouse events with `e.clientX` and `e.clientY`.
		*/
		translate: function(x,y) {
			return {
				x: x - Crafty.stage.x + document.body.scrollLeft + document.documentElement.scrollLeft - Crafty.viewport._x,
				y: y - Crafty.stage.y + document.body.scrollTop + document.documentElement.scrollTop - Crafty.viewport._y
			}
		}
	}
});<|endoftext|>


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

/**
 * External dependencies
 */
import classnames from 'classnames';

/**
 * WordPress dependencies
 */
import { __, _x, sprintf } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { getLocale, numFmt, getChartDifferenceArrow } from '../../../util';

/**
 * Gets data for a Google Chart from an AdSense report.
 *
 * @since 1.23.0
 *
 * @param {Object} current        A report object for the current period.
 * @param {Object} previous       A report object for the previous period.
 * @param {string} label          Metric label.
 * @param {number} selectedColumn Selected column index.
 * @param {Object} metadata       Data metadata.
 * @return {Array.<Array.<number|string>>} Data array.
 */
export function getSiteStatsDataForGoogleChart( current, previous, label, selectedColumn, metadata ) {
	const dataMap = [
		[
			{ type: 'date', label: __( 'Day', 'google-site-kit' ) },
			{ type: 'string', role: 'tooltip', p: { html: true } },
			{ type: 'number', label },
			{ type: 'number', label: __( 'Previous period', 'google-site-kit' ) },
		],
	];

	const stringToDate = ( dateString ) => new Date( `${ dateString } 00:00:00` );
	const findRowByDate = ( searchDate ) => ( ( [ rowDate ] ) => searchDate.getTime() === stringToDate( rowDate ).getTime() );

	const currentDate = stringToDate( current.startDate );
	const previousDate = stringToDate( previous.startDate );

	const ends = stringToDate( current.endDate );

	const locale = getLocale();
	const localeDateOptions = {
		weekday: 'short',
		month: 'short',
		day: 'numeric',
	};

	while ( +currentDate <= +ends ) {
		const currentMonth = parseFloat( ( current?.rows || [] ).find( findRowByDate( currentDate ) )?.[ selectedColumn ] || 0 );
		const prevMonth = parseFloat( ( previous?.rows || [] ).find( findRowByDate( previousDate ) )?.[ selectedColumn ] || 0 );

		const difference = prevMonth !== 0
			? ( currentMonth / prevMonth ) - 1
			: 1; // if previous month has 0, we need to pretend it's 100% growth, thus the "difference" has to be 1
		const svgArrow = getChartDifferenceArrow( difference );
		const dateRange = sprintf(
			/* translators: 1: date for user stats, 2: previous date for user stats comparison */
			_x( '%1$s vs %2$s', 'Date range for chart tooltip', 'google-site-kit' ),
			currentDate.toLocaleDateString( locale, localeDateOptions ),
			previousDate.toLocaleDateString( locale, localeDateOptions ),
		);

		let tooltipData = numFmt( currentMonth, metadata?.currency );
		if ( metadata?.type === 'METRIC_RATIO' ) {
			tooltipData = numFmt( currentMonth, '%' );
		}

		const statInfo = sprintf(
			/* translators: 1: selected stat label, 2: numeric value of selected stat, 3: up or down arrow , 4: different change in percentage, %%: percent symbol */
			_x( '%1$s: %2$s %3$s %4$s%%', 'Stat information for chart tooltip', 'google-site-kit' ),
			label,
			tooltipData,
			svgArrow,
			Math.abs( difference ).toFixed( 2 ).replace( /(.00|0)$/, '' ), // .replace( ... ) removes trailing zeros
		);

		dataMap.push( [
			new Date( currentDate ), // Copy the current date.
			`
 0,
				'googlesitekit-visualization-tooltip--down': difference < 0,
			} ) }">

${ dateRange }

${ statInfo }

`,
			currentMonth,
			prevMonth,
		] );

		currentDate.setDate( currentDate.getDate() + 1 );
		previousDate.setDate( previousDate.getDate() + 1 );
	}

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

/**
 * External dependencies
 */
import classnames from 'classnames';

/**
 * WordPress dependencies
 */
import { __, _x, sprintf } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { getLocale, numFmt, getChartDifferenceArrow } from '../../../util';
import { adsenseDateToInstance } from './date';

/**
 * Gets data for a Google Chart from an AdSense report.
 *
 * @since 1.23.0
 * @since n.e.x.t Updated for API v2 report format.
 *
 * @param {Object} current        A report object for the current period.
 * @param {Object} previous       A report object for the previous period.
 * @param {string} label          Metric label.
 * @param {number} selectedColumn Selected column index.
 * @param {Object} metadata       Data metadata.
 * @return {Array.<Array.<number|string>>} Data array.
 */
export function getSiteStatsDataForGoogleChart( current, previous, label, selectedColumn, metadata ) {
	const dataMap = [
		[
			{ type: 'date', label: __( 'Day', 'google-site-kit' ) },
			{ type: 'string', role: 'tooltip', p: { html: true } },
			{ type: 'number', label },
			{ type: 'number', label: __( 'Previous period', 'google-site-kit' ) },
		],
	];

	const nextDate = ( date ) => {
		const next = new Date( date );
		next.setDate( date.getDate() + 1 );
		return next;
	};
	const stringToDate = ( dateString ) => new Date( `${ dateString } 00:00:00` );
	const findRowByDate = ( searchDate ) => ( row ) => searchDate.getTime() === stringToDate( row.cells[ 0 ].value ).getTime();

	let currentDate = adsenseDateToInstance( current.startDate );
	let previousDate = adsenseDateToInstance( previous.startDate );
	const endDate = adsenseDateToInstance( current.endDate );

	const locale = getLocale();
	const localeDateOptions = {
		weekday: 'short',
		month: 'short',
		day: 'numeric',
	};

	while ( currentDate <= endDate ) {
		const currentMonth = parseFloat(
			( current?.rows || [] ).find( findRowByDate( currentDate ) )?.cells?.[ selectedColumn ]?.value || 0
		);
		const prevMonth = parseFloat(
			( previous?.rows || [] ).find( findRowByDate( previousDate ) )?.cells?.[ selectedColumn ]?.value || 0
		);

		const difference = prevMonth !== 0
			? ( currentMonth / prevMonth ) - 1
			: 1; // if previous month has 0, we need to pretend it's 100% growth, thus the "difference" has to be 1
		const svgArrow = getChartDifferenceArrow( difference );
		const dateRange = sprintf(
			/* translators: 1: date for user stats, 2: previous date for user stats comparison */
			_x( '%1$s vs %2$s', 'Date range for chart tooltip', 'google-site-kit' ),
			currentDate.toLocaleDateString( locale, localeDateOptions ),
			previousDate.toLocaleDateString( locale, localeDateOptions ),
		);

		let tooltipData = numFmt( currentMonth, metadata?.currency );
		if ( metadata?.type === 'METRIC_RATIO' ) {
			tooltipData = numFmt( currentMonth, '%' );
		}

		const statInfo = sprintf(
			/* translators: 1: selected stat label, 2: numeric value of selected stat, 3: up or down arrow , 4: different change in percentage, %%: percent symbol */
			_x( '%1$s: %2$s %3$s %4$s%%', 'Stat information for chart tooltip', 'google-site-kit' ),
			label,
			tooltipData,
			svgArrow,
			Math.abs( difference ).toFixed( 2 ).replace( /(.00|0)$/, '' ), // .replace( ... ) removes trailing zeros
		);

		dataMap.push( [
			currentDate,
			`
 0,
				'googlesitekit-visualization-tooltip--down': difference < 0,
			} ) }">

${ dateRange }

${ statInfo }

`,
			currentMonth,
			prevMonth,
		] );

		currentDate = nextDate( currentDate );
		previousDate = nextDate( previousDate );
	}

	return dataMap;
}
<|endoftext|>


<commit_before>var Class = require('./Class'),
	extend = require('./extend')

module.exports = Class(function() {

	var defaults = {
		duration:250,
		interval:40,
		tween:function linearTween(delta) { return delta }
	}

	this.init = function(animationFunction, opts) {
		this._animationFunction = animationFunction
		opts = extend(opts, defaults)
		this._duration = opts.duration
		this._interval = opts.interval
		this._tween = opts.tween
		this._onDone = opts.onDone
	}

	this.start = function(reverse) {
    this._playing = true
		this._startT = new Date().getTime()
		this._reverse = reverse
		this._onInterval()
		this._intervalID = setInterval(bind(this, this._onInterval), this._interval)
	}

	this.stop = function() {
    this._playing = false
		clearInterval(this._intervalID)
	}

  this.isGoing = function() { return this._playing }

	this._onInterval = function() {
		var deltaT = new Date().getTime() - this._startT,
		  duration = this._duration
		if (deltaT >= duration) {
		  this.stop()
		  this._animationFunction(this._tween(this._reverse ? 0 : 1))
		  if (this._onDone) { this._onDone() }
		  return
		}
		var delta = deltaT / duration
		if (this._reverse) { delta = 1 - delta }
		this._animationFunction(this._tween(delta))
	}

})

// Easing equation function for elastic tween: http://code.google.com/p/kawanet/source/browse/lang/as3/KTween/trunk/src/net/kawa/tween/easing/Elastic.as
module.exports.elasticEaseOut = function(delta) {
	var x = 1 - delta,
		elasticity = 0.25
		value = 1 - Math.pow(x, 4) + x * x * Math.sin(delta * delta * Math.PI * elasticity)
	return value
}
<commit_msg>whitespace fix<commit_after>var Class = require('./Class'),
	extend = require('./extend')

module.exports = Class(function() {

	var defaults = {
		duration:250,
		interval:40,
		tween:function linearTween(delta) { return delta }
	}

	this.init = function(animationFunction, opts) {
		this._animationFunction = animationFunction
		opts = extend(opts, defaults)
		this._duration = opts.duration
		this._interval = opts.interval
		this._tween = opts.tween
		this._onDone = opts.onDone
	}

	this.start = function(reverse) {
    this._playing = true
		this._startT = new Date().getTime()
		this._reverse = reverse
		this._onInterval()
		this._intervalID = setInterval(bind(this, this._onInterval), this._interval)
	}

	this.stop = function() {
    this._playing = false
		clearInterval(this._intervalID)
	}

	this.isGoing = function() { return this._playing }

	this._onInterval = function() {
		var deltaT = new Date().getTime() - this._startT,
		  duration = this._duration
		if (deltaT >= duration) {
		  this.stop()
		  this._animationFunction(this._tween(this._reverse ? 0 : 1))
		  if (this._onDone) { this._onDone() }
		  return
		}
		var delta = deltaT / duration
		if (this._reverse) { delta = 1 - delta }
		this._animationFunction(this._tween(delta))
	}

})

// Easing equation function for elastic tween: http://code.google.com/p/kawanet/source/browse/lang/as3/KTween/trunk/src/net/kawa/tween/easing/Elastic.as
module.exports.elasticEaseOut = function(delta) {
	var x = 1 - delta,
		elasticity = 0.25
		value = 1 - Math.pow(x, 4) + x * x * Math.sin(delta * delta * Math.PI * elasticity)
	return value
}
<|endoftext|>


<commit_before>import Rx from 'rx'

import githubClientFactory from '../apis/github-client-factory'

export default (token, query) => {
  const GithubClient = githubClientFactory(token)

  return Rx.Observable.create((observer) => {
    GithubClient.repos.getAll({

      // https://developer.github.com/v3/repos/#parameters
      visibility: 'public',
      type: 'owner',
      sort: 'updated',
      direction: 'desc'

    }, (error, repos) => {

      if (error) throw(error)

      repos

        // Only get the repos we want
        .filter((repo) => {

          // A single project
          if (query.single) {
            return repo.name === `codelab-${query.single}`
          }
          // An exclusive list of projects
          else if (query.only) {
            const keys = query.only.split(',')
            return keys.some((key) => repo.name === `codelab-${key}`)
          }
          // An exclusive list of projects
          else if (query.keys) {
            const keys = query.keys.split(',')
            return keys.some((key) => repo.name === `codelab-${key}`)
          }
          // All Code Lab projects
          else {
            return repo.name.match(/^codelab-/)
          }

        })

        // Push a formatted object to the observable array
        .forEach((repo) => {
          observer.onNext({
            name: repo.name,
            owner: repo.owner.login,
            status: 'started'
          })
        })

      observer.onCompleted()

    })
  })
}
<commit_msg>fetch up to 100 repos instead of just first 30 found<commit_after>import Rx from 'rx'

import githubClientFactory from '../apis/github-client-factory'

export default (token, query) => {
  const GithubClient = githubClientFactory(token)

  return Rx.Observable.create((observer) => {
    GithubClient.repos.getAll({

      // https://developer.github.com/v3/repos/#parameters
      visibility: 'public',
      type: 'owner',
      sort: 'updated',
      direction: 'desc',
      per_page: 100

    }, (error, repos) => {

      if (error) throw(error)

      repos

        // Only get the repos we want
        .filter((repo) => {

          // A single project
          if (query.single) {
            return repo.name === `codelab-${query.single}`
          }
          // An exclusive list of projects
          else if (query.only) {
            const keys = query.only.split(',')
            return keys.some((key) => repo.name === `codelab-${key}`)
          }
          // An exclusive list of projects
          else if (query.keys) {
            const keys = query.keys.split(',')
            return keys.some((key) => repo.name === `codelab-${key}`)
          }
          // All Code Lab projects
          else {
            return repo.name.match(/^codelab-/)
          }

        })

        // Push a formatted object to the observable array
        .forEach((repo) => {
          observer.onNext({
            name: repo.name,
            owner: repo.owner.login,
            status: 'started'
          })
        })

      observer.onCompleted()

    })
  })
}
<|endoftext|>


<commit_before>/**
 * Created by sunshine on 15/9/12.
 */

$(".sub-nav").attr("style", "padding-left: 35px;");<commit_msg>add padding in dashboard<commit_after>/**
 * Created by sunshine on 15/9/12.
 */

$(function () {
    $(".sub-nav").attr("style", "padding-left: 35px;");
})<|endoftext|>