<commit_before>(function($) {

var $library = $("div.dm_media_library");

$library.find('ul.content li.file a.link').bind('click', function()
{
  var $dialog = $.dm.ctrl.ajaxDialog({
    url:      $(this).attr("href"),
    'class':  'dm_media_library dm_media_file_dialog',
    width:    700,
    height:   420
  }).bind('dmAjaxResponse', function()
  {
    $dialog.prepare();
    $formWrap = $('div.form', $dialog);
    $dialog.dialog('option', 'title', $('.title', $dialog).text());
    $("form", $dialog).dmAjaxForm({
      beforeSubmit: function()
      {
        $formWrap.block();
      },
      success:  function(data) {
        if (!data.match(/</))
        {
          $library.block();
          location.href = data;
        }
        else
        {
          $formWrap.unblock().html(data);
          $dialog.trigger('dmAjaxResponse');
        }
      }
    });
  });
  return false;
});

$library.find("div.control a.dialog_me").bind('click', function()
{
  var $dialog = $.dm.ctrl.ajaxDialog({
    modal:  true,
    title:  $(this).html(),
    url:    $(this).attr("href"),
    width:  380
  }).bind('dmAjaxResponse', function()
  {
    $dialog.prepare();
    $("form", $dialog).dmAjaxForm({
      beforeSubmit: function()
      {
        $dialog.block();
      },
      success:  function(data)
      {
        if (!data.match(/</))
        {
          $library.block();
          location.href = data;
        }
        else
        {
          $dialog.unblock().html(data).trigger('dmAjaxResponse');
        }
      }
    });
  });

  return false;
});

if ($library.metadata().open_media)
{
  $('li.file.media_id_'+$library.metadata().open_media+' a.link', $content).trigger('click');
}

})(jQuery);<commit_msg>fix: ajaxDialog: modal: dependant on browser for admin js  media control<commit_after>(function($) {

var $library = $("div.dm_media_library");

$library.find('ul.content li.file a.link').bind('click', function()
{
  var $dialog = $.dm.ctrl.ajaxDialog({
    url:      $(this).attr("href"),
    'class':  'dm_media_library dm_media_file_dialog',
    width:    700,
    height:   420
  }).bind('dmAjaxResponse', function()
  {
    $dialog.prepare();
    $formWrap = $('div.form', $dialog);
    $dialog.dialog('option', 'title', $('.title', $dialog).text());
    $("form", $dialog).dmAjaxForm({
      beforeSubmit: function()
      {
        $formWrap.block();
      },
      success:  function(data) {
        if (!data.match(/</))
        {
          $library.block();
          location.href = data;
        }
        else
        {
          $formWrap.unblock().html(data);
          $dialog.trigger('dmAjaxResponse');
        }
      }
    });
  });
  return false;
});

$library.find("div.control a.dialog_me").bind('click', function()
{
  var $dialog = $.dm.ctrl.ajaxDialog({
    modal:  $.browser.mozilla && $.browser.version == '5.0' ? false : true,
    title:  $(this).html(),
    url:    $(this).attr("href"),
    width:  380
  }).bind('dmAjaxResponse', function()
  {
    $dialog.prepare();
    $("form", $dialog).dmAjaxForm({
      beforeSubmit: function()
      {
        $dialog.block();
      },
      success:  function(data)
      {
        if (!data.match(/</))
        {
          $library.block();
          location.href = data;
        }
        else
        {
          $dialog.unblock().html(data).trigger('dmAjaxResponse');
        }
      }
    });
  });

  return false;
});

if ($library.metadata().open_media)
{
  $('li.file.media_id_'+$library.metadata().open_media+' a.link', $content).trigger('click');
}

})(jQuery);<|endoftext|>


<commit_before>var Broker = require('../').Broker
var broker = new Broker({
  redis: 6379
})
broker.start(process.env.PORT, function(){
  console.log('Spinal:Broker listening...'+this.port)
})

// setTimeout(function(){
//   broker.stop()
// }, 7000)<commit_msg>Update broker.js example<commit_after>var Broker = require('../').Broker
var broker = new Broker({
  redis: 6379
})
broker.start(process.env.PORT || 7557, function(){
  console.log('Spinal:Broker listening...' + this.port)
})

// setTimeout(function(){
//   broker.stop()
// }, 7000)
<|endoftext|>


<commit_before>(function() {

	//为了开发简单快速，目前所有代码业务都写在这一个文件里~
	//
	//
	var isLoading;

	function extend(o, p) {
		for (var i in p) {
			o[i] = p[i];
		}
		return o;
	}

	function bind() {
		var hash = window.location.hash.slice(1);
		$('[node-type=nav_li]').removeClass('active');
		if (hash == 'home' || hash === '') {
			home_line();
		} else if (hash == 'public') {
			public_line();
		}
	}

	$('[node-type=nav_li]').click(function() {
		$('[data-toggle=collapse]').trigger('click');
	});

	var contentWrap = $('[node-type=time_line]');

	contentWrap.html('正在努力加载中...');

	function get_line(url, page, cb) {
		if (!isLoading) {
			isLoading = true;
			contentWrap.html('正在努力加载中...');
			$.ajax({
				url: url,
				data: {
					count: 4,
					feature: 1,
					page: page
				},
				dataType: 'json',
				success: function(json) {
					isLoading = false;
					cb(json);
				}
			});
		}
	}

	var regUrl = /(htt(p|ps):\/\/[A-Za-z\.\d\/\?\&\;\=\-\#_(\u4e00-\u9fa5)]+)/gi;

	function stripscript(s) {
		var pattern = new RegExp("[`~!#$^&*()=|{}':;',\\[\\].<>/?~！#￥……&*（）——|{}【】‘“'。、]");
		var rs = "";
		for (var i = 0; i < s.length; i++) {
			rs = rs + s.substr(i, 1).replace(pattern, ' ');
		}
		return rs;
	}

	function fixText(txt) {
		txt = txt.replace(regUrl, '');
		txt = txt.replace(/\[*.\]/g, '');
		txt = stripscript(txt);
		return txt;
	}

	function appendContent(items) {
		var html = '
';
		items.forEach(function(item) {
			html += '
' + item.user.name + ' 说: ' + fixText(item.text) + '<button node-type="add_one" class="btn btn-mini">加入播放</button>
';
		});
		html += '
';
		contentWrap.html(html);
	}

	function home_line() {
		$('[node-type=home_line]').parent().addClass('active');
		get_line('/home_list', 1, function(json) {
			appendContent(json.items);
		});
	}

	function public_line() {
		$('[node-type=public_line]').parent().addClass('active');
		get_line('/public_list', 1, function(json) {
			appendContent(json.items);
		});
	}

	bind();

	$(window).bind('hashchange', bind);

	$('[node-type=cn]').addClass('active');

	function events() {
		this.map = {};
	}

	extend(events.prototype, {
		trigger: function(name, args) {
			var cbs = this.map[name];
			if (cbs) {
				cbs.forEach(function(fn) {
					fn.apply(this, args);
				});
			}
		},
		on: function(name, cb) {
			if (this.map[name]) {
				this.map[name].push(cb);
			} else {
				this.map[name] = [cb];
			}
		}
	});

	//声音播放类
	//
	function weiboTspeech() {
		this.supportSpeech = (window.speechSynthesis && window.SpeechSynthesisUtterance);
		if (!this.supportSpeech) {
			alert('您当前的环境不支持微博听,请使用ios设备浏览本站');
		}
		this.speeks = [];
		this.options = {
			lang: 'zh-CN',
			volume: 44,
			rate: 0.5,
			pitch: 0.8
		};
		this.speech = new SpeechSynthesisUtterance();
		this.events = new events();
	}

	weiboTspeech.prototype = {
		constructor: weiboTspeech,
		play: function() {
			if (!speechSynthesis.speaking && this.speeks.length) {
				var msg = this.speeks.shift();
				this.speech.text = msg;
				speechSynthesis.speak(this.speech);
				this.events.trigger('play');
			}
		},
		add: function(msg) {
			this.speeks.push(msg);
			this.events.trigger('add');
			return this;
		},
		init: function() {
			this.set(this.options);
			this.bindEvents(this.speech);
			return this;
		},
		bindEvents: function(speech) {
			var self = this;
			speech.onstart = function() {
				self.events.trigger('start', self);
			};
			speech.onend = function() {
				self.play();
				self.events.trigger('end', self);
			};
			speech.onerror = function(e) {
				self.events.trigger('error', self);
			};
			speech.onpause = function() {
				self.events.trigger('pause', self);
			};
			speech.onresume = function() {
				self.events.trigger('resume', self);
			};
			return this;
		},
		set: function(options) {
			extend(this.speech,options);
			return this;
		},
		clear: function() {
			this.cancel();
			this.speaks = [];
		}
	};

	var myspeaker = new weiboTspeech();
	myspeaker.init();

	if (myspeaker.supportSpeech) {
		$('[node-type=start]').click(function() {
			if (myspeaker.speeks.length) {
				myspeaker.play();
			} else {
				alert('请向播放列表添加微博');
			}
		});
		$('[node-type=add_all]').click(function() {
			contentWrap.find('[node-type=text]').each(function(index,item) {
				var text = $(item).text();
				myspeaker.add(text);
			});
		});

		contentWrap.on('[node-type=add_one]','click',function(){
            myspeaker.add($(this).prev().text());
        });

		$('[node-type=cn]').click(function(){
			myspeaker.set({lang:'zh-CN'});
		});
		$('[node-type=hk]').click(function(){
			myspeaker.set({lang:'zh-HK'});
		});
		$('[node-type=tw]').click(function(){
			myspeaker.set({lang:'zh-TW'});
		});

		function showNum() {
			$('[node-type=count]').text(myspeaker.speeks.length);
		}

		myspeaker.events.on('add', showNum);
		myspeaker.events.on('end', showNum);
	}

})();

<commit_msg>增加语音选项，优化发音类结构<commit_after>(function() {

	//为了开发简单快速，目前所有代码业务都写在这一个文件里~
	//
	//
	var isLoading;

	function extend(o, p) {
		for (var i in p) {
			o[i] = p[i];
		}
		return o;
	}

	function bind() {
		var hash = window.location.hash.slice(1);
		$('[node-type=nav_li]').removeClass('active');
		if (hash == 'home' || hash === '') {
			home_line();
		} else if (hash == 'public') {
			public_line();
		}
	}

	$('[node-type=nav_li]').click(function() {
		$('[data-toggle=collapse]').trigger('click');
	});

	var contentWrap = $('[node-type=time_line]');

	contentWrap.html('正在努力加载中...');

	function get_line(url, page, cb) {
		if (!isLoading) {
			isLoading = true;
			contentWrap.html('正在努力加载中...');
			$.ajax({
				url: url,
				data: {
					count: 4,
					feature: 1,
					page: page
				},
				dataType: 'json',
				success: function(json) {
					isLoading = false;
					cb(json);
				}
			});
		}
	}

	var regUrl = /(htt(p|ps):\/\/[A-Za-z\.\d\/\?\&\;\=\-\#_(\u4e00-\u9fa5)]+)/gi;

	function stripscript(s) {
		var pattern = new RegExp("[`~!#$^&*()=|{}':;',\\[\\].<>/?~！#￥……&*（）——|{}【】‘“'。、]");
		var rs = "";
		for (var i = 0; i < s.length; i++) {
			rs = rs + s.substr(i, 1).replace(pattern, ' ');
		}
		return rs;
	}

	function fixText(txt) {
		txt = txt.replace(regUrl, '');
		txt = txt.replace(/\[*.\]/g, '');
		txt = stripscript(txt);
		return txt;
	}

	function appendContent(items) {
		var html = '
';
		items.forEach(function(item) {
			html += '
' + item.user.name + ' 说: ' + fixText(item.text) + '<button node-type="add_one" class="btn btn-mini">加入播放</button>
';
		});
		html += '
';
		contentWrap.html(html);
	}

	function home_line() {
		$('[node-type=home_line]').parent().addClass('active');
		get_line('/home_list', 1, function(json) {
			appendContent(json.items);
		});
	}

	function public_line() {
		$('[node-type=public_line]').parent().addClass('active');
		get_line('/public_list', 1, function(json) {
			appendContent(json.items);
		});
	}

	bind();

	$(window).bind('hashchange', bind);

	$('[node-type=cn]').addClass('active');

	function events() {
		this.map = {};
	}

	extend(events.prototype, {
		trigger: function(name, args) {
			var cbs = this.map[name];
			if (cbs) {
				cbs.forEach(function(fn) {
					fn.apply(this, args);
				});
			}
		},
		on: function(name, cb) {
			if (this.map[name]) {
				this.map[name].push(cb);
			} else {
				this.map[name] = [cb];
			}
		}
	});

	//声音播放类
	//
	function weiboTspeech() {
		this.supportSpeech = (window.speechSynthesis && window.SpeechSynthesisUtterance);
		if (!this.supportSpeech) {
			alert('您当前的环境不支持微博听,请使用ios设备浏览本站');
		}
		this.speeks = [];
		this.options = {
			lang: 'zh-CN',
			volume: 44,
			rate: 0.5,
			pitch: 0.8
		};
		this.speech = new SpeechSynthesisUtterance();
		this.events = new events();
	}

	weiboTspeech.prototype = {
		constructor: weiboTspeech,
		play: function() {
			if (!speechSynthesis.speaking && this.speeks.length) {
				var msg = this.speeks.shift();
				this.speech.text = msg;
				speechSynthesis.speak(this.speech);
				this.events.trigger('play');
			}
		},
		add: function(msg) {
			this.speeks.push(msg);
			this.events.trigger('add');
			return this;
		},
		init: function() {
			this.set(this.options);
			this.bindEvents(this.speech);
			return this;
		},
		bindEvents: function(speech) {
			var self = this;
			speech.onstart = function() {
				self.events.trigger('start', self);
			};
			speech.onend = function() {
				self.play();
				self.events.trigger('end', self);
			};
			speech.onerror = function(e) {
				self.events.trigger('error', self);
			};
			speech.onpause = function() {
				self.events.trigger('pause', self);
			};
			speech.onresume = function() {
				self.events.trigger('resume', self);
			};
			return this;
		},
		set: function(options) {
			extend(this.speech,options);
			return this;
		},
		clear: function() {
			this.cancel();
			this.speaks = [];
		}
	};

	var myspeaker = new weiboTspeech();
	myspeaker.init();

	if (myspeaker.supportSpeech) {
		$('[node-type=start]').click(function() {
			if (myspeaker.speeks.length) {
				myspeaker.play();
			} else {
				alert('请向播放列表添加微博');
			}
		});
		$('[node-type=add_all]').click(function() {
			contentWrap.find('[node-type=text]').each(function(index,item) {
				var text = $(item).text();
				myspeaker.add(text);
			});
		});

		contentWrap.on('click','[node-type=add_one]',function(){
            		myspeaker.add($(this).prev().text());
        	});

		$('[node-type=cn]').click(function(){
			myspeaker.set({lang:'zh-CN'});
		});
		$('[node-type=hk]').click(function(){
			myspeaker.set({lang:'zh-HK'});
		});
		$('[node-type=tw]').click(function(){
			myspeaker.set({lang:'zh-TW'});
		});

		function showNum() {
			$('[node-type=count]').text(myspeaker.speeks.length);
		}

		myspeaker.events.on('add', showNum);
		myspeaker.events.on('end', showNum);
	}

})();

<|endoftext|>


<commit_before>function determineWeekday() {
  var today = new Date();
  var weekday_integer = today.getDay(); // 0 for Sunday, 1 for Monday, etc
  var hour = today.getHours(); // 0 for midnight, 23:59 for 11:59pm

  // is it late night? food trucks might still be out from previous day
  if (hour >= 0 && hour <= 4) {
    // is it sunday? rewinding one day needs to be 6, not -1
    if (weekday_integer === 0) {
      weekday_integer = 6;
    } else {
      weekday_integer -= 1;
    }
  }

  var weekdayMap = {
    0: "sunday",
    1: "monday",
    2: "tuesday",
    3: "wednesday",
    4: "thursday",
    5: "friday",
    6: "saturday"
  };
  var weekday = weekdayMap[weekday_integer];
  return weekday;
}

function convertToAmPm(time) {
  var hour = time.split(":")[0]; // 12:00 => 12
  var min = time.split(":")[1]; // 12:00 => 00
  var ampm;
  if (hour === 0) {
    hour = 12; // 0:00 => 12:00
    ampm = "am";
  }
  if (hour > 12) {
    hour -= 12; // 13:00 => 1:00
    ampm = "pm";
  } else {
    ampm = "am";
  }
  var converted_time = hour + ":" + min + ampm;
  return converted_time;
}

function getFoodtrucks(map, infowindow) {
  $.ajax({
    type: "GET",
    url: '/api/foodtrucks',
    dataType: "json",
    success: function (res) {
      pinFoodtrucks(res, map, infowindow);
    }
  });
}

function pinFoodtrucks(foodtrucks, map, infowindow) {
  var weekday = determineWeekday();

  for (i = 0; i < foodtrucks.length; i++) {
    var lng = foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[0];
    var lat = foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[1];
    // var latLng = new google.maps.LatLng(foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[1], foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[0]);
    var latLng = new google.maps.LatLng(lat, lng);
    var name = foodtrucks[i].name;
    var cuisine = foodtrucks[i].cuisine;
    var payment = foodtrucks[i].payment.toLowerCase();
    var description = foodtrucks[i].description;
    var open = foodtrucks[i].schedule["" + weekday + ""][0].open;
    if (open !== undefined) {
      open = convertToAmPm(open);
    }
    var close = foodtrucks[i].schedule["" + weekday + ""][0].close;
    if (close !== undefined) {
      close = convertToAmPm(close);
    }
    var address = foodtrucks[i].schedule["" + weekday + ""][0].address;

    var content = "

Name: " + name + "

Cuisine: " + cuisine + "

Accepted payment: " + payment + "

" + description + "

Open: " + open + "

Close: " + close + "

Address (approximate): " + address + "

";

    var marker = new google.maps.Marker({
      position: latLng,
      map: map,
      label: "F",
      content: content
    });

    marker.addListener('click', function(e) {
      infowindow.setContent(this.content);
      infowindow.open(map, this);
    });
  }
}

// function getBreweries() {
//
// }
//
// function getDistilleries() {
//
// }

function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 12,
    center: {lat: 47.6097, lng: -122.3331}
  });

  var infowindow = new google.maps.InfoWindow({});

  getFoodtrucks(map, infowindow);
  // getBreweries();
  // getDistilleries();
}
<commit_msg>Added contact links. Hard-coded and thus broken if undefined. Will dynamically append later.<commit_after>function determineWeekday() {
  var today = new Date();
  var weekday_integer = today.getDay(); // 0 for Sunday, 1 for Monday, etc
  var hour = today.getHours(); // 0 for midnight, 23:59 for 11:59pm

  // is it late night? food trucks might still be out from previous day
  if (hour >= 0 && hour <= 4) {
    // is it sunday? rewinding one day needs to be 6, not -1
    if (weekday_integer === 0) {
      weekday_integer = 6;
    } else {
      weekday_integer -= 1;
    }
  }

  var weekdayMap = {
    0: "sunday",
    1: "monday",
    2: "tuesday",
    3: "wednesday",
    4: "thursday",
    5: "friday",
    6: "saturday"
  };
  var weekday = weekdayMap[weekday_integer];
  return weekday;
}

function convertToAmPm(time) {
  var hour = time.split(":")[0]; // 12:00 => 12
  var min = time.split(":")[1]; // 12:00 => 00
  var ampm;
  if (hour === 0) {
    hour = 12; // 0:00 => 12:00
    ampm = "am";
  }
  if (hour > 12) {
    hour -= 12; // 13:00 => 1:00
    ampm = "pm";
  } else {
    ampm = "am";
  }
  var converted_time = hour + ":" + min + ampm;
  return converted_time;
}

function getFoodtrucks(map, infowindow) {
  $.ajax({
    type: "GET",
    url: '/api/foodtrucks',
    dataType: "json",
    success: function (res) {
      pinFoodtrucks(res, map, infowindow);
    }
  });
}

function pinFoodtrucks(foodtrucks, map, infowindow) {
  var weekday = determineWeekday();

  for (i = 0; i < foodtrucks.length; i++) {
    var lng = foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[0];
    var lat = foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[1];
    // var latLng = new google.maps.LatLng(foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[1], foodtrucks[i].schedule["" + weekday + ""][0].geometry.coordinates[0]);
    var latLng = new google.maps.LatLng(lat, lng);
    var name = foodtrucks[i].name;
    var cuisine = foodtrucks[i].cuisine;
    var payment = foodtrucks[i].payment.toLowerCase();
    var description = foodtrucks[i].description;
    var open = foodtrucks[i].schedule["" + weekday + ""][0].open;
    if (open !== undefined) {
      open = convertToAmPm(open);
    }
    var close = foodtrucks[i].schedule["" + weekday + ""][0].close;
    if (close !== undefined) {
      close = convertToAmPm(close);
    }
    var address = foodtrucks[i].schedule["" + weekday + ""][0].address;
    var facebook_url = foodtrucks[i].contact.facebook;
    var facebook_link = "Facebook";
    var twitter_url = foodtrucks[i].contact.twitter_link;
    var twitter_link = "Twitter";
    var website_url = foodtrucks[i].contact.website;
    var website_link = "website";

    // add way to only append urls if they are defined

    var content = "

" + name + "

Cuisine: " + cuisine + "

Accepted payment: " + payment + "

" + description + "

Hours: " + open + " - " + close + "
" + facebook_link + " " + twitter_link + " " + website_link + "
Address (approximate): " + address + "

";

    var marker = new google.maps.Marker({
      position: latLng,
      map: map,
      label: "F",
      content: content
    });

    marker.addListener('click', function(e) {
      infowindow.setContent(this.content);
      infowindow.open(map, this);
    });
  }
}

// function getBreweries() {
//
// }
//
// function getDistilleries() {
//
// }

function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 12,
    center: {lat: 47.6097, lng: -122.3331}
  });

  var infowindow = new google.maps.InfoWindow({});

  getFoodtrucks(map, infowindow);
  // getBreweries();
  // getDistilleries();
}
<|endoftext|>


<commit_before>(function( $ ){
    /**
     * Pops up an alert dialog using jQuery UI
     *
     * @param {String} text what to say to user
     * @param {Function} okCallBack [optional] called for ok button
     */
    $.fn.alert = function(text, okCallBack){
        $('
' + text + '
').dialog({
            title: 'Alert',
            modal: true,
            buttons: {
                "Ok": function() {
                    if(typeof(okCallBack)=='function'){
                        okCallBack();
                    }
                    $( this ).dialog( "close" );
                }
            }
        });
    };

    /**
     * Pops up a confirm dialog using jQuery UI
     *
     * @param {String} text what we are asking the user to confirm
     * @param {Function} [okCallBack] called for ok button click
     * @param {Function} [cancelCallBack] called for cancel button click
     */
    $.fn.confirm = function(text, okCallBack, cancelCallBack){
        var p = $('

');
        p.append(text);
        p.dialog({
            title: 'Confirm',
            modal: true,
            buttons: {
                "Ok": function() {
                    if(typeof(okCallBack)=='function'){
                        okCallBack();
                    }
                    $( this ).dialog( "close" );
                },
                Cancel: function() {
                    if(typeof(cancelCallBack)=='function'){
                        cancelCallBack();
                    }
                    $( this ).dialog( "close" );
                }
            }
        });
    };

    $.fn.addImage = function (name, description, src) {
        var p = $('

');
        this.append(p);
        p.append('' + description + '
' +
            '');
        var urlBox=$('<input style="width:370px;margin-right:10px" name="' + name + '" value="' + src + '">');
        p.append(urlBox);
        p.append('<button type="button" class="image-button ui-button ui-widget ' +
            'ui-state-default ui-corner-all ui-button-text-only" role="button" ' +
            'aria-disabled="false">' +
            'Browse' +
            '</button>');

        if(!this.addImageEventsDelegated){

            this.addImageEventsDelegated = true;

            this.delegate('.imageInput button, .imageInput img','click',
                function(){
                    rcmEdit.showFileBrowserForInputBox(
                        $(this).parent().children('input')
                        ,'images'
                    );
                }
            );
            this.delegate('.imageInput input','change', function(){
                $(this).parent().children('img').attr('src', $(this).val());
            });
        }
        return this;
    };

    /**
     * Build html for a text input
     *
     * @param {String} name html name
     * @param {String} description title to show user
     * @param {String} value the current value
     *
     * @return String
     */
    $.fn.addInput = function (name, description, value) {
        this.append('' +
            '
' + description + '
' +
            '<input name="' + name + '" value="' + value + '">
'
        );
        return this;
    };

    /**
     * Build html for a text input
     *
     * @param {String} name html name
     * @param {String} description title to show user
     * @param {String} value the current value
     *
     * @return String
     */
    $.fn.addDate = function (name, description, value) {
        var p = $('
' + description + '' +
            '

');
        var input = $('<input name="' + name + '" value="' + value + '">');
        p.append(input);
        this.append(p);
        input.datepicker();
        return this;
    };

    /**
    * Build html for a text input
    *
    * Due to ckEditor limitations, this must be called AFTER .dialog is called
    *
    * @param {String} name html name
    * @param {String} description title to show user
    * @param {String} value the current value
    * @param {Object} [toolBarConfig] tool bar config for ckEditor
    *
    * @return String
    */
    $.fn.addRichEdit = function (name, description, value, toolBarConfig) {

        if(typeof(toolBarConfig)=='undefined'){
            toolBarConfig = {
                toolbar: [
                    { name: 'document', items : [ 'Source' ] },
                    { name: 'undoRedo', items : ['Undo','Redo'] },
                    { name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
                    { name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv',
                        '-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] },
                    { name: 'clipboard', items : ['Cut','Copy','Paste','PasteText','PasteFromWord'] },
                    { name: 'insert', items : [ 'Image', 'Table','HorizontalRule','SpecialChar','Templates'] },
                    { name: 'links', items : [ 'Link','Unlink','Anchor' ] },
                ]
            };
        }

        var id = $.fn.generateUUID();
        var div = $('
' + value +'
');
        var p = $(
            '
' +
                '' + description + '
' +
            '
'
        );
        this.append(p);
        p.append(div);
        // This terrible timeout hack is needed because the new version of
        // ckEditor only works on elements that are in the DOM
        setTimeout(
            function(){
                CKEDITOR.replace(id,toolBarConfig);
            },
            100
        );
        return this;
    };

    /**
     * Build html for a select drop down box
     *
     * @param {String} name html name
     * @param {String} description title to show user
     * @param {Array} choices options [html key => display value]
     * @param {String} [value] current choice key
     * @param {Boolean} [allowCustomValues] allow user to enter custom values that
     *                  are no in the select
     *
     * @return {String}
     */
    $.fn.addSelect = function (name, description, choices, value, allowCustomValues) {
        var p = $('

');
        var selected;
        p.append('' + description + '
');
        var customClass = '';
        if(allowCustomValues){
            customClass = ' class="selectAllowCustomValues"';
        }
        var select=$('<select' + customClass + ' name="' + name + '"><select>');

        for (var key in choices) {
            selected = '';
            if (key == value) {
                selected=' selected="selected"';
            }
            select.append('<option value="' + key + '"' + selected + '>' + choices[key] + '</option>')
        }
        var inputBox = '';
        if (allowCustomValues) {

            selected = '';
            var displayNone = ' style="display:none"';
            var customValue = '';
            if (!(value in choices)) {
                selected=' selected="selected"';
                displayNone = '';
                customValue=value;
            }
            select.append('<option class="custom" value="' + customValue + '"' + selected + '>Custom Value</option>');
            inputBox = $('<input' + customClass + displayNone + ' size="80" value="' + customValue + '">');
        }

        p.append(select);
        p.append(inputBox);

        this.append(p);

        //Ensure events are attached for the custom input box
        if(allowCustomValues&&!this.selectAllowCustomValuesDelegated){

            this.selectAllowCustomValuesDelegated=true;

            //Hide/show the custom text box if the 'Custom Value' is modded
            this.delegate('select.selectAllowCustomValues','change',function(event){
                var select = $(event.target);
                var textBox = select.parent().children('input');
                if(select.children('option.custom').attr('selected')
                    == 'selected'
                    ) {
                    textBox.show();
                }else{
                    textBox.hide();
                }
            });

            //Move any input box input to the select key value
            this.delegate('input.selectAllowCustomValues','change',function(event){
                var textBox = $(event.target);
                textBox.parent().children('select')
                    .children('option.custom').val(textBox.val());
            });
        }
        return this;
    };

    /**
     * Build a check box
     *
     * @param {String} name name
     * @param {String} description name to show user
     * @param {Boolean} checked is it checked?
     *
     * @return {String}
     */
    $.fn.addCheckBox = function (name, description, checked) {
        var checkedHtml = '';
        if (checked) {
            checkedHtml = ' checked="checked"';
        }
        this.append(
            '
<input type="checkbox"' + checkedHtml + ' name="' + name +
                '" value="true" />' + description + '
'
        );
        return this;
    };

    /**
     * Generates RFC4122 v4 compliant random ids
     * @return {String}
     */
    $.fn.generateUUID = function(){
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
            /[xy]/g,
            function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            }
        );
    };
})( jQuery );
<commit_msg>improve people slider<commit_after>(function( $ ){
    /**
     * Pops up an alert dialog using jQuery UI
     *
     * @param {String} text what to say to user
     * @param {Function} okCallBack [optional] called for ok button
     */
    $.fn.alert = function(text, okCallBack){
        $('
' + text + '
').dialog({
            title: 'Alert',
            modal: true,
            buttons: {
                "Ok": function() {
                    if(typeof(okCallBack)=='function'){
                        okCallBack();
                    }
                    $( this ).dialog( "close" );
                }
            }
        });
    };

    /**
     * Pops up a confirm dialog using jQuery UI
     *
     * @param {String} text what we are asking the user to confirm
     * @param {Function} [okCallBack] called for ok button click
     * @param {Function} [cancelCallBack] called for cancel button click
     */
    $.fn.confirm = function(text, okCallBack, cancelCallBack){
        var p = $('

');
        p.append(text);
        p.dialog({
            title: 'Confirm',
            modal: true,
            buttons: {
                "Ok": function() {
                    if(typeof(okCallBack)=='function'){
                        okCallBack();
                    }
                    $( this ).dialog( "close" );
                },
                Cancel: function() {
                    if(typeof(cancelCallBack)=='function'){
                        cancelCallBack();
                    }
                    $( this ).dialog( "close" );
                }
            }
        });
    };

    $.fn.addImage = function (name, description, src) {
        var p = $('

');
        this.append(p);
        p.append('' + description + '
' +
            '');
        var urlBox=$('<input style="width:370px;margin-right:10px" name="' + name + '" value="' + src + '">');
        p.append(urlBox);
        p.append('<button type="button" class="image-button ui-button ui-widget ' +
            'ui-state-default ui-corner-all ui-button-text-only" role="button" ' +
            'aria-disabled="false">' +
            'Browse' +
            '</button>');

        if(!this.addImageEventsDelegated){

            this.addImageEventsDelegated = true;

            this.delegate('.imageInput button, .imageInput img','click',
                function(){
                    rcmEdit.showFileBrowserForInputBox(
                        $(this).parent().children('input')
                        ,'images'
                    );
                }
            );
            this.delegate('.imageInput input','change', function(){
                $(this).parent().children('img').attr('src', $(this).val());
            });
        }
        return this;
    };

    /**
     * Build html for a text input
     *
     * @param {String} name html name
     * @param {String} description title to show user
     * @param {String} value the current value
     *
     * @return String
     */
    $.fn.addInput = function (name, description, value) {
        this.append('' +
            '
' + description + '
' +
            '<input name="' + name + '" value="' + value + '">
'
        );
        return this;
    };

    /**
     * Build html for a text input
     *
     * @param {String} name html name
     * @param {String} description title to show user
     * @param {String} value the current value
     *
     * @return String
     */
    $.fn.addDate = function (name, description, value) {
        var p = $('
' + description + '' +
            '

');
        var input = $('<input name="' + name + '" value="' + value + '">');
        p.append(input);
        this.append(p);
        input.datepicker();
        return this;
    };

    /**
    * Build html for a text input
    *
    * Due to ckEditor limitations, this must be called AFTER .dialog is called
    *
    * @param {String} name html name
    * @param {String} description title to show user
    * @param {String} value the current value
    * @param {Object} [toolBarConfig] tool bar config for ckEditor
    *
    * @return String
    */
    $.fn.addRichEdit = function (name, description, value, toolBarConfig) {

        if(typeof(toolBarConfig)=='undefined'){
            toolBarConfig = {
                toolbar: [
                    { name: 'document', items : [ 'Source' ] },
                    { name: 'undoRedo', items : ['Undo','Redo'] },
                    { name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
                    { name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv',
                        '-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock' ] },
                    { name: 'clipboard', items : ['Cut','Copy','Paste','PasteText','PasteFromWord'] },
                    { name: 'insert', items : [ 'Image', 'Table','HorizontalRule','SpecialChar','Templates'] },
                    { name: 'links', items : [ 'Link','Unlink','Anchor' ] }
                ]
            };
        }

        var id = $.fn.generateUUID();
        var div = $('
' + value +'
');
        var p = $(
            '
' +
                '' + description + '
' +
            '
'
        );
        this.append(p);
        p.append(div);
        // This terrible timeout hack is needed because the new version of
        // ckEditor only works on elements that are in the DOM
        setTimeout(
            function(){
                CKEDITOR.replace(id,toolBarConfig);
            },
            100
        );
        return this;
    };

    /**
     * Build html for a select drop down box
     *
     * @param {String} name html name
     * @param {String} description title to show user
     * @param {Array} choices options [html key => display value]
     * @param {String} [value] current choice key
     * @param {Boolean} [allowCustomValues] allow user to enter custom values that
     *                  are no in the select
     *
     * @return {String}
     */
    $.fn.addSelect = function (name, description, choices, value, allowCustomValues) {
        var p = $('

');
        var selected;
        p.append('' + description + '
');
        var customClass = '';
        if(allowCustomValues){
            customClass = ' class="selectAllowCustomValues"';
        }
        var select=$('<select' + customClass + ' name="' + name + '"><select>');

        for (var key in choices) {
            selected = '';
            if (key == value) {
                selected=' selected="selected"';
            }
            select.append('<option value="' + key + '"' + selected + '>' + choices[key] + '</option>')
        }
        var inputBox = '';
        if (allowCustomValues) {

            selected = '';
            var displayNone = ' style="display:none"';
            var customValue = '';
            if (!(value in choices)) {
                selected=' selected="selected"';
                displayNone = '';
                customValue=value;
            }
            select.append('<option class="custom" value="' + customValue + '"' + selected + '>Custom Value</option>');
            inputBox = $('<input' + customClass + displayNone + ' size="80" value="' + customValue + '">');
        }

        p.append(select);
        p.append(inputBox);

        this.append(p);

        //Ensure events are attached for the custom input box
        if(allowCustomValues&&!this.selectAllowCustomValuesDelegated){

            this.selectAllowCustomValuesDelegated=true;

            //Hide/show the custom text box if the 'Custom Value' is modded
            this.delegate('select.selectAllowCustomValues','change',function(event){
                var select = $(event.target);
                var textBox = select.parent().children('input');
                if(select.children('option.custom').attr('selected')
                    == 'selected'
                    ) {
                    textBox.show();
                }else{
                    textBox.hide();
                }
            });

            //Move any input box input to the select key value
            this.delegate('input.selectAllowCustomValues','change',function(event){
                var textBox = $(event.target);
                textBox.parent().children('select')
                    .children('option.custom').val(textBox.val());
            });
        }
        return this;
    };

    /**
     * Build a check box
     *
     * @param {String} name name
     * @param {String} description name to show user
     * @param {Boolean} checked is it checked?
     *
     * @return {String}
     */
    $.fn.addCheckBox = function (name, description, checked) {
        var checkedHtml = '';
        if (checked) {
            checkedHtml = ' checked="checked"';
        }
        this.append(
            '
<input type="checkbox"' + checkedHtml + ' name="' + name +
                '" value="true" />' + description + '
'
        );
        return this;
    };

    /**
     * Converts a given element to a rich edit. WARNING: changes ele ID
     * @param ele
     * @return {*}
     */
    $.fn.richEdit = function (ele) {
        var id = $.fn.generateUUID();
        ele.attr('id', id);
        CKEDITOR.replace(id,rcmCkConfig);
        return this;
    };

    /**
     * Generates RFC4122 v4 compliant random ids
     * @return {String}
     */
    $.fn.generateUUID = function(){
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
            /[xy]/g,
            function(c) {
                var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
                return v.toString(16);
            }
        );
    };
})( jQuery );
<|endoftext|>


<commit_before>/* eslint no-useless-escape: 0 */
const mimelib = require('mimelib');
const encoding = require('encoding');
const he = require('he');
const os = require('os');
const fs = require('fs');
const path = require('path')
const mkdirp = require('mkdirp');
const {Imap, Errors: {APIError}} = require('isomorphic-core');

// aiming for the former in length, but the latter is the hard db cutoff
const SNIPPET_SIZE = 100;
const SNIPPET_MAX_SIZE = 255;

// The input is the value of a to/cc/bcc/from header as parsed by the imap
// library we're using, but it currently parses them in a weird format. If an
// email is sent to a@example.com and b@example.com, the parsed output of the
// 'to' header is ['a@example.com, b@example.com']. (Note both emails are in
// the same string.) When fixed, this function will need to update accordingly.
function extractContacts(input) {
  if (!input || input.length === 0 || !input[0]) {
    return [];
  }
  const s = `["${input[0].replace(/"/g, '\\"').replace(/, /g, '", "')}"]`;
  const values = JSON.parse(s);
  return values.map(v => {
    const parsed = mimelib.parseAddresses(v)
    if (!parsed || parsed.length === 0) {
      return null
    }
    const {name, address: email} = parsed.pop()
    return {name, email}
  })
  .filter(c => c != null)
}

/*
Iteratively walk the DOM of this document's <body>, calling the callback on
each node. Skip any nodes and the skipTags set, including their children.
*/
function _walkBodyDOM(doc, callback, skipTags) {
  let nodes = Array.from(doc.body.childNodes);

  while (nodes.length) {
    const node = nodes.shift();

    callback(node);

    if (!skipTags.has(node.tagName)) {
      if (node.childNodes && node.childNodes.length) {
        nodes = Array.from(node.childNodes).concat(nodes);
      }
    }
  }
}

function extractSnippet(plainBody, htmlBody) {
  let snippetText = plainBody || '';
  if (htmlBody) {
    const doc = new DOMParser().parseFromString(htmlBody, 'text/html')
    const extractedTextElements = [];

    _walkBodyDOM(doc, (node) => {
      if (node.nodeType === Node.TEXT_NODE) {
        const nodeValue = node.nodeValue ? node.nodeValue.trim() : null;
        if (nodeValue) {
          extractedTextElements.push(nodeValue);
        }
      }
    }, new Set(['TITLE', 'SCRIPT', 'STYLE', 'IMG']));

    const extractedText = extractedTextElements.join(' ').trim();
    if (extractedText) {
      snippetText = extractedText;
    }
  }

  // clean up and trim snippet
  let trimmed = snippetText.trim().replace(/[\n\r]/g, ' ').replace(/\s\s+/g, ' ').substr(0, SNIPPET_MAX_SIZE);
  if (trimmed) {
  // TODO: strip quoted text from snippets also
    // trim down to approx. SNIPPET_SIZE w/out cutting off words right in the
    // middle (if possible)
    const wordBreak = trimmed.indexOf(' ', SNIPPET_SIZE);
    if (wordBreak !== -1) {
      trimmed = trimmed.substr(0, wordBreak);
    }
  }
  return trimmed;
}

/*
Preserve whitespacing on plaintext emails -- has the side effect of
monospacing, but that seems OK and perhaps sometimes even desired (for e.g.
ascii art, alignment)
*/
function HTMLifyPlaintext(text) {
  const escapedText = he.escape(text);
  return `<pre class="nylas-plaintext">${escapedText}</pre>`;
}

/*
Since we only fetch the MIME structure and specific desired MIME parts from
IMAP, we unfortunately can't use an existing library like mailparser to parse
the message, and have to do fun stuff like deal with character sets and
content-transfer-encodings ourselves.
*/
async function parseFromImap(imapMessage, desiredParts, {db, accountId, folder}) {
  const {Message, Label} = db
  const {attributes} = imapMessage

  const body = {}
  for (const {id, mimetype, transferEncoding, charset} of desiredParts) {
    // see https://www.w3.org/Protocols/rfc1341/5_Content-Transfer-Encoding.html
    if (!transferEncoding || new Set(['7bit', '8bit']).has(transferEncoding.toLowerCase())) {
      // NO transfer encoding has been performed --- how to decode to a string
      // depends ONLY on the charset, which defaults to 'ascii' according to
      // https://tools.ietf.org/html/rfc2045#section-5.2
      const convertedBuffer = encoding.convert(imapMessage.parts[id], 'utf-8', charset || 'ascii')
      body[mimetype] = convertedBuffer.toString('utf-8');
    } else if (transferEncoding.toLowerCase() === 'quoted-printable') {
      body[mimetype] = mimelib.decodeQuotedPrintable(imapMessage.parts[id], charset || 'ascii');
    } else if (transferEncoding.toLowerCase() === 'base64') {
      body[mimetype] = mimelib.decodeBase64(imapMessage.parts[id], charset || 'ascii');
    } else {
      // 'binary' and custom x-token content-transfer-encodings
      return Promise.reject(new Error(`Unsupported Content-Transfer-Encoding ${transferEncoding}, mimetype ${mimetype}`))
    }
  }
  const headers = imapMessage.headers.toString('ascii');
  const parsedHeaders = Imap.parseHeader(headers);
  for (const key of ['x-gm-thrid', 'x-gm-msgid', 'x-gm-labels']) {
    parsedHeaders[key] = attributes[key];
  }

  const parsedMessage = {
    to: extractContacts(parsedHeaders.to),
    cc: extractContacts(parsedHeaders.cc),
    bcc: extractContacts(parsedHeaders.bcc),
    from: extractContacts(parsedHeaders.from),
    replyTo: extractContacts(parsedHeaders['reply-to']),
    accountId: accountId,
    body: body['text/html'] || body['text/plain'] || body['application/pgp-encrypted'] || '',
    snippet: null,
    unread: !attributes.flags.includes('\\Seen'),
    starred: attributes.flags.includes('\\Flagged'),
    date: attributes.date,
    folderImapUID: attributes.uid,
    folderId: folder.id,
    folder: null,
    labels: [],
    headers: parsedHeaders,
    headerMessageId: parsedHeaders['message-id'] ? parsedHeaders['message-id'][0] : '',
    gMsgId: parsedHeaders['x-gm-msgid'],
    subject: parsedHeaders.subject[0],
  }
  parsedMessage.id = Message.hash(parsedMessage)

  if (!body['text/html'] && body['text/plain']) {
    parsedMessage.body = HTMLifyPlaintext(body['text/plain']);
  }

  parsedMessage.snippet = extractSnippet(body['text/plain'], body['text/html']);
  parsedMessage.folder = folder

  // TODO: unclear if this is necessary given we already have parsed labels
  const xGmLabels = attributes['x-gm-labels']
  if (xGmLabels) {
    parsedMessage.folderImapXGMLabels = JSON.stringify(xGmLabels)
    parsedMessage.labels = await Label.findXGMLabels(xGmLabels)
  }

  if (process.env.NYLAS_DEBUG) {
    const outJSON = JSON.stringify({imapMessage, desiredParts, result: parsedMessage});
    const outDir = path.join(os.tmpdir(), "k2-parse-output", folder.name)
    const outFile = path.join(outDir, imapMessage.attributes.uid.toString());
    mkdirp.sync(outDir);
    fs.writeFileSync(outFile, outJSON);
  }

  return parsedMessage;
}

function getReplyHeaders(messageReplyingTo) {
  let inReplyTo;
  let references;
  if (messageReplyingTo.headerMessageId) {
    inReplyTo = messageReplyingTo.headerMessageId;
    if (messageReplyingTo.references) {
      references = messageReplyingTo.references.concat(messageReplyingTo.headerMessageId);
    } else {
      references = [messageReplyingTo.headerMessageId];
    }
  }
  return {inReplyTo, references}
}

function replaceBodyMessageIds(messageId, originalBody) {
  const env = NylasEnv.config.get('env')
  const serverUrl = {
    local: 'http:\/\/lvh\.me:5100',
    development: 'http:\/\/lvh\.me:5100',
    staging: 'https:\/\/n1-staging\.nylas\.com',
    production: 'https:\/\/n1\.nylas\.com',
  }[env];
  const regex = new RegExp(`(${serverUrl}.+?)MESSAGE_ID`, 'g')
  return originalBody.replace(regex, `$1${messageId}`)
}

async function buildForSend(db, json) {
  const {Thread, Message} = db
  let replyToThread;
  let replyToMessage;

  if (json.thread_id != null) {
    replyToThread = await Thread.find({
      where: {id: json.thread_id},
      include: [{
        model: Message,
        as: 'messages',
        attributes: ['id'],
      }],
    });
  }

  if (json.reply_to_message_id != null) {
    replyToMessage = await Message.findById(json.reply_to_message_id);
  }

  if (replyToThread && replyToMessage) {
    if (!replyToThread.messages.find((msg) => msg.id === replyToMessage.id)) {
      throw new APIError(`Message ${replyToMessage.id} is not in thread ${replyToThread.id}`, 400)
    }
  }

  let thread;
  let replyHeaders = {};
  if (replyToMessage) {
    replyHeaders = getReplyHeaders(replyToMessage);
    thread = await replyToMessage.getThread();
  } else if (replyToThread) {
    thread = replyToThread;
    const previousMessages = thread.messages.filter(msg => !msg.isDraft);
    if (previousMessages.length > 0) {
      const lastMessage = previousMessages[previousMessages.length - 1]
      replyHeaders = getReplyHeaders(lastMessage);
    }
  }

  const {inReplyTo, references} = replyHeaders
  const message = {
    accountId: json.account_id,
    threadId: thread ? thread.id : null,
    headerMessageId: Message.buildHeaderMessageId(json.client_id),
    from: json.from,
    to: json.to,
    cc: json.cc,
    bcc: json.bcc,
    references,
    inReplyTo,
    replyTo: json.reply_to,
    subject: json.subject,
    body: json.body,
    unread: true,
    isDraft: json.draft,
    isSent: false,
    version: 0,
    date: new Date(),
    uploads: json.uploads,
  }
  message.id = Message.hash(message)
  message.body = replaceBodyMessageIds(message.id, message.body)
  return Message.build(message)
}

module.exports = {
  buildForSend,
  parseFromImap,
  extractSnippet,
  replaceBodyMessageIds,
}
<commit_msg>[local-sync] Fix a couple of message parsing bugs<commit_after>/* eslint no-useless-escape: 0 */
const mimelib = require('mimelib');
const encoding = require('encoding');
const he = require('he');
const os = require('os');
const fs = require('fs');
const path = require('path')
const mkdirp = require('mkdirp');
const {Imap, Errors: {APIError}} = require('isomorphic-core');

// aiming for the former in length, but the latter is the hard db cutoff
const SNIPPET_SIZE = 100;
const SNIPPET_MAX_SIZE = 255;

// The input is the value of a to/cc/bcc/from header as parsed by the imap
// library we're using, but it currently parses them in a weird format. If an
// email is sent to a@example.com and b@example.com, the parsed output of the
// 'to' header is ['a@example.com, b@example.com']. (Note both emails are in
// the same string.) When fixed, this function will need to update accordingly.
function extractContacts(input) {
  if (!input || input.length === 0 || !input[0]) {
    return [];
  }
  const s = `["${input[0].replace(/"/g, '\\"').replace(/, /g, '", "')}"]`;
  const values = JSON.parse(s);
  return values.map(v => {
    const parsed = mimelib.parseAddresses(v)
    if (!parsed || parsed.length === 0) {
      return null
    }
    const {name, address: email} = parsed.pop()
    return {name, email}
  })
  .filter(c => c != null)
}

/*
Iteratively walk the DOM of this document's <body>, calling the callback on
each node. Skip any nodes and the skipTags set, including their children.
*/
function _walkBodyDOM(doc, callback, skipTags) {
  let nodes = Array.from(doc.body.childNodes);

  while (nodes.length) {
    const node = nodes.shift();

    callback(node);

    if (!skipTags.has(node.tagName)) {
      if (node.childNodes && node.childNodes.length) {
        nodes = Array.from(node.childNodes).concat(nodes);
      }
    }
  }
}

function extractSnippet(plainBody, htmlBody) {
  let snippetText = plainBody || '';
  if (htmlBody) {
    const doc = new DOMParser().parseFromString(htmlBody, 'text/html')
    const extractedTextElements = [];

    _walkBodyDOM(doc, (node) => {
      if (node.nodeType === Node.TEXT_NODE) {
        const nodeValue = node.nodeValue ? node.nodeValue.trim() : null;
        if (nodeValue) {
          extractedTextElements.push(nodeValue);
        }
      }
    }, new Set(['TITLE', 'SCRIPT', 'STYLE', 'IMG']));

    const extractedText = extractedTextElements.join(' ').trim();
    if (extractedText) {
      snippetText = extractedText;
    }
  }

  // clean up and trim snippet
  let trimmed = snippetText.trim().replace(/[\n\r]/g, ' ').replace(/\s\s+/g, ' ').substr(0, SNIPPET_MAX_SIZE);
  if (trimmed) {
  // TODO: strip quoted text from snippets also
    // trim down to approx. SNIPPET_SIZE w/out cutting off words right in the
    // middle (if possible)
    const wordBreak = trimmed.indexOf(' ', SNIPPET_SIZE);
    if (wordBreak !== -1) {
      trimmed = trimmed.substr(0, wordBreak);
    }
  }
  return trimmed;
}

/*
Preserve whitespacing on plaintext emails -- has the side effect of
monospacing, but that seems OK and perhaps sometimes even desired (for e.g.
ascii art, alignment)
*/
function HTMLifyPlaintext(text) {
  const escapedText = he.escape(text);
  return `<pre class="nylas-plaintext">${escapedText}</pre>`;
}

/*
Since we only fetch the MIME structure and specific desired MIME parts from
IMAP, we unfortunately can't use an existing library like mailparser to parse
the message, and have to do fun stuff like deal with character sets and
content-transfer-encodings ourselves.
*/
async function parseFromImap(imapMessage, desiredParts, {db, accountId, folder}) {
  const {Message, Label} = db
  const {attributes} = imapMessage

  const body = {}
  for (const {id, mimetype, transferEncoding, charset} of desiredParts) {
    // see https://www.w3.org/Protocols/rfc1341/5_Content-Transfer-Encoding.html
    if (!transferEncoding || new Set(['7bit', '8bit', 'binary']).has(transferEncoding.toLowerCase())) {
      // NO transfer encoding has been performed --- how to decode to a string
      // depends ONLY on the charset, which defaults to 'ascii' according to
      // https://tools.ietf.org/html/rfc2045#section-5.2
      const convertedBuffer = encoding.convert(imapMessage.parts[id], 'utf-8', charset || 'ascii')
      body[mimetype] = convertedBuffer.toString('utf-8');
    } else if (transferEncoding.toLowerCase() === 'quoted-printable') {
      body[mimetype] = mimelib.decodeQuotedPrintable(imapMessage.parts[id], charset || 'ascii');
    } else if (transferEncoding.toLowerCase() === 'base64') {
      body[mimetype] = mimelib.decodeBase64(imapMessage.parts[id], charset || 'ascii');
    } else {
      // 'binary' and custom x-token content-transfer-encodings
      return Promise.reject(new Error(`Unsupported Content-Transfer-Encoding ${transferEncoding}, mimetype ${mimetype}`))
    }
  }
  const headers = imapMessage.headers.toString('ascii');
  const parsedHeaders = Imap.parseHeader(headers);
  for (const key of ['x-gm-thrid', 'x-gm-msgid', 'x-gm-labels']) {
    parsedHeaders[key] = attributes[key];
  }

  const parsedMessage = {
    to: extractContacts(parsedHeaders.to),
    cc: extractContacts(parsedHeaders.cc),
    bcc: extractContacts(parsedHeaders.bcc),
    from: extractContacts(parsedHeaders.from),
    replyTo: extractContacts(parsedHeaders['reply-to']),
    accountId: accountId,
    body: body['text/html'] || body['text/plain'] || body['application/pgp-encrypted'] || '',
    snippet: null,
    unread: !attributes.flags.includes('\\Seen'),
    starred: attributes.flags.includes('\\Flagged'),
    date: attributes.date,
    folderImapUID: attributes.uid,
    folderId: folder.id,
    folder: null,
    labels: [],
    headers: parsedHeaders,
    headerMessageId: parsedHeaders['message-id'] ? parsedHeaders['message-id'][0] : '',
    gMsgId: parsedHeaders['x-gm-msgid'],
    subject: parsedHeaders.subject ? parsedHeaders.subject[0] : '(no subject)',
  }
  parsedMessage.id = Message.hash(parsedMessage)

  if (!body['text/html'] && body['text/plain']) {
    parsedMessage.body = HTMLifyPlaintext(body['text/plain']);
  }

  parsedMessage.snippet = extractSnippet(body['text/plain'], body['text/html']);
  parsedMessage.folder = folder

  // TODO: unclear if this is necessary given we already have parsed labels
  const xGmLabels = attributes['x-gm-labels']
  if (xGmLabels) {
    parsedMessage.folderImapXGMLabels = JSON.stringify(xGmLabels)
    parsedMessage.labels = await Label.findXGMLabels(xGmLabels)
  }

  if (process.env.NYLAS_DEBUG) {
    const outJSON = JSON.stringify({imapMessage, desiredParts, result: parsedMessage});
    const outDir = path.join(os.tmpdir(), "k2-parse-output", folder.name)
    const outFile = path.join(outDir, imapMessage.attributes.uid.toString());
    mkdirp.sync(outDir);
    fs.writeFileSync(outFile, outJSON);
  }

  return parsedMessage;
}

function getReplyHeaders(messageReplyingTo) {
  let inReplyTo;
  let references;
  if (messageReplyingTo.headerMessageId) {
    inReplyTo = messageReplyingTo.headerMessageId;
    if (messageReplyingTo.references) {
      references = messageReplyingTo.references.concat(messageReplyingTo.headerMessageId);
    } else {
      references = [messageReplyingTo.headerMessageId];
    }
  }
  return {inReplyTo, references}
}

function replaceBodyMessageIds(messageId, originalBody) {
  const env = NylasEnv.config.get('env')
  const serverUrl = {
    local: 'http:\/\/lvh\.me:5100',
    development: 'http:\/\/lvh\.me:5100',
    staging: 'https:\/\/n1-staging\.nylas\.com',
    production: 'https:\/\/n1\.nylas\.com',
  }[env];
  const regex = new RegExp(`(${serverUrl}.+?)MESSAGE_ID`, 'g')
  return originalBody.replace(regex, `$1${messageId}`)
}

async function buildForSend(db, json) {
  const {Thread, Message} = db
  let replyToThread;
  let replyToMessage;

  if (json.thread_id != null) {
    replyToThread = await Thread.find({
      where: {id: json.thread_id},
      include: [{
        model: Message,
        as: 'messages',
        attributes: ['id'],
      }],
    });
  }

  if (json.reply_to_message_id != null) {
    replyToMessage = await Message.findById(json.reply_to_message_id);
  }

  if (replyToThread && replyToMessage) {
    if (!replyToThread.messages.find((msg) => msg.id === replyToMessage.id)) {
      throw new APIError(`Message ${replyToMessage.id} is not in thread ${replyToThread.id}`, 400)
    }
  }

  let thread;
  let replyHeaders = {};
  if (replyToMessage) {
    replyHeaders = getReplyHeaders(replyToMessage);
    thread = await replyToMessage.getThread();
  } else if (replyToThread) {
    thread = replyToThread;
    const previousMessages = thread.messages.filter(msg => !msg.isDraft);
    if (previousMessages.length > 0) {
      const lastMessage = previousMessages[previousMessages.length - 1]
      replyHeaders = getReplyHeaders(lastMessage);
    }
  }

  const {inReplyTo, references} = replyHeaders
  const message = {
    accountId: json.account_id,
    threadId: thread ? thread.id : null,
    headerMessageId: Message.buildHeaderMessageId(json.client_id),
    from: json.from,
    to: json.to,
    cc: json.cc,
    bcc: json.bcc,
    references,
    inReplyTo,
    replyTo: json.reply_to,
    subject: json.subject,
    body: json.body,
    unread: true,
    isDraft: json.draft,
    isSent: false,
    version: 0,
    date: new Date(),
    uploads: json.uploads,
  }
  message.id = Message.hash(message)
  message.body = replaceBodyMessageIds(message.id, message.body)
  return Message.build(message)
}

module.exports = {
  buildForSend,
  parseFromImap,
  extractSnippet,
  replaceBodyMessageIds,
}
<|endoftext|>


<commit_before>const _ = require('lodash');
const { Report } = require('./models');

/**
 * Gets any useful textual information from a string or undefined if there is
 * none.
 */
function ignoreEmpty(string) {
  if (string === undefined) {
    return undefined;
  }

  if (string.trim().length === 0) {
    // only whitespace, we shouldn't populate the field
    return undefined;
  }

  return string.trim();
}

/**
 * Convert a legacy report to the new report schema.
 */
exports.toNewReport = function toNewReport(legacy) {
  return new Report({
    diff: {
      geometry: legacy.geometry,
      name: ignoreEmpty(legacy.properties.name),
      active: legacy.properties.active,
      access: ignoreEmpty(legacy.properties.access),
      opening: ignoreEmpty(legacy.properties.opening),
      type: ignoreEmpty(legacy.properties.type),
      accessibleType: ignoreEmpty(legacy.properties.accessibleType),
      babyChange: legacy.properties.babyChange,
      radar: legacy.properties.radar,
      attended: legacy.properties.attended,
      automatic: legacy.properties.automatic,
      fee: ignoreEmpty(legacy.properties.fee),
      notes: ignoreEmpty(legacy.properties.notes),
      removal_reason: ignoreEmpty(legacy.properties.removal_reason),
      area: legacy.properties.area.map(area =>
        _.omit(area.toObject(), ['_id'])
      ),
    },
  });
};

/**
 * Convert a list of legacy reports to a list of new reports, chaining them
 * together into a linked list in the process.
 */
exports.toNewReports = function toNewReports(legacies) {
  const newReps = legacies.map(exports.toNewReport);

  const core = {};
  for (let i = 0; i < newReps.length; i++) {
    if (i - 1 >= 0) {
      newReps[i].previous = newReps[i - 1]._id;

      for (const key of Object.keys(Report.schema.tree.diff)) {
        if (_.isEqual(newReps[i].toObject().diff[key], core[key])) {
          newReps[i].diff[key] = undefined;
        }
      }
    }

    for (const key of Object.keys(Report.schema.tree.diff)) {
      if (newReps[i].toObject().diff[key] !== undefined) {
        core[key] = _.clone(newReps[i].toObject().diff[key]);
      }
    }

    if (i + 1 < newReps.length) {
      newReps[i].next = newReps[i + 1]._id;
    }
  }

  return newReps;
};
<commit_msg>fix: only remove filler values when they're truly redundant<commit_after>const _ = require('lodash');
const { Report } = require('./models');

/**
 * Gets any useful textual information from a string or undefined if there is
 * none.
 */
function ignoreEmpty(string) {
  if (string === undefined) {
    return undefined;
  }

  if (string.trim().length === 0) {
    // only whitespace, the field was likely intended to be removed
    return null;
  }

  return string.trim();
}

/**
 * Convert a legacy report to the new report schema.
 */
exports.toNewReport = function toNewReport(legacy) {
  return new Report({
    diff: {
      geometry: legacy.geometry,
      name: ignoreEmpty(legacy.properties.name),
      active: legacy.properties.active,
      access: ignoreEmpty(legacy.properties.access),
      opening: ignoreEmpty(legacy.properties.opening),
      type: ignoreEmpty(legacy.properties.type),
      accessibleType: ignoreEmpty(legacy.properties.accessibleType),
      babyChange: legacy.properties.babyChange,
      radar: legacy.properties.radar,
      attended: legacy.properties.attended,
      automatic: legacy.properties.automatic,
      fee: ignoreEmpty(legacy.properties.fee),
      notes: ignoreEmpty(legacy.properties.notes),
      removal_reason: ignoreEmpty(legacy.properties.removal_reason),
      area: legacy.properties.area.map(area =>
        _.omit(area.toObject(), ['_id'])
      ),
    },
  });
};

/**
 * Convert a list of legacy reports to a list of new reports, chaining them
 * together into a linked list in the process.
 */
exports.toNewReports = function toNewReports(legacies) {
  // create a new report based on a legacy report, with no knowledge of others
  const newReps = legacies.map(exports.toNewReport);

  // iterate through chain of reports
  const core = {};
  for (let i = 0; i < newReps.length; i++) {
    if (i - 1 >= 0) {
      // give reference to previous report
      newReps[i].previous = newReps[i - 1]._id;

      // remove any values given identically in a previous report
      for (const key of Object.keys(Report.schema.tree.diff)) {
        if (_.isEqual(newReps[i].toObject().diff[key], core[key])) {
          newReps[i].diff[key] = undefined;
        }
      }
    }

    for (const key of Object.keys(Report.schema.tree.diff)) {
      // ignore properties not in this report
      if (newReps[i].toObject().diff[key] === undefined) continue;

      // if the property is marked as null, see if it should be
      if (newReps[i].toObject().diff[key] === null) {
        if (core[key] === undefined) {
          // previously the property was undefined
          newReps[i].diff[key] = undefined; // the null is unneeded
        } else {
          // null indicates that this report should remove the property from the loo
          delete core[key]; // remove it from our simulated loo
          // leave the null value as is, it is needed to indicate the property removal
        }
      }

      // keep track of how our loo would be generated on-the-fly
      core[key] = _.clone(newReps[i].toObject().diff[key]);
    }

    // give reference to following report
    if (i + 1 < newReps.length) {
      newReps[i].next = newReps[i + 1]._id;
    }
  }

  return newReps;
};
<|endoftext|>


<commit_before>const config = {
  publish: [
      'github'
  ],
  productName: 'Tidepool Uploader',
  appId: 'org.tidepool.TidepoolUploader',
  directories: {
    buildResources: 'resources',
    output: 'release'
  },
  afterSign: 'scripts/notarize.js',
  dmg: {
    contents: [
      {
        x: 381,
        y: 190,
        type: 'link',
        path: '/Applications'
      },
      {
        x: 159,
        y: 190,
        type: 'file'
      }
    ],
    background: 'resources/background.tiff'
  },
  nsis: {
    oneClick: false,
    perMachine: true,
    allowElevation: true
  },
  files: [
    'dist/',
    'node_modules/',
    'app.html',
    'main.prod.js',
    'main.prod.js.map',
    'package.json'
  ],
  extraResources: [
    {
      from: 'resources/${os}',
      to: 'driver/',
      filter: [
        '**/*',
        '!*.md'
      ]
    },
    'sounds/'
  ],
  win: {
    target: [
      {
        target: 'nsis',
        arch: [
          'ia32',
          'x64'
        ]
      },
      {
        target: 'zip',
        arch: [
          'ia32',
          'x64'
        ]
      }
    ],
    publisherName: [
      'Tidepool Project'
    ],
    rfc3161TimeStampServer: 'http://timestamp.digicert.com'
  },
  mac: {
    category: 'public.app-category.tools',
    entitlements: 'resources/mac/entitlements.mac.plist',
    entitlementsInherit: 'resources/mac/entitlements.mac.plist',
    target: [
      {
        target: 'zip',
        arch: [
          'x64'
        ]
      },
      {
        target: 'dmg',
        arch: [
          'x64'
        ]
      },
      'dir'
    ]
  }
};

console.log('CIRCLE_TAG:', process.env.CIRCLE_TAG);
console.log('APPVEYOR_REPO_TAG:', process.env.APPVEYOR_REPO_TAG);

if ( (process.env.CIRCLE_TAG && process.env.CIRCLE_TAG.length > 0) ||
     (process.env.APPVEYOR_REPO_TAG_NAME && process.env.APPVEYOR_REPO_TAG_NAME.length > 0) ) {
  let releaseType = null;

  if ( (process.env.CIRCLE_TAG && process.env.CIRCLE_TAG.indexOf('-') !== -1) ||
       (process.env.APPVEYOR_REPO_TAG_NAME && process.env.APPVEYOR_REPO_TAG_NAME.indexOf('-') !== -1) ) {
    // non-production releases have hyphens in their tags
    releaseType = 'pre-release';
  } else {
    releaseType = 'release';
  }

  config.publish = [
    {
      provider: 'github',
      releaseType: releaseType,
    },
    {
      provider: 's3',
      bucket: 'downloads.tidepool.org',
    },
  ];
}

module.exports = config;
<commit_msg>add github owner<commit_after>const config = {
  publish: [
      'github'
  ],
  productName: 'Tidepool Uploader',
  appId: 'org.tidepool.TidepoolUploader',
  directories: {
    buildResources: 'resources',
    output: 'release'
  },
  afterSign: 'scripts/notarize.js',
  dmg: {
    contents: [
      {
        x: 381,
        y: 190,
        type: 'link',
        path: '/Applications'
      },
      {
        x: 159,
        y: 190,
        type: 'file'
      }
    ],
    background: 'resources/background.tiff'
  },
  nsis: {
    oneClick: false,
    perMachine: true,
    allowElevation: true
  },
  files: [
    'dist/',
    'node_modules/',
    'app.html',
    'main.prod.js',
    'main.prod.js.map',
    'package.json'
  ],
  extraResources: [
    {
      from: 'resources/${os}',
      to: 'driver/',
      filter: [
        '**/*',
        '!*.md'
      ]
    },
    'sounds/'
  ],
  win: {
    target: [
      {
        target: 'nsis',
        arch: [
          'ia32',
          'x64'
        ]
      },
      {
        target: 'zip',
        arch: [
          'ia32',
          'x64'
        ]
      }
    ],
    publisherName: [
      'Tidepool Project'
    ],
    rfc3161TimeStampServer: 'http://timestamp.digicert.com'
  },
  mac: {
    category: 'public.app-category.tools',
    entitlements: 'resources/mac/entitlements.mac.plist',
    entitlementsInherit: 'resources/mac/entitlements.mac.plist',
    target: [
      {
        target: 'zip',
        arch: [
          'x64'
        ]
      },
      {
        target: 'dmg',
        arch: [
          'x64'
        ]
      },
      'dir'
    ]
  }
};

console.log('CIRCLE_TAG:', process.env.CIRCLE_TAG);
console.log('APPVEYOR_REPO_TAG:', process.env.APPVEYOR_REPO_TAG);

if ( (process.env.CIRCLE_TAG && process.env.CIRCLE_TAG.length > 0) ||
     (process.env.APPVEYOR_REPO_TAG_NAME && process.env.APPVEYOR_REPO_TAG_NAME.length > 0) ) {
  let releaseType = null;

  if ( (process.env.CIRCLE_TAG && process.env.CIRCLE_TAG.indexOf('-') !== -1) ||
       (process.env.APPVEYOR_REPO_TAG_NAME && process.env.APPVEYOR_REPO_TAG_NAME.indexOf('-') !== -1) ) {
    // non-production releases have hyphens in their tags
    releaseType = 'pre-release';
  } else {
    releaseType = 'release';
  }

  config.publish = [
    {
      provider: 'github',
      owner: 'tidepool-org', // required to overwrite existing binaries
      releaseType: releaseType,
    },
    {
      provider: 's3',
      bucket: 'downloads.tidepool.org',
    },
  ];
}

module.exports = config;
<|endoftext|>


<commit_before>import { isFalsy, isObject, isFunction, isArray } from '@dnajs/core/src/lib/typeof.js';
import { registry } from '@dnajs/core/src/lib/registry.js';
import {
    skip,
    text,
    attr,
    elementClose,
    elementOpenStart,
    elementOpenEnd,
    patch as originalPatch,
} from 'incremental-dom/index.js';

function handleChildren(children) {
    children.forEach((child) => {
        if (isFunction(child)) {
            child();
        } else if (isArray(child)) {
            handleChildren(child);
        } else if (child) {
            text(child);
        }
    });
}

function interpolate(template, data) {
    if (isFunction(template)) {
        let res = template.call(this, data);
        interpolate.call(this, res);
    } else if (isArray(template)) {
        template.forEach((chunk) => {
            interpolate.call(this, chunk);
        });
    }
}

export function h(element, props, ...children) {
    return () => {
        if (!isObject(props)) {
            if (props) {
                children.unshift(props);
            }
            props = {};
        }
        let key = props.key;
        delete props.key;

        elementOpenStart(element, key);

        for (let k in props) {
            if (!isFalsy(props[k])) {
                attr(k, props[k]);
            }
        }

        const node = elementOpenEnd(element);
        const isComponent = !!registry.get(
            node.getAttribute('is') || node.tagName
        );

        if (isComponent) {
            skip();
        } else {
            handleChildren(children);
        }
        elementClose(element);
        return node;
    };
}

export function patch(scope, fn, data) {
    return originalPatch(scope, interpolate.bind(this, fn, data));
}

export { text };
<commit_msg>fix: IDOM children handling<commit_after>import { isFalsy, isObject, isFunction, isArray } from '@dnajs/core/src/lib/typeof.js';
import { registry } from '@dnajs/core/src/lib/registry.js';
import {
    skip,
    text,
    attr,
    elementClose,
    elementOpenStart,
    elementOpenEnd,
    patch as originalPatch,
} from 'incremental-dom/index.js';

function handleChildren(children) {
    children.forEach((child) => {
        if (isFunction(child)) {
            child();
        } else if (isArray(child)) {
            handleChildren(child);
        } else if (child) {
            text(child);
        }
    });
}

function interpolate(template, data) {
    if (isFunction(template)) {
        let res = template.call(this, data);
        interpolate.call(this, res);
    } else if (isArray(template)) {
        template.forEach((chunk) => {
            interpolate.call(this, chunk);
        });
    }
}

export function h(element, props, ...children) {
    return () => {
        if (!isObject(props)) {
            if (props) {
                children.unshift(props);
            }
            props = {};
        }
        let key = props.key;
        delete props.key;

        elementOpenStart(element, key);

        for (let k in props) {
            if (!isFalsy(props[k])) {
                attr(k, props[k]);
            }
        }

        const node = elementOpenEnd(element);
        const isComponent = !!registry.get(
            node.getAttribute('is') || node.tagName
        );

        if (isComponent && children.length === 0) {
            skip();
        } else {
            handleChildren(children);
        }
        elementClose(element);
        return node;
    };
}

export function patch(scope, fn, data) {
    return originalPatch(scope, interpolate.bind(this, fn, data));
}

export { text };
<|endoftext|>


<commit_before>
define(function(require) {
    var Crafty = require('crafty');
    var Expires = require('Expires');
    var Vec2d = Crafty.math.Vector2D;

    Crafty.sprite(20, 20, "image/blood.png", {bloodspray:[0, 0]});
    Crafty.sprite(20, 20, "image/slash.png", {slash:[0, 0]});

    var bombmeter_max = 40;
    var special_combo_val = 50;
    var normal_combo_val = 10;

    Crafty.c("EnemyDestroyer", {
        _destroy: false,

        _score: 0,
        _combo: -1,
        _comboval: 0,
        _bombmeter: 0,
        _bombtimer: 0,
        _bombtime: 100,
        _bombing: false,

        _destroyEnemy: function(entityHit) {
            var slashimg = Crafty.e('2D, Canvas, slash, SpriteAnimation, Expires')
                .attr({x: entityHit.x + 10, y: entityHit.y+10, w:20, h:20, z: -999})
                .reel('slash', 50, 0, 0, 5)
                .animate('slash')
                .expires(100)
                .origin(10, 10);
            var bloodsprite = Crafty.e('2D, Canvas, bloodspray, SpriteAnimation')
                .attr({x: entityHit.x + 10, y: entityHit.y + 10, w:20, h:20, z: -1000})
                .reel('spray', 100, 0, 0, 5)
                .animate('spray')
                .origin(10, 10);

            slashimg.rotation = this.rotation + 90;
            bloodsprite.rotation = this.rotation + 90;

            if(this._combo >= 0) {
                if(this._bombing) {
                    this._comboval += 5;
                } else if(entityHit.isSpecialEnemy()) {
                    this._comboval += special_combo_val;
                } else {
                    this._comboval += normal_combo_val;
                }
                this._score += this._comboval;

                var textsize = 8 + this._comboval / 5.0;
                var redness = Math.min(0 + this._comboval, 255);
                var rednessHex = redness.toString(16);
                if(rednessHex.length < 2) {
                    rednessHex = '0' + rednessHex;
                }

                Crafty.e('2D, Canvas, Text, Expires')
                    .attr({x: entityHit.x, y: entityHit.y, w:20, h:20})
                    .textFont({size:textsize + 'px'})
                    .textColor('#' + rednessHex + '0000')
                    .text('+' + this._comboval)
                    .expires(1000 + this._comboval * 5);
                this._combo++;
            }

            entityHit.destroy();
            this.trigger('EnemyKill');
            this.trigger('ScoreChange', this._score);
        },

        _bombframe: function(data) {
            this._bombtimer += data.dt;
            if(this._bombtimer >= this._bombtime) {
                var enemy = Crafty('Enemy').get(0);
                if(enemy === undefined) {
                    this._bombing = false;
                    //Don't count the bomb as a combo
                    this._combo = 0;
                    this.unbind('EnterFrame', this._bombframe);
                    this.trigger('EndBomb');
                } else {
                    this._destroyEnemy(enemy);
                    this.trigger('BombKill');
                }
                this._bombtimer -= this._bombtime;
            }
        },

        _startbomb: function() {
            this._bombmeter = 0;
            this._bombing = true;
            this._combo = 0;
            this.trigger('NewMeter', this._bombmeter / bombmeter_max * 100);
            this.bind('EnterFrame', this._bombframe);
        },

        _canBomb: function() {
            return this._bombmeter >= bombmeter_max;
        },

        _onHit: function(hitData) {
            for(var i = 0; i < hitData.length; i++) {
                var entityHit = hitData[i].obj;
                if(this._destroy) {
                    this._destroyEnemy(entityHit);
                }
            }
        },

        _onMaxMove: function() {
        },

        _onStartDash: function() {
            this._destroy= true;
            this._combo = 0;
        },

        _onEndDash: function() {
            this._bombmeter = Math.min(Math.max(0, this._bombmeter + this._combo - 1), bombmeter_max);
            this.trigger('NewCombo', this._combo);
            this.trigger('NewMeter', this._bombmeter / bombmeter_max * 100);
            this._comboval = 0;
            this._destroy= false;
            this._combo = -1;
        },

        init: function() {
            this.onHit('Enemy', this._onHit);
            //this.bind("HitOn", this._onHit);
            this.bind('HitMaxSpeed', this._onMaxMove);
            this.bind('StartDash', this._onStartDash);
            this.bind('StopDash', this._onEndDash);
            this.bind('StartBomb', this._startbomb);
        },

        enemydestroyer: function() {
            return this;
        },
    });
});
<commit_msg>Make bomb easier<commit_after>
define(function(require) {
    var Crafty = require('crafty');
    var Expires = require('Expires');
    var Vec2d = Crafty.math.Vector2D;

    Crafty.sprite(20, 20, "image/blood.png", {bloodspray:[0, 0]});
    Crafty.sprite(20, 20, "image/slash.png", {slash:[0, 0]});

    var bombmeter_max = 30;
    var special_combo_val = 50;
    var normal_combo_val = 10;

    Crafty.c("EnemyDestroyer", {
        _destroy: false,

        _score: 0,
        _combo: -1,
        _comboval: 0,
        _bombmeter: 0,
        _bombtimer: 0,
        _bombtime: 100,
        _bombing: false,

        _destroyEnemy: function(entityHit) {
            var slashimg = Crafty.e('2D, Canvas, slash, SpriteAnimation, Expires')
                .attr({x: entityHit.x + 10, y: entityHit.y+10, w:20, h:20, z: -999})
                .reel('slash', 50, 0, 0, 5)
                .animate('slash')
                .expires(100)
                .origin(10, 10);
            var bloodsprite = Crafty.e('2D, Canvas, bloodspray, SpriteAnimation')
                .attr({x: entityHit.x + 10, y: entityHit.y + 10, w:20, h:20, z: -1000})
                .reel('spray', 100, 0, 0, 5)
                .animate('spray')
                .origin(10, 10);

            slashimg.rotation = this.rotation + 90;
            bloodsprite.rotation = this.rotation + 90;

            if(this._combo >= 0) {
                if(this._bombing) {
                    this._comboval += 5;
                } else if(entityHit.isSpecialEnemy()) {
                    this._comboval += special_combo_val;
                } else {
                    this._comboval += normal_combo_val;
                }
                this._score += this._comboval;

                var textsize = 8 + this._comboval / 5.0;
                var redness = Math.min(0 + this._comboval, 255);
                var rednessHex = redness.toString(16);
                if(rednessHex.length < 2) {
                    rednessHex = '0' + rednessHex;
                }

                Crafty.e('2D, Canvas, Text, Expires')
                    .attr({x: entityHit.x, y: entityHit.y, w:20, h:20})
                    .textFont({size:textsize + 'px'})
                    .textColor('#' + rednessHex + '0000')
                    .text('+' + this._comboval)
                    .expires(1000 + this._comboval * 5);
                this._combo++;
            }

            entityHit.destroy();
            this.trigger('EnemyKill');
            this.trigger('ScoreChange', this._score);
        },

        _bombframe: function(data) {
            this._bombtimer += data.dt;
            if(this._bombtimer >= this._bombtime) {
                var enemy = Crafty('Enemy').get(0);
                if(enemy === undefined) {
                    this._bombing = false;
                    //Don't count the bomb as a combo
                    this._combo = 0;
                    this.unbind('EnterFrame', this._bombframe);
                    this.trigger('EndBomb');
                } else {
                    this._destroyEnemy(enemy);
                    this.trigger('BombKill');
                }
                this._bombtimer -= this._bombtime;
            }
        },

        _startbomb: function() {
            this._bombmeter = 0;
            this._bombing = true;
            this._combo = 0;
            this.trigger('NewMeter', this._bombmeter / bombmeter_max * 100);
            this.bind('EnterFrame', this._bombframe);
        },

        _canBomb: function() {
            return this._bombmeter >= bombmeter_max;
        },

        _onHit: function(hitData) {
            for(var i = 0; i < hitData.length; i++) {
                var entityHit = hitData[i].obj;
                if(this._destroy) {
                    this._destroyEnemy(entityHit);
                }
            }
        },

        _onMaxMove: function() {
        },

        _onStartDash: function() {
            this._destroy= true;
            this._combo = 0;
        },

        _onEndDash: function() {
            this._bombmeter = Math.min(Math.max(0, this._bombmeter + this._combo - 1), bombmeter_max);
            this.trigger('NewCombo', this._combo);
            this.trigger('NewMeter', this._bombmeter / bombmeter_max * 100);
            this._comboval = 0;
            this._destroy= false;
            this._combo = -1;
        },

        init: function() {
            this.onHit('Enemy', this._onHit);
            //this.bind("HitOn", this._onHit);
            this.bind('HitMaxSpeed', this._onMaxMove);
            this.bind('StartDash', this._onStartDash);
            this.bind('StopDash', this._onEndDash);
            this.bind('StartBomb', this._startbomb);
        },

        enemydestroyer: function() {
            return this;
        },
    });
});
<|endoftext|>


<commit_before>/*click node=view subtree
tree.on("click", function (node)
{
  var root = tree.root();
  //node.sort(function (node, node.parent()) {return 1});
  var tempTree = root.subtree(node.get_all_leaves());
  var nodeParent = node.parent();
  tree.data(tempTree.data());
    tree.update();
});*/

/*click node=tree turns cyan
tree.on("click", function (node)
{
  var root = tree.root();
  //node.sort(function (node, node.parent()) {return 1});
  var tempTree = root.subtree(node.get_all_leaves());
  var nodeParent = node.parent();
  tree.data(tempTree.data());
  tree.node_display(tree.node_display()
        .size(10)
        .fill("cyan"));
    tree.update();
});*/

<commit_msg>another block comment<commit_after>/*click node=view subtree
tree.on("click", function (node)
{
  var root = tree.root();
  //node.sort(function (node, node.parent()) {return 1});
  var tempTree = root.subtree(node.get_all_leaves());
  var nodeParent = node.parent();
  tree.data(tempTree.data());
    tree.update();
});*/

/*click node=tree turns cyan
tree.on("click", function (node)
{
  var root = tree.root();
  //node.sort(function (node, node.parent()) {return 1});
  var tempTree = root.subtree(node.get_all_leaves());
  var nodeParent = node.parent();
  tree.data(tempTree.data());
  tree.node_display(tree.node_display()
        .size(10)
        .fill("cyan"));
    tree.update();
});*/

/*var numOpenPar = 0;
var numClosedPar = 0;
var numCommas = 0;
    numCommas=0;
  for (var x=0; x<newick.length; x++)
    {
      if (newick.charAt(x)==='(')
        {
        numOpenPar++;
        }
      if (newick.charAt(x)===')')
        {
        numClosedPar++;
        }
      if (newick.charAt(x)==',')
        {
          numCommas++;
        }
    }
  if (numOpenPar!==numClosedPar || newick.charAt(0)!=='(' || newick.charAt(newick.length-1)!==';')
    {  document.getElementById("errorspot").style.color="Red"
      document.getElementById("errorspot").innerHTML = "ERROR: INVALID INPUT";
      resetPar();
  }
   else
    {
      resetPar();
      }

*/

/*tooltips
tree.on("click", function (d) {
                tnt.tooltip.table()
                    .width(120)
                    .call(this, {
                        "header" : "Node",
                        "rows" : [
                            {"label": "id", "value": d.id()}
                        ]
                    })
            })
            */<|endoftext|>


<commit_before>import assert from 'assert';

import electron from 'electron';
const { BrowserWindow, ipcMain } = electron;

function jews(url) {
    return new Promise((resolve, reject) => {
        if (jews.cache[url]) return resolve(jews.cache[url]);
        let window = new BrowserWindow({
            show: process.env.JEWS_SHOW_ELECTRON_WINDOW === 'show'
        });
        ipcMain.on('jews-done', (event, result) => {
            window.destroy();
            jews.cache[url] = result;
            resolve(result);
        });
        ipcMain.on('jews-error', (event, err) => {
            window.destroy();
            reject(err);
        });
        window.loadURL(url);
        window.webContents.executeJavaScript(`
            try {
                eval(require('fs').readFileSync('dist/jews.user.js', 'utf-8'));
            } catch (e) {
                require('electron').ipcRenderer.sendSync('jews-error', e);
            }
        `);
    });
}
jews.cache = {};

describe('파싱 중에 에러가 나면 안됨', function () {
    this.timeout(0);
    it('한겨레', async () => {
        await jews('http://www.hani.co.kr/arti/society/schooling/718916.html'); // #211
    });
    it('중앙일보', async () => {
       await jews('http://news.joins.com/article/19174423'); // #217
    });
    it('중앙일보', async () => {
        await jews('http://news.joins.com/article/19455621'); // #217
    });
});

async function testTimestampParsing(url) {
    let jewsResult = await jews(url);
    let t = jewsResult.timestamp;
    if (t) {
        if (t.created === 'Invalid Date') throw new Error('잘못된 작성일');
        if (t.lastModified === 'Invalid Date') throw new Error('잘못된 마지막 수정일');
    }
}

describe('날짜 파싱에 실패하면 안됨', function () {
    this.timeout(0);
    it('마이데일리', async () => {
        await testTimestampParsing('http://www.mydaily.co.kr/new_yk/html/read.php?newsid=201511192101592220&ext=na'); // #208
    });
    it('SBS', async () => {
        await testTimestampParsing('http://news.sbs.co.kr/news/endPage.do?news_id=N1003295957#sns'); // #218
    });
    it('SBS', async () => {
        await testTimestampParsing('http://news.sbs.co.kr/news/endPage.do?news_id=N1003355118'); // #228
    });
    it('머니투데이', async () => {
        await testTimestampParsing('http://news.mt.co.kr/mtview.php?no=2016011710375658661&MT'); // #229
    });
    it('한국경제', async () => {
        await testTimestampParsing('http://www.hankyung.com/news/app/newsview.php?aid=201601189451v'); // #235
    });
});

async function testTitleParsing(url) {
    let jewsResult = await jews(url);
    switch (jewsResult.title) {
    case undefined: throw new Error('제목이 undefined');
    case '': throw new Error('제목이 비어있음');
    }
}

describe('제목이 비어있으면 안됨', function () {
    this.timeout(0);
    it('마이데일리', async () => {
        await testTitleParsing('http://www.mydaily.co.kr/new_yk/html/read.php?newsid=201511192101592220&ext=na'); // #208
    });
    it('한국경제', async () => {
        await testTitleParsing('http://www.hankyung.com/news/app/newsview.php?aid=201601189451v'); // #235
    })
});

async function testContentParsing(url) {
    let jewsResult = await jews(url);
    let content = jewsResult.content.replace(/(<([^>]+)>)/ig,"");
    switch (content) {
    case undefined: throw new Error('내용이 undefined');
    case '': throw new Error('내용이 비어있음');
    }
}

describe('내용이 비어있으면 안됨', function () {
    this.timeout(0);
    it('매일경제', async () => {
        await testContentParsing('http://news.mk.co.kr/newsRead.php?year=2015&no=979621'); // #231
    })
})
<commit_msg>Add a test for parsing (Fix #196)<commit_after>import assert from 'assert';

import electron from 'electron';
const { BrowserWindow, ipcMain } = electron;

function jews(url) {
    return new Promise((resolve, reject) => {
        if (jews.cache[url]) return resolve(jews.cache[url]);
        let window = new BrowserWindow({
            show: process.env.JEWS_SHOW_ELECTRON_WINDOW === 'show'
        });
        ipcMain.on('jews-done', (event, result) => {
            window.destroy();
            jews.cache[url] = result;
            resolve(result);
        });
        ipcMain.on('jews-error', (event, err) => {
            window.destroy();
            reject(err);
        });
        window.loadURL(url);
        window.webContents.executeJavaScript(`
            try {
                eval(require('fs').readFileSync('dist/jews.user.js', 'utf-8'));
            } catch (e) {
                require('electron').ipcRenderer.sendSync('jews-error', e);
            }
        `);
    });
}
jews.cache = {};

describe('파싱 중에 에러가 나면 안됨', function () {
    this.timeout(0);
    it('한겨레', async () => {
        await jews('http://www.hani.co.kr/arti/politics/politics_general/716934.html'); // #196
    });
    it('한겨레', async () => {
        await jews('http://www.hani.co.kr/arti/society/schooling/718916.html'); // #211
    });
    it('중앙일보', async () => {
       await jews('http://news.joins.com/article/19174423'); // #217
    });
    it('중앙일보', async () => {
        await jews('http://news.joins.com/article/19455621'); // #217
    });
});

async function testTimestampParsing(url) {
    let jewsResult = await jews(url);
    let t = jewsResult.timestamp;
    if (t) {
        if (t.created === 'Invalid Date') throw new Error('잘못된 작성일');
        if (t.lastModified === 'Invalid Date') throw new Error('잘못된 마지막 수정일');
    }
}

describe('날짜 파싱에 실패하면 안됨', function () {
    this.timeout(0);
    it('마이데일리', async () => {
        await testTimestampParsing('http://www.mydaily.co.kr/new_yk/html/read.php?newsid=201511192101592220&ext=na'); // #208
    });
    it('SBS', async () => {
        await testTimestampParsing('http://news.sbs.co.kr/news/endPage.do?news_id=N1003295957#sns'); // #218
    });
    it('SBS', async () => {
        await testTimestampParsing('http://news.sbs.co.kr/news/endPage.do?news_id=N1003355118'); // #228
    });
    it('머니투데이', async () => {
        await testTimestampParsing('http://news.mt.co.kr/mtview.php?no=2016011710375658661&MT'); // #229
    });
    it('한국경제', async () => {
        await testTimestampParsing('http://www.hankyung.com/news/app/newsview.php?aid=201601189451v'); // #235
    });
});

async function testTitleParsing(url) {
    let jewsResult = await jews(url);
    switch (jewsResult.title) {
    case undefined: throw new Error('제목이 undefined');
    case '': throw new Error('제목이 비어있음');
    }
}

describe('제목이 비어있으면 안됨', function () {
    this.timeout(0);
    it('마이데일리', async () => {
        await testTitleParsing('http://www.mydaily.co.kr/new_yk/html/read.php?newsid=201511192101592220&ext=na'); // #208
    });
    it('한국경제', async () => {
        await testTitleParsing('http://www.hankyung.com/news/app/newsview.php?aid=201601189451v'); // #235
    })
});

async function testContentParsing(url) {
    let jewsResult = await jews(url);
    let content = jewsResult.content.replace(/(<([^>]+)>)/ig,"");
    switch (content) {
    case undefined: throw new Error('내용이 undefined');
    case '': throw new Error('내용이 비어있음');
    }
}

describe('내용이 비어있으면 안됨', function () {
    this.timeout(0);
    it('매일경제', async () => {
        await testContentParsing('http://news.mk.co.kr/newsRead.php?year=2015&no=979621'); // #231
    })
})
<|endoftext|>


<commit_before>import assert from 'assert';

import { checkUrl } from '../../jews';
import sites from '../../sites';

function site(site) {
    return function (url) {
        assert(checkUrl(sites[site], url));
    }
}

describe('주소 패턴이 제대로 맞아 떨어지나 테스트', () => {
    it('매일경제', () => {
        let test = site('매일경제');
        test('http://nnews.mk.co.kr/newsRead.php?year=2015&no=1117117&sc=30000001'); // #210
    });
});
<commit_msg>Add sites test for 스포츠서울<commit_after>import assert from 'assert';

import { checkUrl } from '../../jews';
import sites from '../../sites';

function site(site) {
    return function (url) {
        assert(checkUrl(sites[site], url));
    }
}

describe('주소 패턴이 제대로 맞아 떨어지나 테스트', () => {
    it('매일경제', () => {
        let test = site('매일경제');
        test('http://nnews.mk.co.kr/newsRead.php?year=2015&no=1117117&sc=30000001'); // #210
    });

    it('스포츠서울', () => {
        let test = site('스포츠서울');
        test('http://www.sportsseoul.com/news/read/326593'); // #216
    });
});
<|endoftext|>


<commit_before>QUnit.test("_pr.AddScript() : Add External JS File", function(assert){
  var done = assert.async();
  new _pr().AddScript("localhost:8081/mocks/loadscript.js");
  setTimeout(function(){
    assert.ok(mock.isExternalScriptLoaded());
      done();
  },500);
  delete mock.isExternalScriptLoaded
});<commit_msg>added new assertions to AddScript Test<commit_after>QUnit.test("_pr.AddScript() : Add External JS File", function(assert){
  var done = assert.async();
  new _pr().AddScript("localhost:8081/mocks/loadscript.js");
  assert.equal(jQuery("script[src='http://localhost:8081/mocks/loadscript.js']").length, 1, "JavaScript Tag is Added to Document");
  assert.ok(jQuery("script[src='http://localhost:8081/mocks/loadscript.js']").parent()[0].isSameNode(jQuery("head")[0]), "JavaScript Tag is Added to Head");
  setTimeout(function(){
    assert.ok(mock.isExternalScriptLoaded(), "JavaScript was successfully loaded and executed");
    done();
  },500);
  delete mock.isExternalScriptLoaded
});<|endoftext|>