<commit_before>
/*
// #############
// CONFIGURATION
// #############
*/

var TUTORIAL_REPOSITORY = 'http://localhost:5820/tutorial';

/*
// ############
//    STEP 1
// ############
*/

// If the element with id 'link1' is clicked
$('#link1').on('click', function(e){
	var bootstrapCSSLink = $('<link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">');
	var bootstrapThemeCSSLink = $('<link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">');
	var bootstrapJavaScriptLink = $('');

	$('body').append(bootstrapCSSLink);
	$('body').append(bootstrapThemeCSSLink);
	$('body').append(bootstrapJavaScriptLink);
});

/*
// ############
//    STEP 2
// ############
*/

// No JavaScript involved!

/*
// ############
//    STEP 3
// ############
*/

// If the element with id 'link3' is clicked
$('#link3').on('click',function(e){
	// Do a GET request on the '/show' URL, with the data payload 'Hello World'.
	$.get('/show',data={'message': 'Hello World'}, function(data){
		// If successful, add the data to the DOM tree under the 'linktarget3' element.
		$('#linktarget3').html(data);
	});
});

/*
// ############
//    STEP 4
// ############
*/

// If the element with id 'link4' is clicked
$('#link4').on('click',function(e){

	var message = $('#messageInput1').val();

	// Do a GET request on the '/show' URL, with the data payload 'Hello World'.
	$.get('/show',data={'message': message}, function(data){
		// If successful, add the data to the DOM tree under the 'linktarget3' element.
		$('#linktarget4').html(data);
	});
});

/*
// ############
//    STEP 5
// ############
*/

// If the element with id 'link5' is clicked
$('#link5').on('click',function(e){

	var message = $('#messageInput2').val();
	$('#linktarget5').html(message);

});

/*
// ############
//    STEP 6
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput3').on('input', function(e){
	var message = $('#messageInput3').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100}

	$.getJSON(lld_autocomplete_url, data=data, function(json){
		var pre = $('

');
		pre.text(JSON.stringify(json));
		$('#linktarget6').html(pre);
	});
});

/*
// ############
//    STEP 7
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput7').on('input', function(e){
	var message = $('#messageInput7').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100}

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);
			a.attr('href',uri);
			li.append(a);
			ul.append(li);
		}

		$('#linktarget7').html(ul);
	});
});

/*
// ############
//    STEP 8
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput8').on('input', function(e){
	var message = $('#messageInput8').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100}

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			a.on('click', {'uri': uri} function(e){
				alert('You clicked '+ e.data.uri);
			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget8').html(ul);
	});
});

/*
// ############
//    STEP 9
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput9').on('input', function(e){
	var message = $('#messageInput9').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100}

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			a.on('click', {'uri': uri}, function(e){
				var clicked_uri = e.data.uri;
				var query = 'DESCRIBE <'+clicked_uri+'>'
				var endpoint = 'http://linkedlifedata.com/sparql.rdf';
				var format = 'RDF';

				$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(json){
					console.log(json);
					var pre = $('

');
					pre.text(JSON.stringify(json));
					$('#linktarget9').html(pre);
				});

			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget9').html(ul);
	});
});

/*
// ############
//    STEP 10
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput10').on('input', function(e){
	var message = $('#messageInput10').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100}

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			console.log(uri);

			a.on('click', {'uri': uri}, function(e){
				var clicked_uri = e.data.uri;
				console.log('Clicked URI: <'+clicked_uri+'>');
				var query = 'DESCRIBE <'+clicked_uri+'>'
				var endpoint = 'http://linkedlifedata.com/sparql.rdf';
				var format = 'RDF';

				$('#linktarget10').html("
Querying endpoint...
");
				$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(data){
					console.log(data);
					var pre = $('

');
					pre.text(data);
					$('#linktarget10').html(pre);

					// New, set an identifier on our
 tag

					pre.attr('id','pre10');

					// New, enable the button, and add a click handler

					$('#link10').toggleClass('disabled');

					$('#link10').on('click',function(e){
						var rdf_data = $('#pre10').text();

						$.post('/store',data={'data': rdf_data}, function(data){
							var pre = $('

');
							pre.text(data);
							$('#linktarget10').html(pre);
							$('#link10').toggleClass('disabled');
						})

					});

				});

			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget10').html(ul);
	});
});

/*
// ############
//    STEP 11
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput11').on('input', function(e){
	var message = $('#messageInput11').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100}

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			a.on('click', {'label': label}, function(e){
				var label = e.data.label;
				var query = 'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n\nCONSTRUCT {	?s rdfs:label "'+label+'" .\n ?s ?p1 ?o1 . ?o1 ?p2 ?o2 . } WHERE { ?s rdfs:label "'+label+'"@en;\n ?p1 ?o1 .\n ?o1 ?p2 ?o2 .}';
				var endpoint = 'http://live.dbpedia.org/sparql';
				var format = 'RDF';

				$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(data){
					var pre = $('

');
					pre.text(data);
					$('#linktarget11').html(pre);

					// New, set an identifier on our
 tag

					pre.attr('id','pre11');

					// New, enable the button, and add a click handler

					$('#link11').toggleClass('disabled');

					$('#link11').on('click',function(e){
						var rdf_data = $('#pre11').text();

						$.post('/store',data={'data': rdf_data}, function(data){
							var pre = $('

');
							pre.text(data);
							$('#linktarget11').html(pre);
							$('#link11').toggleClass('disabled');
						})

					});

				});

			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget11').html(ul);
	});
});

/*
// ############
//    STEP 12
// ############
*/

$('#link12').on('click',function(e){
	var rdf_data = $('#schema').val();

	$.post('/store',data={'data': rdf_data}, function(data){
		var pre = $('

');
		pre.text(data);
		$('#linktarget12').html(pre);
	})

});

/*
// ############
//    STEP 13
// ############
*/

$('#link13').on('click', function(e){

	var query = $('#query13').text();
	var endpoint = TUTORIAL_REPOSITORY + '/query';
	var format = 'JSON';

	$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(json){
		var pre = $('

');
		pre.text(JSON.stringify(json));
		$('#linktarget13').html(pre);
	});

});

/*
// ############
//    STEP 14
// ############
*/

$('#link14').on('click', function(e){

	var query = $('#query14').val();
	var endpoint = TUTORIAL_REPOSITORY + '/query';
	var format = 'JSON';

	$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(json){
		console.log(json);

		try {
			var vars = json.head.vars;

			var ul = $('

');
			ul.addClass('list-group');

			$.each(json.results.bindings, function(index,value){
				var li = $('

');
				li.addClass('list-group-item');

				$.each(vars, function(index, v){
					var v_type = value[v]['type'];
					var v_value = value[v]['value'];

					li.append(''+v+'
');

					// If the value is a URI, create a hyperlink
					if (v_type == 'uri') {
						var a = $('');
						a.attr('href',v_value);
						a.text(v_value);
						li.append(a);
					// Else we're just showing the value.
					} else {
						li.append(v_value);
					}
					li.append('
');

				});
				ul.append(li);

			});

			$('#linktarget14').html(ul);
		} catch(err) {
			$('#linktarget14').html('Something went wrong!');
		}

	});

});
<commit_msg>Fixed missing comma (and semicolons)<commit_after>
/*
// #############
// CONFIGURATION
// #############
*/

var TUTORIAL_REPOSITORY = 'http://localhost:5820/tutorial';

/*
// ############
//    STEP 1
// ############
*/

// If the element with id 'link1' is clicked
$('#link1').on('click', function(e){
	var bootstrapCSSLink = $('<link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">');
	var bootstrapThemeCSSLink = $('<link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">');
	var bootstrapJavaScriptLink = $('');

	$('body').append(bootstrapCSSLink);
	$('body').append(bootstrapThemeCSSLink);
	$('body').append(bootstrapJavaScriptLink);
});

/*
// ############
//    STEP 2
// ############
*/

// No JavaScript involved!

/*
// ############
//    STEP 3
// ############
*/

// If the element with id 'link3' is clicked
$('#link3').on('click',function(e){
	// Do a GET request on the '/show' URL, with the data payload 'Hello World'.
	$.get('/show',data={'message': 'Hello World'}, function(data){
		// If successful, add the data to the DOM tree under the 'linktarget3' element.
		$('#linktarget3').html(data);
	});
});

/*
// ############
//    STEP 4
// ############
*/

// If the element with id 'link4' is clicked
$('#link4').on('click',function(e){

	var message = $('#messageInput1').val();

	// Do a GET request on the '/show' URL, with the data payload 'Hello World'.
	$.get('/show',data={'message': message}, function(data){
		// If successful, add the data to the DOM tree under the 'linktarget3' element.
		$('#linktarget4').html(data);
	});
});

/*
// ############
//    STEP 5
// ############
*/

// If the element with id 'link5' is clicked
$('#link5').on('click',function(e){

	var message = $('#messageInput2').val();
	$('#linktarget5').html(message);

});

/*
// ############
//    STEP 6
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput3').on('input', function(e){
	var message = $('#messageInput3').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100};

	$.getJSON(lld_autocomplete_url, data=data, function(json){
		var pre = $('

');
		pre.text(JSON.stringify(json));
		$('#linktarget6').html(pre);
	});
});

/*
// ############
//    STEP 7
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput7').on('input', function(e){
	var message = $('#messageInput7').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100};

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);
			a.attr('href',uri);
			li.append(a);
			ul.append(li);
		}

		$('#linktarget7').html(ul);
	});
});

/*
// ############
//    STEP 8
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput8').on('input', function(e){
	var message = $('#messageInput8').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100};

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			a.on('click', {'uri': uri}, function(e){
				alert('You clicked '+ e.data.uri);
			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget8').html(ul);
	});
});

/*
// ############
//    STEP 9
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput9').on('input', function(e){
	var message = $('#messageInput9').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100};

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			a.on('click', {'uri': uri}, function(e){
				var clicked_uri = e.data.uri;
				var query = 'DESCRIBE <'+clicked_uri+'>';
				var endpoint = 'http://linkedlifedata.com/sparql.rdf';
				var format = 'RDF';

				$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(json){
					console.log(json);
					var pre = $('

');
					pre.text(JSON.stringify(json));
					$('#linktarget9').html(pre);
				});

			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget9').html(ul);
	});
});

/*
// ############
//    STEP 10
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput10').on('input', function(e){
	var message = $('#messageInput10').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100};

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			console.log(uri);

			a.on('click', {'uri': uri}, function(e){
				var clicked_uri = e.data.uri;
				console.log('Clicked URI: <'+clicked_uri+'>');
				var query = 'DESCRIBE <'+clicked_uri+'>';
				var endpoint = 'http://linkedlifedata.com/sparql.rdf';
				var format = 'RDF';

				$('#linktarget10').html("
Querying endpoint...
");
				$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(data){
					console.log(data);
					var pre = $('

');
					pre.text(data);
					$('#linktarget10').html(pre);

					// New, set an identifier on our
 tag

					pre.attr('id','pre10');

					// New, enable the button, and add a click handler

					$('#link10').toggleClass('disabled');

					$('#link10').on('click',function(e){
						var rdf_data = $('#pre10').text();

						$.post('/store',data={'data': rdf_data}, function(data){
							var pre = $('

');
							pre.text(data);
							$('#linktarget10').html(pre);
							$('#link10').toggleClass('disabled');
						});

					});

				});

			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget10').html(ul);
	});
});

/*
// ############
//    STEP 11
// ############
*/

// Autocompletion from Linked Life Data

// http://linkedlifedata.com/autocomplete.json?
// Parameters are 'q' for query and some limit.
// Note that we have to add a 'callback=?' suffix to the URI, to make sure the call is a JSONP request.
$('#messageInput11').on('input', function(e){
	var message = $('#messageInput11').val();

	var lld_autocomplete_url = 'http://linkedlifedata.com/autocomplete.json?callback=?';

	var data = {'q': message, 'limit': 100};

	$.getJSON(lld_autocomplete_url, data=data, function(json){

		var ul = $('

');
		ul.addClass('list-group');
		for (var i in json.results) {
			var r = json.results[i];

			var uri = r.uri.namespace + r.uri.localName;
			var label = r.label;

			var li = $('

');
			li.addClass('list-group-item');
			var a = $('');
			a.html(label);

			a.on('click', {'label': label}, function(e){
				var label = e.data.label;
				var query = 'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n\nCONSTRUCT {	?s rdfs:label "'+label+'" .\n ?s ?p1 ?o1 . ?o1 ?p2 ?o2 . } WHERE { ?s rdfs:label "'+label+'"@en;\n ?p1 ?o1 .\n ?o1 ?p2 ?o2 .}';
				var endpoint = 'http://live.dbpedia.org/sparql';
				var format = 'RDF';

				$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(data){
					var pre = $('

');
					pre.text(data);
					$('#linktarget11').html(pre);

					// New, set an identifier on our
 tag

					pre.attr('id','pre11');

					// New, enable the button, and add a click handler

					$('#link11').toggleClass('disabled');

					$('#link11').on('click',function(e){
						var rdf_data = $('#pre11').text();

						$.post('/store',data={'data': rdf_data}, function(data){
							var pre = $('

');
							pre.text(data);
							$('#linktarget11').html(pre);
							$('#link11').toggleClass('disabled');
						});

					});

				});

			});

			li.append(a);
			ul.append(li);
		}

		$('#linktarget11').html(ul);
	});
});

/*
// ############
//    STEP 12
// ############
*/

$('#link12').on('click',function(e){
	var rdf_data = $('#schema').val();

	$.post('/store',data={'data': rdf_data}, function(data){
		var pre = $('

');
		pre.text(data);
		$('#linktarget12').html(pre);
	});

});

/*
// ############
//    STEP 13
// ############
*/

$('#link13').on('click', function(e){

	var query = $('#query13').text();
	var endpoint = TUTORIAL_REPOSITORY + '/query';
	var format = 'JSON';

	$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(json){
		var pre = $('

');
		pre.text(JSON.stringify(json));
		$('#linktarget13').html(pre);
	});

});

/*
// ############
//    STEP 14
// ############
*/

$('#link14').on('click', function(e){

	var query = $('#query14').val();
	var endpoint = TUTORIAL_REPOSITORY + '/query';
	var format = 'JSON';

	$.get('/sparql',data={'endpoint': endpoint, 'query': query, 'format': format}, function(json){
		console.log(json);

		try {
			var vars = json.head.vars;

			var ul = $('

');
			ul.addClass('list-group');

			$.each(json.results.bindings, function(index,value){
				var li = $('

');
				li.addClass('list-group-item');

				$.each(vars, function(index, v){
					var v_type = value[v]['type'];
					var v_value = value[v]['value'];

					li.append(''+v+'
');

					// If the value is a URI, create a hyperlink
					if (v_type == 'uri') {
						var a = $('');
						a.attr('href',v_value);
						a.text(v_value);
						li.append(a);
					// Else we're just showing the value.
					} else {
						li.append(v_value);
					}
					li.append('
');

				});
				ul.append(li);

			});

			$('#linktarget14').html(ul);
		} catch(err) {
			$('#linktarget14').html('Something went wrong!');
		}

	});

});
<|endoftext|>


<commit_before>const request = require('request-json');
const client = request.createClient('https://raw.githubusercontent.com');
const boilerplatePackagesPath = "/SC5/sc5-serverless-boilerplate/master/package.json";

const currentPackages = require('../package.json');

client.get(boilerplatePackagesPath, function(err, res, body) {
  const boilerPackages = body;
  console.log('DEVDEPENDENCIES');

  const devPackages = Object.keys(boilerPackages.devDependencies);
  devPackages.forEach((pkg) => {
    if (boilerPackages.devDependencies[pkg] != currentPackages.devDependencies[pkg]) {
      console.log(` ${pkg}: ${boilerPackages.devDependencies[pkg]} <=> ${currentPackages.devDependencies[pkg]}`);
    }
  });

  console.log('DEPENDENCIES');
  const prodPackages = Object.keys(boilerPackages.dependencies);
  prodPackages.forEach((pkg) => {
    if (boilerPackages.dependencies[pkg] != currentPackages.dependencies[pkg]) {
      console.log(` ${pkg}: ${boilerPackages.dependencies[pkg]} <=> ${currentPackages.dependencies[pkg]}`);
    }
  });

  console.log('SCRIPTS');
  const scripts = Object.keys(boilerPackages.scripts);
  scripts.forEach((script) => {
    if (boilerPackages.scripts[script] != currentPackages.scripts[script]) {
      console.log(` ${script}: ${boilerPackages.scripts[script]} <=> ${currentPackages.scripts[script]}`);
    }
  });
});<commit_msg>Fix check-boilerplatate-package<commit_after>const request = require('request-json');
const client = request.createClient('https://raw.githubusercontent.com');
const boilerplatePackagesPath = "/nordcloud/serverless-boilerplate/master/package.json";

const currentPackages = require('../package.json');

client.get(boilerplatePackagesPath, function(err, res, body) {
  const boilerPackages = body;
  console.log('DEVDEPENDENCIES');

  const devPackages = Object.keys(boilerPackages.devDependencies);
  devPackages.forEach((pkg) => {
    if (boilerPackages.devDependencies[pkg] != currentPackages.devDependencies[pkg]) {
      console.log(` ${pkg}: ${boilerPackages.devDependencies[pkg]} <=> ${currentPackages.devDependencies[pkg]}`);
    }
  });

  console.log('DEPENDENCIES');
  const prodPackages = Object.keys(boilerPackages.dependencies);
  prodPackages.forEach((pkg) => {
    if (boilerPackages.dependencies[pkg] != currentPackages.dependencies[pkg]) {
      console.log(` ${pkg}: ${boilerPackages.dependencies[pkg]} <=> ${currentPackages.dependencies[pkg]}`);
    }
  });

  console.log('SCRIPTS');
  const scripts = Object.keys(boilerPackages.scripts);
  scripts.forEach((script) => {
    if (boilerPackages.scripts[script] != currentPackages.scripts[script]) {
      console.log(` ${script}: ${boilerPackages.scripts[script]} <=> ${currentPackages.scripts[script]}`);
    }
  });
});<|endoftext|>


<commit_before>Template.bottle.helpers({
    historicalURLs: function() {
        return [
        	{
        		year: 1,
        		url: 'https://mail.google.com/mail/u/1/#search/after%3A2013%2F8%2F17+before%3A2013%2F8%2F18'
        	},
        	{
        		year: 2,
        		url: 'https://mail.google.com/mail/u/1/#search/after%3A2012%2F8%2F17+before%3A2012%2F8%2F18'
        	},
        	{
        		year: 3,
        		url: 'https://mail.google.com/mail/u/1/#search/after%3A2011%2F8%2F17+before%3A2011%2F8%2F18'
        	},
        ];
    }
});
<commit_msg>added functions for generating URLs<commit_after>// after:2014/5/3 before:2014/5/4
function historicalURL(date, yearsInThePast) {
	// creating a copy because idk how to javascript tbh
	var dateCopy = new Date(date);
	var year = dateCopy.getFullYear() - yearsInThePast;

	var afterComponent = 'after:' + historicalDate(dateCopy, yearsInThePast);

	dateCopy.setDate(dateCopy.getDate() + 1);
	var beforeComponent = 'before:' + historicalDate(dateCopy, yearsInThePast);

	var baseURL = 'https://mail.google.com/mail/u/1/#search/';
	var combinedURL = baseURL + encodeURIComponent(afterComponent) + '+' + encodeURIComponent(beforeComponent);

	return combinedURL;
}

function historicalDate(date, yearsInThePast) {
	var year = date.getFullYear() - yearsInThePast;
	var month = date.getMonth() + 1;
	var day = date.getDate();
	return year + '/' + month + '/' + day;
}

Template.bottle.helpers({
    historicalURLs: function() {
        return [
        	{
        		year: 1,
        		url: 'https://mail.google.com/mail/u/1/#search/after%3A2013%2F8%2F17+before%3A2013%2F8%2F18'
        	},
        	{
        		year: 2,
        		url: 'https://mail.google.com/mail/u/1/#search/after%3A2012%2F8%2F17+before%3A2012%2F8%2F18'
        	},
        	{
        		year: 3,
        		url: 'https://mail.google.com/mail/u/1/#search/after%3A2011%2F8%2F17+before%3A2011%2F8%2F18'
        	},
        ];
    }
});

var d = new Date();
console.log(historicalURL(d, 1));
<|endoftext|>


<commit_before>import Browser from 'mobiledoc-kit/utils/browser';
import {
    CURSOR_AFTER,
    CURSOR_BEFORE,
    SPECIAL_MARKUPS
} from '../components/koenig-editor';

// Key commands will run any time a particular key or key combination is pressed
// https://github.com/bustlelabs/mobiledoc-kit#configuring-hot-keys

export const DEFAULT_KEY_COMMANDS = [{
    str: 'ENTER',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;

        // if cursor is at beginning of a heading, insert a blank paragraph above
        if (isCollapsed && offset === 0 && section.tagName && section.tagName.match(/^h\d$/)) {
            editor.run((postEditor) => {
                let newPara = postEditor.builder.createMarkupSection('p');
                let collection = section.parent.sections;
                postEditor.insertSectionBefore(collection, newPara, section);
            });
            koenig._scrollCursorIntoView();
            return;
        }

        return false;
    }
}, {
    // CMD+ENTER is our keyboard shortcut for putting a selected card into edit mode
    str: 'META+ENTER',
    run(editor, koenig) {
        if (koenig.selectedCard) {
            koenig.editCard(koenig.selectedCard);
            return;
        }

        return false;
    }
}, {
    // CTRL+ENTER is our keyboard shortcut for putting a selected card into edit mode
    str: 'CTRL+ENTER',
    run(editor, koenig) {
        if (Browser.isWin() && koenig.selectedCard) {
            koenig.editCard(koenig.selectedCard);
            return;
        }

        return false;
    }
}, {
    str: 'SHIFT+ENTER',
    run(editor) {
        if (!editor.range.headSection.isMarkerable) {
            return;
        }

        editor.run((postEditor) => {
            let softReturn = postEditor.builder.createAtom('soft-return');
            postEditor.insertMarkers(editor.range.head, [softReturn]);
        });
    }
}, {
    str: 'BACKSPACE',
    run(editor, koenig) {
        let {head, isCollapsed, head: {marker, offset, section}} = editor.range;
        let {next, prev} = section;

        // if a card is selected we should delete the card then place the cursor
        // at the end of the previous section
        if (koenig.selectedCard) {
            let cursorPosition = section.prev ? CURSOR_BEFORE : CURSOR_AFTER;
            koenig.deleteCard(koenig.selectedCard, cursorPosition);
            return;
        }

        // if the cursor is at the beginning of the doc and on a blank paragraph,
        // then delete or re-create the paragraph to remove formatting
        let sections = section.isListItem ? section.parent.parent.sections : section.parent.sections;
        let isFirstSection = section === sections.head;
        if (isFirstSection && isCollapsed && offset === 0 && (section.isBlank || section.text === '')) {
            editor.run((postEditor) => {
                // remove the current section
                postEditor.removeSection(section);

                // select the next section
                if (next) {
                    return;
                }

                // we don't have another section so create a blank paragraph
                // which will have the effect of removing any formatting
                let {builder} = postEditor;
                let p = builder.createMarkupSection('p');
                postEditor.insertSectionAtEnd(p);
                postEditor.setRange(p.tailPosition());
            });

            // allow default behaviour which will trigger `cursorDidChange` and
            // fire our `cursorDidExitAtTop` action
            return;
        }

        // if the section about to be deleted by a backspace is a card then
        // actually delete the card rather than selecting it.
        // However, if the current paragraph is blank then delete the paragraph
        // instead - allows blank paragraphs between cards to be deleted and
        // feels more natural
        if (isCollapsed && offset === 0 && prev && prev.type === 'card-section' && !section.isBlank) {
            let card = koenig.getCardFromSection(prev);
            koenig.deleteCard(card);
            return;
        }

        // if cursor is at the beginning of a heading and previous section is a
        // blank paragraph, delete the blank paragraph
        if (isCollapsed && offset === 0 && section.tagName.match(/^h\d$/) && prev && prev.tagName === 'p' && prev.isBlank) {
            editor.run((postEditor) => {
                postEditor.removeSection(prev);
            });
            return;
        }

        // if the markup about to be deleted is a special format (code, strike)
        // then undo the text expansion to allow it to be extended
        if (isCollapsed && marker) {
            let specialMarkupTagNames = Object.keys(SPECIAL_MARKUPS);
            let hasReversed = false;
            specialMarkupTagNames.forEach((tagName) => {
                // only continue if we're about to delete a special markup
                let markup = marker.markups.find(markup => markup.tagName.toUpperCase() === tagName);
                if (markup) {
                    let nextMarker = head.markerIn(1);
                    // ensure we're at the end of the markup not inside it
                    if (!nextMarker || !nextMarker.hasMarkup(tagName)) {
                        // wrap with the text expansion, remove formatting, then delete the last char
                        editor.run((postEditor) => {
                            let markdown = SPECIAL_MARKUPS[tagName];
                            let range = editor.range.expandByMarker(marker => !!marker.markups.includes(markup));
                            postEditor.insertText(range.head, markdown);
                            range = range.extend(markdown.length);
                            let endPos = postEditor.insertText(range.tail, markdown);
                            range = range.extend(markdown.length);
                            postEditor.toggleMarkup(tagName, range);
                            endPos = postEditor.deleteAtPosition(endPos, -1);
                            postEditor.setRange(endPos);
                        });
                        hasReversed = true;
                    }
                }
            });
            if (hasReversed) {
                return;
            }
        }

        return false;
    }
}, {
    str: 'DEL',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;

        // if a card is selected we should delete the card then place the cursor
        // at the beginning of the next section or select the following card
        if (koenig.selectedCard) {
            let selectNextCard = section.next.type === 'card-section';
            let nextCard = koenig.getCardFromSection(section.next);

            koenig.deleteCard(koenig.selectedCard);

            if (selectNextCard) {
                koenig.selectCard(nextCard);
            }
            return;
        }

        // if the section about to be deleted by a DEL is a card then actually
        // delete the card rather than selecting it
        // However, if the current paragraph is blank then delete the paragraph
        // instead - allows blank paragraphs between cards to be deleted and
        // feels more natural
        if (isCollapsed && offset === section.length && section.next && section.next.type === 'card-section' && !section.isBlank) {
            let card = koenig.getCardFromSection(section.next);
            koenig.deleteCard(card, CURSOR_BEFORE);
            return;
        }

        return false;
    }
}, {
    // trigger a closure action to indicate that the caret "left" the top of
    // the editor canvas when pressing UP with the caret at the beginning of
    // the doc
    str: 'UP',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;
        let prevSection = section.isListItem && !section.prev ? section.parent.prev : section.prev;

        if (isCollapsed && (offset === 0 || section.isCardSection) && !prevSection) {
            koenig.send('exitCursorAtTop');
        }

        return false;
    }
}, {
    str: 'LEFT',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;

        // trigger a closure action to indicate that the caret "left" the top of
        // the editor canvas if the caret is at the very beginning of the doc
        let prevSection = section.isListItem && !section.prev ? section.parent.prev : section.prev;
        if (isCollapsed && (offset === 0 || section.isCardSection) && !prevSection) {
            koenig.send('exitCursorAtTop');
            return;
        }

        // if we have a selected card move the caret to end of the previous
        // section because the cursor will likely be at the end of the card
        // section meaning the default behaviour would move the cursor to the
        // beginning and require two key presses instead of one
        if (koenig.selectedCard && koenig.selectedCard.postModel === section) {
            koenig.moveCaretToTailOfSection(section.prev, false);
            return;
        }

        return false;
    }
}, {
    str: 'CTRL+K',
    run(editor, koenig) {
        if (Browser.isWin()) {
            return koenig.send('editLink', editor.range);
        }

        // default behaviour for Mac is delete to end of section
        return false;
    }
}, {
    str: 'META+K',
    run(editor, koenig) {
        return koenig.send('editLink', editor.range);
    }
}];

export default function registerKeyCommands(editor, koenig) {
    DEFAULT_KEY_COMMANDS.forEach((keyCommand) => {
        editor.registerKeyCommand({
            str: keyCommand.str,
            run() {
                return keyCommand.run(editor, koenig);
            }
        });
    });
}
<commit_msg>🎨 Koenig - Split list when pressing Enter on a blank list item<commit_after>import Browser from 'mobiledoc-kit/utils/browser';
import {
    CURSOR_AFTER,
    CURSOR_BEFORE,
    SPECIAL_MARKUPS
} from '../components/koenig-editor';

// Key commands will run any time a particular key or key combination is pressed
// https://github.com/bustlelabs/mobiledoc-kit#configuring-hot-keys

export const DEFAULT_KEY_COMMANDS = [{
    str: 'ENTER',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;

        // if cursor is at beginning of a heading, insert a blank paragraph above
        if (isCollapsed && offset === 0 && section.tagName && section.tagName.match(/^h\d$/)) {
            editor.run((postEditor) => {
                let newPara = postEditor.builder.createMarkupSection('p');
                let collection = section.parent.sections;
                postEditor.insertSectionBefore(collection, newPara, section);
            });
            koenig._scrollCursorIntoView();
            return;
        }

        // if cursor is on a blank list item mid-list then split the list
        if (isCollapsed && section.isBlank && section.isListItem && section.next && section.next.isListItem) {
            editor.run((postEditor) => {
                postEditor.toggleSection('p', section.headPosition());
            });
            koenig._scrollCursorIntoView();
            return;
        }

        return false;
    }
}, {
    // CMD+ENTER is our keyboard shortcut for putting a selected card into edit mode
    str: 'META+ENTER',
    run(editor, koenig) {
        if (koenig.selectedCard) {
            koenig.editCard(koenig.selectedCard);
            return;
        }

        return false;
    }
}, {
    // CTRL+ENTER is our keyboard shortcut for putting a selected card into edit mode
    str: 'CTRL+ENTER',
    run(editor, koenig) {
        if (Browser.isWin() && koenig.selectedCard) {
            koenig.editCard(koenig.selectedCard);
            return;
        }

        return false;
    }
}, {
    str: 'SHIFT+ENTER',
    run(editor) {
        if (!editor.range.headSection.isMarkerable) {
            return;
        }

        editor.run((postEditor) => {
            let softReturn = postEditor.builder.createAtom('soft-return');
            postEditor.insertMarkers(editor.range.head, [softReturn]);
        });
    }
}, {
    str: 'BACKSPACE',
    run(editor, koenig) {
        let {head, isCollapsed, head: {marker, offset, section}} = editor.range;
        let {next, prev} = section;

        // if a card is selected we should delete the card then place the cursor
        // at the end of the previous section
        if (koenig.selectedCard) {
            let cursorPosition = section.prev ? CURSOR_BEFORE : CURSOR_AFTER;
            koenig.deleteCard(koenig.selectedCard, cursorPosition);
            return;
        }

        // if the cursor is at the beginning of the doc and on a blank paragraph,
        // then delete or re-create the paragraph to remove formatting
        let sections = section.isListItem ? section.parent.parent.sections : section.parent.sections;
        let isFirstSection = section === sections.head;
        if (isFirstSection && isCollapsed && offset === 0 && (section.isBlank || section.text === '')) {
            editor.run((postEditor) => {
                // remove the current section
                postEditor.removeSection(section);

                // select the next section
                if (next) {
                    return;
                }

                // we don't have another section so create a blank paragraph
                // which will have the effect of removing any formatting
                let {builder} = postEditor;
                let p = builder.createMarkupSection('p');
                postEditor.insertSectionAtEnd(p);
                postEditor.setRange(p.tailPosition());
            });

            // allow default behaviour which will trigger `cursorDidChange` and
            // fire our `cursorDidExitAtTop` action
            return;
        }

        // if the section about to be deleted by a backspace is a card then
        // actually delete the card rather than selecting it.
        // However, if the current paragraph is blank then delete the paragraph
        // instead - allows blank paragraphs between cards to be deleted and
        // feels more natural
        if (isCollapsed && offset === 0 && prev && prev.type === 'card-section' && !section.isBlank) {
            let card = koenig.getCardFromSection(prev);
            koenig.deleteCard(card);
            return;
        }

        // if cursor is at the beginning of a heading and previous section is a
        // blank paragraph, delete the blank paragraph
        if (isCollapsed && offset === 0 && section.tagName.match(/^h\d$/) && prev && prev.tagName === 'p' && prev.isBlank) {
            editor.run((postEditor) => {
                postEditor.removeSection(prev);
            });
            return;
        }

        // if the markup about to be deleted is a special format (code, strike)
        // then undo the text expansion to allow it to be extended
        if (isCollapsed && marker) {
            let specialMarkupTagNames = Object.keys(SPECIAL_MARKUPS);
            let hasReversed = false;
            specialMarkupTagNames.forEach((tagName) => {
                // only continue if we're about to delete a special markup
                let markup = marker.markups.find(markup => markup.tagName.toUpperCase() === tagName);
                if (markup) {
                    let nextMarker = head.markerIn(1);
                    // ensure we're at the end of the markup not inside it
                    if (!nextMarker || !nextMarker.hasMarkup(tagName)) {
                        // wrap with the text expansion, remove formatting, then delete the last char
                        editor.run((postEditor) => {
                            let markdown = SPECIAL_MARKUPS[tagName];
                            let range = editor.range.expandByMarker(marker => !!marker.markups.includes(markup));
                            postEditor.insertText(range.head, markdown);
                            range = range.extend(markdown.length);
                            let endPos = postEditor.insertText(range.tail, markdown);
                            range = range.extend(markdown.length);
                            postEditor.toggleMarkup(tagName, range);
                            endPos = postEditor.deleteAtPosition(endPos, -1);
                            postEditor.setRange(endPos);
                        });
                        hasReversed = true;
                    }
                }
            });
            if (hasReversed) {
                return;
            }
        }

        return false;
    }
}, {
    str: 'DEL',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;

        // if a card is selected we should delete the card then place the cursor
        // at the beginning of the next section or select the following card
        if (koenig.selectedCard) {
            let selectNextCard = section.next.type === 'card-section';
            let nextCard = koenig.getCardFromSection(section.next);

            koenig.deleteCard(koenig.selectedCard);

            if (selectNextCard) {
                koenig.selectCard(nextCard);
            }
            return;
        }

        // if the section about to be deleted by a DEL is a card then actually
        // delete the card rather than selecting it
        // However, if the current paragraph is blank then delete the paragraph
        // instead - allows blank paragraphs between cards to be deleted and
        // feels more natural
        if (isCollapsed && offset === section.length && section.next && section.next.type === 'card-section' && !section.isBlank) {
            let card = koenig.getCardFromSection(section.next);
            koenig.deleteCard(card, CURSOR_BEFORE);
            return;
        }

        return false;
    }
}, {
    // trigger a closure action to indicate that the caret "left" the top of
    // the editor canvas when pressing UP with the caret at the beginning of
    // the doc
    str: 'UP',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;
        let prevSection = section.isListItem && !section.prev ? section.parent.prev : section.prev;

        if (isCollapsed && (offset === 0 || section.isCardSection) && !prevSection) {
            koenig.send('exitCursorAtTop');
        }

        return false;
    }
}, {
    str: 'LEFT',
    run(editor, koenig) {
        let {isCollapsed, head: {offset, section}} = editor.range;

        // trigger a closure action to indicate that the caret "left" the top of
        // the editor canvas if the caret is at the very beginning of the doc
        let prevSection = section.isListItem && !section.prev ? section.parent.prev : section.prev;
        if (isCollapsed && (offset === 0 || section.isCardSection) && !prevSection) {
            koenig.send('exitCursorAtTop');
            return;
        }

        // if we have a selected card move the caret to end of the previous
        // section because the cursor will likely be at the end of the card
        // section meaning the default behaviour would move the cursor to the
        // beginning and require two key presses instead of one
        if (koenig.selectedCard && koenig.selectedCard.postModel === section) {
            koenig.moveCaretToTailOfSection(section.prev, false);
            return;
        }

        return false;
    }
}, {
    str: 'CTRL+K',
    run(editor, koenig) {
        if (Browser.isWin()) {
            return koenig.send('editLink', editor.range);
        }

        // default behaviour for Mac is delete to end of section
        return false;
    }
}, {
    str: 'META+K',
    run(editor, koenig) {
        return koenig.send('editLink', editor.range);
    }
}];

export default function registerKeyCommands(editor, koenig) {
    DEFAULT_KEY_COMMANDS.forEach((keyCommand) => {
        editor.registerKeyCommand({
            str: keyCommand.str,
            run() {
                return keyCommand.run(editor, koenig);
            }
        });
    });
}
<|endoftext|>


<commit_before>/**
 * Gets a radius from bounds in meters.
 *
 * @private
 * @param {L.LatLngBounds} bounds
 * @return {number}
 */
function getRadiusFromBounds( bounds ) {
	// This is currently drawing a circle around the whole box so that some
	// results might be outside of the visible area.
	return Math.floor( bounds.getCenter().distanceTo( bounds.getSouthWest() ) );
}

/**
 * Building the search query. Includes calculations to debounce the input
 * from the bounding box.
 *
 * @private
 * @param {L.LatLngBounds} bounds
 * @return {string}
 */
function getSearchQuery( bounds ) {
	// TODO: Precision could be influenced by zoom factor
	var lat = bounds.getCenter().lat.toFixed( 4 ), // cut to a precision of ~11m
		lng = bounds.getCenter().lng.toFixed( 4 ), // cut to a precision of ~11m
		radius = Math.floor( getRadiusFromBounds( bounds ) / 100 ) * 100; // cut to a precision of 100m steps

	return 'nearcoord:' + radius + 'm,' + lat + ',' + lng;
}

module.exports = {
	/**
	 * @param {L.LatLngBounds} bounds
	 * @return {jQuery.Promise}
	 */
	fetch: function ( bounds ) {
		// TODO: Cache results if bounds remains unchanged
		return ( new mw.Api( {
			/* ajax: {
				// TODO: Temporary override for local testing
				url: 'https://en.wikipedia.org/w/api.php',
				headers: {
					'User-Agent': 'Kartographer - the WMF Content Transform Team (https://www.mediawiki.org/wiki/Content_Transform_Team)'
				}
			} */
		} ) ).get( {
			action: 'query',
			format: 'json',
			formatversion: '2',
			prop: 'coordinates|pageprops|pageimages|description',
			colimit: 'max',
			generator: 'search',
			gsrsearch: getSearchQuery( bounds ),
			gsrnamespace: '0',
			// Set to the max thumbnail limit
			gsrlimit: '50',
			ppprop: 'displaytitle',
			piprop: 'thumbnail',
			pithumbsize: '300',
			// The thumbnail limit is currently 50
			pilimit: '50'
		} );
	},

	/**
	 * @param {Object} response Raw data returned by the geosearch API.
	 * @return {Object[]} A list of GeoJSON features, one for each page.
	 */
	convertGeosearchToGeojson: function ( response ) {
		return response.query.pages.map( function ( page ) {
			var thumbnail = page.thumbnail;
			return {
				type: 'Feature',
				geometry: {
					type: 'Point',
					coordinates: [
						page.coordinates[ 0 ].lon,
						page.coordinates[ 0 ].lat
					]
				},
				properties: {
					title: page.title,
					description: page.description,
					imageUrl: thumbnail ? thumbnail.source : undefined
				}
			};
		} );
	},

	/**
	 * @param {string} title
	 * @param {string|undefined} description
	 * @param {string|undefined} imageUrl
	 * @return {string}
	 */
	createPopupHtml: function ( title, description, imageUrl ) {
		title = mw.Title.newFromText( title );

		var linkHtml = mw.html.element( 'a', {
				href: title.getUrl(),
				target: '_blank'
			}, title.getPrefixedText() ),
			titleHtml = mw.html.element( 'div', {
				class: 'marker-title'
			}, new mw.html.Raw( linkHtml ) ),
			contentHtml = '';

		if ( description ) {
			contentHtml += mw.html.element( 'span', {}, description );
		}

		if ( imageUrl ) {
			contentHtml += mw.html.element( 'img', { src: imageUrl } );
		}

		if ( contentHtml ) {
			return titleHtml + mw.html.element( 'div', {
				class: 'marker-description'
			}, new mw.html.Raw( contentHtml ) );
		}
		return titleHtml;
	}
};
<commit_msg>Fix nearby search failing when nothing can be found<commit_after>/**
 * Gets a radius from bounds in meters.
 *
 * @private
 * @param {L.LatLngBounds} bounds
 * @return {number}
 */
function getRadiusFromBounds( bounds ) {
	// This is currently drawing a circle around the whole box so that some
	// results might be outside of the visible area.
	return Math.floor( bounds.getCenter().distanceTo( bounds.getSouthWest() ) );
}

/**
 * Building the search query. Includes calculations to debounce the input
 * from the bounding box.
 *
 * @private
 * @param {L.LatLngBounds} bounds
 * @return {string}
 */
function getSearchQuery( bounds ) {
	// TODO: Precision could be influenced by zoom factor
	// Absolute limitation of the center point's precision to ~11m
	var lat = bounds.getCenter().lat.toFixed( 4 ),
		lng = bounds.getCenter().lng.toFixed( 4 );
	// Absolute limitation of the radius' precision to 100m
	var radius = ( Math.floor( getRadiusFromBounds( bounds ) / 100 ) || 1 ) * 100;

	return 'nearcoord:' + radius + 'm,' + lat + ',' + lng;
}

module.exports = {
	/**
	 * @param {L.LatLngBounds} bounds
	 * @return {jQuery.Promise}
	 */
	fetch: function ( bounds ) {
		// The maximum thumbnail limit is currently 50
		var limit = 50;
		// TODO: Cache results if bounds remains unchanged
		return ( new mw.Api( {
			/* ajax: {
				// TODO: Temporary override for local testing
				url: 'https://en.wikipedia.org/w/api.php',
				headers: {
					'User-Agent': 'Kartographer - the WMF Content Transform Team (https://www.mediawiki.org/wiki/Content_Transform_Team)'
				}
			} */
		} ) ).get( {
			action: 'query',
			format: 'json',
			formatversion: 2,
			prop: 'coordinates|pageprops|pageimages|description',
			// co… arguments belong to prop=coordinates
			colimit: 'max',
			generator: 'search',
			// gsr… arguments belong to generator=search
			gsrsearch: getSearchQuery( bounds ),
			gsrnamespace: 0,
			gsrlimit: limit,
			// pp… arguments belong to prop=pageprops
			ppprop: 'displaytitle',
			// pi… arguments belong to prop=pageimages
			piprop: 'thumbnail',
			pithumbsize: 300,
			pilimit: limit
		} );
	},

	/**
	 * @param {Object} response Raw data returned by the geosearch API.
	 * @return {Object[]} A list of GeoJSON features, one for each page.
	 */
	convertGeosearchToGeojson: function ( response ) {
		var pages = response.query && response.query.pages || [];
		return pages.map( function ( page ) {
			var thumbnail = page.thumbnail,
				coordinates = page.coordinates[ 0 ];
			return {
				type: 'Feature',
				geometry: { type: 'Point', coordinates: [ coordinates.lon, coordinates.lat ] },
				properties: {
					title: page.title,
					description: page.description,
					imageUrl: thumbnail ? thumbnail.source : undefined
				}
			};
		} );
	},

	/**
	 * @param {string} title
	 * @param {string|undefined} description
	 * @param {string|undefined} imageUrl
	 * @return {string}
	 */
	createPopupHtml: function ( title, description, imageUrl ) {
		title = mw.Title.newFromText( title );

		var linkHtml = mw.html.element( 'a', {
				href: title.getUrl(),
				target: '_blank'
			}, title.getPrefixedText() ),
			titleHtml = mw.html.element( 'div', {
				class: 'marker-title'
			}, new mw.html.Raw( linkHtml ) ),
			contentHtml = '';

		if ( description ) {
			contentHtml += mw.html.element( 'span', {}, description );
		}

		if ( imageUrl ) {
			contentHtml += mw.html.element( 'img', { src: imageUrl } );
		}

		if ( contentHtml ) {
			return titleHtml + mw.html.element( 'div', {
				class: 'marker-description'
			}, new mw.html.Raw( contentHtml ) );
		}
		return titleHtml;
	}
};
<|endoftext|>


<commit_before>/**
 * Returns a historical Gmail URL
 * based on the date passed in
 * yearsInThePast years back from the current date
 *
 * @param <Date> date
 * @param <Integer> yearsInThePast
 * @return <String> combinedURL
 */
function historicalSearchURL(date, yearsInThePast, accountIndex) {
	// creating a copy because idk how to javascript tbh
	var dateCopy = new Date(date);
	var year = dateCopy.getFullYear() - yearsInThePast;

	var afterComponent = 'after:' + historicalDate(dateCopy, yearsInThePast);

	dateCopy.setDate(dateCopy.getDate() + 1);
	var beforeComponent = 'before:' + historicalDate(dateCopy, yearsInThePast);

	var url = baseURL(accountIndex) + encodeURIComponent(afterComponent) + '+' + encodeURIComponent(beforeComponent);

	return url;
}

/**
 * Creates a formatted date string
 * YYYY/MM/DD
 * given a Date object and years in the past
 *
 * @param <Date> date
 * @param <Integer> yearsInThePast
 * @return <String>
 */
function historicalDate(date, yearsInThePast) {
	var year = date.getFullYear() - yearsInThePast;
	var month = date.getMonth() + 1;
	var day = date.getDate();
	return year + '/' + month + '/' + day;
}

/**
 * Returns a shade of blue that
 * is lighter based on
 * number of years in the past
 * returns an rgba string
 *
 * @param <Integer> yearsInThePast
 * @return <String>
 */
function historicalColor(yearsInThePast) {
	var baseBlue = 149;
	var increment = 15;

	var blue = baseBlue + increment * yearsInThePast;

	return 'rgba(34, 110, ' + blue +', 1)';
}

/**
 * tbh i copied this from stackoverflow
 * and have no idea what it does
 *
 * @param <String> name
 * @returns <String>
 */
function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

/**
 * Turns an object to a query string
 * and appends it to the current path
 *
 * @param <Object> params
 */
function updateURL(params) {
	var str = [];
  	for(var p in params) {
    	if (params.hasOwnProperty(p)) {
    		str.push(encodeURIComponent(p) + '=' + encodeURIComponent(params[p]));
    	}
  	}
  	var queryString = str.join('&');
	window.history.pushState('', '', '/?' + queryString);
}

/**
 * Returns true if the user-agent is
 * a mobile device
 *
 * @returns <Boolean>
 */
function isMobile() {
	if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
 		return true;
	} else {
		return false;
	}
}

/**
 * Turns an object to a query string
 * and appends it to the current path
 *
 * @param <Integer> accountIndex
 * @returns <String> baseURL
 */
function baseURL(accountIndex) {
	var firstPart = '';
	var secondPart = '';
	if (isMobile()) {
		firstPart = 'https://mail.google.com/mail/mu/mp/';
		secondPart = '#tl/search/';
	} else {
		firstPart = 'https://mail.google.com/mail/u/';
		secondPart = '#search/';
	}

	var url =  firstPart + accountIndex + secondPart;
	return url;
}

Template.bottle.helpers({

	/**
	 * Generates an array of historical URLs
	 * based on the provided number of years back
	 *
	 * @returns <Array>
	 */
 	historicalURLs: function() {
        var histories = [];
        // TODO(seanrose): un-hardcode numYears
        var numYears = getParameterByName('years') || 4;
        var accountIndex = getParameterByName('u') || 0;
        updateURL({
        	u: accountIndex,
        	years: numYears
        });

        var currentDate = new Date();

        for (var i = numYears; i > 0; i--) {
        	histories.push({
        		text: i + ' ' + (i == 1 ? 'year' : 'years') + ' ago',
        		url: historicalSearchURL(currentDate, i, accountIndex),
        		color: historicalColor(i)
        	});
        }

        return histories.reverse();
    }
});

Template.bottle.rendered = function () {
	$('.year').delay('fast').each( function(index, value) {
		$(value).delay(300 * index).fadeIn('slow');
	});
	$('.signature').delay($('a').length * 300 + 1000).fadeIn('slow');
	GAnalytics.pageview();
};
<commit_msg>fixed url issues<commit_after>/**
 * Returns a historical Gmail URL
 * based on the date passed in
 * yearsInThePast years back from the current date
 *
 * @param <Date> date
 * @param <Integer> yearsInThePast
 * @return <String> combinedURL
 */
function historicalSearchURL(date, yearsInThePast, accountIndex) {
	// creating a copy because idk how to javascript tbh
	var dateCopy = new Date(date);
	var year = dateCopy.getFullYear() - yearsInThePast;

	var afterComponent = 'after:' + historicalDate(dateCopy, yearsInThePast);

	dateCopy.setDate(dateCopy.getDate() + 1);
	var beforeComponent = 'before:' + historicalDate(dateCopy, yearsInThePast);

	var url = baseURL(accountIndex) + encodeURIComponent(afterComponent) + '+' + encodeURIComponent(beforeComponent);

	return url;
}

/**
 * Creates a formatted date string
 * YYYY/MM/DD
 * given a Date object and years in the past
 *
 * @param <Date> date
 * @param <Integer> yearsInThePast
 * @return <String>
 */
function historicalDate(date, yearsInThePast) {
	var year = date.getFullYear() - yearsInThePast;
	var month = date.getMonth() + 1;
	var day = date.getDate();
	return year + '/' + month + '/' + day;
}

/**
 * Returns a shade of blue that
 * is lighter based on
 * number of years in the past
 * returns an rgba string
 *
 * @param <Integer> yearsInThePast
 * @return <String>
 */
function historicalColor(yearsInThePast) {
	var baseBlue = 149;
	var increment = 15;

	var blue = baseBlue + increment * yearsInThePast;

	return 'rgba(34, 110, ' + blue +', 1)';
}

/**
 * tbh i copied this from stackoverflow
 * and have no idea what it does
 *
 * @param <String> name
 * @returns <String>
 */
function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

/**
 * Turns an object to a query string
 * and appends it to the current path
 *
 * @param <Object> params
 */
function updateURL(params) {
	var str = [];
  	for(var p in params) {
    	if (params.hasOwnProperty(p)) {
    		str.push(encodeURIComponent(p) + '=' + encodeURIComponent(params[p]));
    	}
  	}
  	var queryString = str.join('&');
	window.history.pushState('', '', '/?' + queryString);
}

/**
 * Returns true if the user-agent is
 * a mobile device
 *
 * @returns <Boolean>
 */
function isMobile() {
	if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
 		return true;
	} else {
		return false;
	}
}

/**
 * Turns an object to a query string
 * and appends it to the current path
 *
 * @param <Integer> accountIndex
 * @returns <String> baseURL
 */
function baseURL(accountIndex) {
	var firstPart = '';
	var secondPart = '';
	if (isMobile()) {
		firstPart = 'https://mail.google.com/mail/mu/mp/';
		secondPart = '#tl/search/';
	} else {
		firstPart = 'https://mail.google.com/mail/u/';
		secondPart = '/#search/';
	}

	var url =  firstPart + accountIndex + secondPart;
	return url;
}

Template.bottle.helpers({

	/**
	 * Generates an array of historical URLs
	 * based on the provided number of years back
	 *
	 * @returns <Array>
	 */
 	historicalURLs: function() {
        var histories = [];
        // TODO(seanrose): un-hardcode numYears
        var numYears = getParameterByName('years') || 4;
        var accountIndex = getParameterByName('u') || 0;
        updateURL({
        	u: accountIndex,
        	years: numYears
        });

        var currentDate = new Date();

        for (var i = numYears; i > 0; i--) {
        	histories.push({
        		text: i + ' ' + (i == 1 ? 'year' : 'years') + ' ago',
        		url: historicalSearchURL(currentDate, i, accountIndex),
        		color: historicalColor(i)
        	});
        }

        return histories.reverse();
    }
});

Template.bottle.rendered = function () {
	$('.year').delay('fast').each( function(index, value) {
		$(value).delay(300 * index).fadeIn('slow');
	});
	$('.signature').delay($('a').length * 300 + 1000).fadeIn('slow');
	GAnalytics.pageview();
};
<|endoftext|>


<commit_before>import React from 'react'
import { Link } from 'react-router'
import { prefixLink } from 'gatsby-helpers'
import Helmet from 'react-helmet'
import { config } from 'config'

export default class Index extends React.Component {
  render () {
    return (

        <Helmet
          title={config.siteTitle}
          meta={[
            {"name": "description", "content": "Sample"},
            {"name": "keywords", "content": "sample, something"},
          ]}
        />

          Caleb
Sawtell

    )
  }
}
<commit_msg>added tag to page<commit_after>import React from 'react'
import { Link } from 'react-router'
import { prefixLink } from 'gatsby-helpers'
import Helmet from 'react-helmet'
import { config } from 'config'

export default class Index extends React.Component {
  render () {
    return (

        <Helmet
          title={config.siteTitle}
          meta={[
            {"name": "description", "content": "Sample"},
            {"name": "keywords", "content": "sample, something"},
          ]}
        />

          Caleb
Sawtell

Designer &
 Frontend Developer

    )
  }
}
<|endoftext|>


<commit_before>/**
 * Block Store
 *
 * The Block Store is responsible for defining how blocks are stored
 * within Colonel Kurtz. Whenever an action associated with block
 * records is pushed into the system, this module tells Colonel Kurtz
 * how that action manipulates block data.
 */

import Actions      from 'actions/blocks'
import Block        from 'models/block'
import blocksToJson from 'utils/blocksToJson'
import findBy       from 'utils/findBy'
import insertAt     from 'utils/insertAt'
import jsonToBlocks from 'utils/jsonToBlocks'

export default {

  /**
   * getInitialState
   * Runs whenever the application starts. Provides the expected array
   * relied upon by other operations
   */
  getInitialState() {
    return []
  },

  /**
   * toString
   * Tells Colonel Kurtz that this store manages the `blocks` key. Any
   * modifications related to blocks can be found there
   */
  toString() {
    return 'blocks'
  },

  /**
   * For simplicity, blocks are stored in an array, `blocksToJson`
   * takes a list of blocks and transforms them into the nested structure
   * shown in the front end
   */
  serialize   : blocksToJson,

  /**
   * As mentioned previously, blocks are serialized to a nested
   * structure. jsonToBlocks takes this nested structure and flattens
   * into a list for this store.
   */
  deserialize : jsonToBlocks,

  /**
   * Actions.append
   * Simply add a new block at the end of the list
   */
  [Actions.append](state, params) {
    return state.concat(new Block(params))
  },

  /**
   * Actions.create
   * Produces a new block based upon given parameters.
   * If the provided position is a Block, place the new block right
   * after it.
   */
  [Actions.create](state, { type, parent, position=0 }) {
    let record = new Block({ parent, type })

      if (position instanceof Block) {
        position = state.indexOf(position) + 1
      }

    return insertAt(state, record, position)
  },

  /**
   * Actions.destroy
   * Given an id, remove that block and eliminate all other blocks
   * nested inside of it. This is important so removing that `section`
   * type blocks also eliminates children
   */
  [Actions.destroy](state, id) {
    return state.filter(function(node) {
      for (var n = node; n; n = n.parent) {
        if (n.id == id) return false
      }
      return true
    })
  },

  /**
   * Actions.update
   * Given a parameters, find the block associated with those
   * parameters (by id) and update the content inside. All other
   * attributes will not be changed.
   */
  [Actions.update](state, params) {
    var block = findBy(state, params.id, 'id')

    block.content = { ...block.content, ...params.content }

    return state
  }

}
<commit_msg>[update] Inline requires to serialize methods<commit_after>/**
 * Block Store
 *
 * The Block Store is responsible for defining how blocks are stored
 * within Colonel Kurtz. Whenever an action associated with block
 * records is pushed into the system, this module tells Colonel Kurtz
 * how that action manipulates block data.
 */

import Actions      from 'actions/blocks'
import Block        from 'models/block'
import findBy       from 'utils/findBy'
import insertAt     from 'utils/insertAt'

export default {

  /**
   * getInitialState
   * Runs whenever the application starts. Provides the expected array
   * relied upon by other operations
   */
  getInitialState() {
    return []
  },

  /**
   * toString
   * Tells Colonel Kurtz that this store manages the `blocks` key. Any
   * modifications related to blocks can be found there
   */
  toString() {
    return 'blocks'
  },

  /**
   * For simplicity, blocks are stored in an array, `blocksToJson`
   * takes a list of blocks and transforms them into the nested structure
   * shown in the front end
   */
  serialize: require('utils/blocksToJson'),

  /**
   * As mentioned previously, blocks are serialized to a nested
   * structure. jsonToBlocks takes this nested structure and flattens
   * into a list for this store.
   */
  deserialize: require('utils/jsonToBlocks'),

  /**
   * Actions.append
   * Simply add a new block at the end of the list
   */
  [Actions.append](state, params) {
    return state.concat(new Block(params))
  },

  /**
   * Actions.create
   * Produces a new block based upon given parameters.
   * If the provided position is a Block, place the new block right
   * after it.
   */
  [Actions.create](state, { type, parent, position=0 }) {
    let record = new Block({ parent, type })

      if (position instanceof Block) {
        position = state.indexOf(position) + 1
      }

    return insertAt(state, record, position)
  },

  /**
   * Actions.destroy
   * Given an id, remove that block and eliminate all other blocks
   * nested inside of it. This is important so removing that `section`
   * type blocks also eliminates children
   */
  [Actions.destroy](state, id) {
    return state.filter(function(node) {
      for (var n = node; n; n = n.parent) {
        if (n.id == id) return false
      }
      return true
    })
  },

  /**
   * Actions.update
   * Given a parameters, find the block associated with those
   * parameters (by id) and update the content inside. All other
   * attributes will not be changed.
   */
  [Actions.update](state, params) {
    var block = findBy(state, params.id, 'id')

    block.content = { ...block.content, ...params.content }

    return state
  }

}
<|endoftext|>


<commit_before>const Attachment = require('./MessageAttachment');
const Embed = require('./MessageEmbed');
const Collection = require('../util/Collection');

/**
 * Represents a Message on Discord
 */
class Message {
  constructor(channel, data, client) {
    /**
     * The client that instantiated the Message
     * @type {Client}
     */
    this.client = client;
    Object.defineProperty(this, 'client', { enumerable: false, configurable: false });

    /**
     * The channel that the message was sent in
     * @type {TextChannel|DMChannel|GroupDMChannel}
     */
    this.channel = channel;

    /**
     * If the message was sent in a guild, this will be the guild the message was sent in
     * @type {?Guild}
     */
    this.guild = channel.guild || null;

    if (data) this.setup(data);
  }

  setup(data) {
    /**
     * The ID of the message (unique in the channel it was sent)
     * @type {string}
     */
    this.id = data.id;

    /**
     * The content of the message
     * @type {string}
     */
    this.content = data.content;

    /**
     * The author of the message
     * @type {User}
     */
    this.author = this.client.dataManager.newUser(data.author);

    /**
     * Represents the Author of the message as a Guild Member. Only available if the message comes from a Guild
     * where the author is still a member.
     * @type {GuildMember}
     */
    this.member = this.guild ? this.guild.member(this.author) || null : null;

    /**
     * Whether or not this message is pinned
     * @type {boolean}
     */
    this.pinned = data.pinned;

    /**
     * Whether or not the message was Text-To-Speech
     * @type {boolean}
     */
    this.tts = data.tts;

    /**
     * A random number used for checking message delivery
     * @type {string}
     */
    this.nonce = data.nonce;

    /**
     * Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications)
     * @type {boolean}
     */
    this.system = data.type === 6;

    /**
     * A list of embeds in the message - e.g. YouTube Player
     * @type {Embed[]}
     */
    this.embeds = data.embeds.map(e => new Embed(this, e));

    /**
     * A collection of attachments in the message - e.g. Pictures - mapped by their ID.
     * @type {Collection<string, MessageAttachment>}
     */
    this.attachments = new Collection();
    for (const attachment of data.attachments) this.attachments.set(attachment.id, new Attachment(this, attachment));

    /**
     * An object containing a further users, roles or channels collections
     * @type {Object}
     * @property {Collection<string, User>} mentions.users Mentioned users, maps their ID to the user object.
     * @property {Collection<string, Role>} mentions.roles Mentioned roles, maps their ID to the role object.
     * @property {Collection<string, GuildChannel>} mentions.channels Mentioned channels,
     * maps their ID to the channel object.
     * @property {boolean} mentions.everyone Whether or not @everyone was mentioned.
     */
    this.mentions = {
      users: new Collection(),
      roles: new Collection(),
      channels: new Collection(),
      everyone: data.mention_everyone,
    };

    for (const mention of data.mentions) {
      let user = this.client.users.get(mention.id);
      if (user) {
        this.mentions.users.set(user.id, user);
      } else {
        user = this.client.dataManager.newUser(mention);
        this.mentions.users.set(user.id, user);
      }
    }

    if (data.mention_roles) {
      for (const mention of data.mention_roles) {
        const role = this.channel.guild.roles.get(mention);
        if (role) this.mentions.roles.set(role.id, role);
      }
    }

    if (this.channel.guild) {
      const channMentionsRaw = data.content.match(/<#([0-9]{14,20})>/g) || [];
      for (const raw of channMentionsRaw) {
        const chan = this.channel.guild.channels.get(raw.match(/([0-9]{14,20})/g)[0]);
        if (chan) this.mentions.channels.set(chan.id, chan);
      }
    }

    this._timestamp = new Date(data.timestamp).getTime();
    this._editedTimestamp = data.edited_timestamp ? new Date(data.edited_timestamp).getTime() : null;
    this._edits = [];
  }

  /**
   * When the message was sent
   * @type {Date}
   */
  get timestamp() {
    return new Date(this._timestamp);
  }

  /**
   * If the message was edited, the timestamp at which it was last edited
   * @type {?Date}
   */
  get editedTimestamp() {
    return new Date(this._editedTimestamp);
  }

  /**
   * The message contents with all mentions replaced by the equivalent text.
   * @type {string}
   */
  get cleanContent() {
    return this.content.replace(/<@!?[0-9]+>/g, input => {
      let user = this.channel.guild.members.get(input.replace(/<|!|>|@/g, ''));

      if (user) {
        if (user.nickname) {
          return `@${user.nickname}`;
        }
        return `@${user.user.username}`;
      }

      return input;
    }).replace(/<#[0-9]+>/g, (input) => {
      let channel = this.client.channels.get(input.replace(/<|#|>/g, ''));

      if (channel) {
        return `#${channel.name}`;
      }

      return input;
    }).replace(/<@&[0-9]+>/g, (input) => {
      let role = this.guild.roles.get(input.replace(/<|@|>|&/g, ''));

      if (role) {
        return `@${role.name}`;
      }

      return input;
    })
    .replace(/@everyone/g, '@\u200Beveryone')
    .replace(/@here/g, '@\u200Bhere');
  }

 /**
   * An array of cached versions of the message, including the current version.
   * Sorted from latest (first) to oldest (last).
   * @type {Message[]}
   */
  get edits() {
    return this._edits.slice().unshift(this);
  }

  patch(data) { // eslint-disable-line complexity
    if (data.author) {
      this.author = this.client.users.get(data.author.id);
      if (this.guild) this.member = this.guild.member(this.author);
    }
    if (data.content) this.content = data.content;
    if (data.timestamp) this._timestamp = new Date(data.timestamp).getTime();
    if (data.edited_timestamp) {
      this._editedTimestamp = data.edited_timestamp ? new Date(data.edited_timestamp).getTime() : null;
    }
    if ('tts' in data) this.tts = data.tts;
    if ('mention_everyone' in data) this.mentions.everyone = data.mention_everyone;
    if (data.nonce) this.nonce = data.nonce;
    if (data.embeds) this.embeds = data.embeds.map(e => new Embed(this, e));
    if (data.type > -1) {
      this.system = false;
      if (data.type === 6) {
        this.system = true;
      }
    }
    if (data.attachments) {
      this.attachments = new Collection();
      for (const attachment of data.attachments) {
        this.attachments.set(attachment.id, new Attachment(this, attachment));
      }
    }
    if (data.mentions) {
      for (const mention of data.mentions) {
        let user = this.client.users.get(mention.id);
        if (user) {
          this.mentions.users.set(user.id, user);
        } else {
          user = this.client.dataManager.newUser(mention);
          this.mentions.users.set(user.id, user);
        }
      }
    }
    if (data.mention_roles) {
      for (const mention of data.mention_roles) {
        const role = this.channel.guild.roles.get(mention);
        if (role) {
          this.mentions.roles.set(role.id, role);
        }
      }
    }
    if (data.id) this.id = data.id;
    if (this.channel.guild && data.content) {
      const channMentionsRaw = data.content.match(/<#([0-9]{14,20})>/g) || [];
      for (const raw of channMentionsRaw) {
        const chan = this.channel.guild.channels.get(raw.match(/([0-9]{14,20})/g)[0]);
        if (chan) {
          this.mentions.channels.set(chan.id, chan);
        }
      }
    }
  }

  /**
   * Deletes the message
   * @param {number} [timeout=0] How long to wait to delete the message in milliseconds
   * @returns {Promise<Message>}
   * @example
   * // delete a message
   * message.delete()
   *  .then(msg => console.log(`Deleted message from ${msg.author}`))
   *  .catch(console.log);
   */
  delete(timeout = 0) {
    return new Promise((resolve, reject) => {
      this.client.setTimeout(() => {
        this.client.rest.methods.deleteMessage(this)
          .then(resolve)
          .catch(reject);
      }, timeout);
    });
  }

  /**
   * Edit the content of a message
   * @param {StringResolvable} content The new content for the message
   * @returns {Promise<Message>}
   * @example
   * // update the content of a message
   * message.edit('This is my new content!')
   *  .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
   *  .catch(console.log);
   */
  edit(content) {
    return this.client.rest.methods.updateMessage(this, content);
  }

  /**
   * Reply to a message
   * @param {StringResolvable} content The content for the message
   * @param {MessageOptions} [options = {}] The options to provide
   * @returns {Promise<Message>}
   * @example
   * // reply to a message
   * message.reply('Hey, I'm a reply!')
   *  .then(msg => console.log(`Sent a reply to ${msg.author}`))
   *  .catch(console.log);
   */
  reply(content, options = {}) {
    content = this.client.resolver.resolveString(content);
    const newContent = this.guild ? `${this.author}, ${content}` : content;
    return this.client.rest.methods.sendMessage(this.channel, newContent, options.tts);
  }

  /**
   * Whether or not a user, channel or role is mentioned in this message.
   * @param {GuildChannel|User|Role|string} data either a guild channel, user or a role object, or a string representing
   * the ID of any of these.
   * @returns {boolean}
   */
  isMentioned(data) {
    data = data.id ? data.id : data;
    return this.mentions.users.has(data) || this.mentions.channels.has(data) || this.mentions.roles.has(data);
  }

  /**
   * Pins this message to the channel's pinned messages
   * @returns {Promise<Message>}
   */
  pin() {
    return this.client.rest.methods.pinMessage(this);
  }

  /**
   * Unpins this message from the channel's pinned messages
   * @returns {Promise<Message>}
   */
  unpin() {
    return this.client.rest.methods.unpinMessage(this);
  }

  /**
   * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages
   * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This
   * method allows you to see if there are differences in content, embeds, attachments, nonce and tts properties.
   * @param {Message} message The message to compare it to
   * @param {Object} rawData Raw data passed through the WebSocket about this message
   * @returns {boolean}
   */
  equals(message, rawData) {
    if (!message) return false;
    const embedUpdate = !message.author && !message.attachments;
    if (embedUpdate) return this.id === message.id && this.embeds.length === message.embeds.length;

    let equal = this.id === message.id &&
        this.author.id === message.author.id &&
        this.content === message.content &&
        this.tts === message.tts &&
        this.nonce === message.nonce &&
        this.embeds.length === message.embeds.length &&
        this.attachments.length === message.attachments.length;

    if (equal && rawData) {
      equal = this.mentions.everyone === message.mentions.everyone &&
        this._timestamp === new Date(rawData.timestamp).getTime() &&
        this._editedTimestamp === new Date(rawData.edited_timestamp).getTime();
    }

    return equal;
  }
}

module.exports = Message;
<commit_msg>Improve cleanContent code<commit_after>const Attachment = require('./MessageAttachment');
const Embed = require('./MessageEmbed');
const Collection = require('../util/Collection');

/**
 * Represents a Message on Discord
 */
class Message {
  constructor(channel, data, client) {
    /**
     * The client that instantiated the Message
     * @type {Client}
     */
    this.client = client;
    Object.defineProperty(this, 'client', { enumerable: false, configurable: false });

    /**
     * The channel that the message was sent in
     * @type {TextChannel|DMChannel|GroupDMChannel}
     */
    this.channel = channel;

    /**
     * If the message was sent in a guild, this will be the guild the message was sent in
     * @type {?Guild}
     */
    this.guild = channel.guild || null;

    if (data) this.setup(data);
  }

  setup(data) {
    /**
     * The ID of the message (unique in the channel it was sent)
     * @type {string}
     */
    this.id = data.id;

    /**
     * The content of the message
     * @type {string}
     */
    this.content = data.content;

    /**
     * The author of the message
     * @type {User}
     */
    this.author = this.client.dataManager.newUser(data.author);

    /**
     * Represents the Author of the message as a Guild Member. Only available if the message comes from a Guild
     * where the author is still a member.
     * @type {GuildMember}
     */
    this.member = this.guild ? this.guild.member(this.author) || null : null;

    /**
     * Whether or not this message is pinned
     * @type {boolean}
     */
    this.pinned = data.pinned;

    /**
     * Whether or not the message was Text-To-Speech
     * @type {boolean}
     */
    this.tts = data.tts;

    /**
     * A random number used for checking message delivery
     * @type {string}
     */
    this.nonce = data.nonce;

    /**
     * Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications)
     * @type {boolean}
     */
    this.system = data.type === 6;

    /**
     * A list of embeds in the message - e.g. YouTube Player
     * @type {Embed[]}
     */
    this.embeds = data.embeds.map(e => new Embed(this, e));

    /**
     * A collection of attachments in the message - e.g. Pictures - mapped by their ID.
     * @type {Collection<string, MessageAttachment>}
     */
    this.attachments = new Collection();
    for (const attachment of data.attachments) this.attachments.set(attachment.id, new Attachment(this, attachment));

    /**
     * An object containing a further users, roles or channels collections
     * @type {Object}
     * @property {Collection<string, User>} mentions.users Mentioned users, maps their ID to the user object.
     * @property {Collection<string, Role>} mentions.roles Mentioned roles, maps their ID to the role object.
     * @property {Collection<string, GuildChannel>} mentions.channels Mentioned channels,
     * maps their ID to the channel object.
     * @property {boolean} mentions.everyone Whether or not @everyone was mentioned.
     */
    this.mentions = {
      users: new Collection(),
      roles: new Collection(),
      channels: new Collection(),
      everyone: data.mention_everyone,
    };

    for (const mention of data.mentions) {
      let user = this.client.users.get(mention.id);
      if (user) {
        this.mentions.users.set(user.id, user);
      } else {
        user = this.client.dataManager.newUser(mention);
        this.mentions.users.set(user.id, user);
      }
    }

    if (data.mention_roles) {
      for (const mention of data.mention_roles) {
        const role = this.channel.guild.roles.get(mention);
        if (role) this.mentions.roles.set(role.id, role);
      }
    }

    if (this.channel.guild) {
      const channMentionsRaw = data.content.match(/<#([0-9]{14,20})>/g) || [];
      for (const raw of channMentionsRaw) {
        const chan = this.channel.guild.channels.get(raw.match(/([0-9]{14,20})/g)[0]);
        if (chan) this.mentions.channels.set(chan.id, chan);
      }
    }

    this._timestamp = new Date(data.timestamp).getTime();
    this._editedTimestamp = data.edited_timestamp ? new Date(data.edited_timestamp).getTime() : null;
    this._edits = [];
  }

  /**
   * When the message was sent
   * @type {Date}
   */
  get timestamp() {
    return new Date(this._timestamp);
  }

  /**
   * If the message was edited, the timestamp at which it was last edited
   * @type {?Date}
   */
  get editedTimestamp() {
    return new Date(this._editedTimestamp);
  }

  /**
   * The message contents with all mentions replaced by the equivalent text.
   * @type {string}
   */
  get cleanContent() {
    return this.content
      .replace(/@everyone/g, '@\u200Beveryone')
      .replace(/@here/g, '@\u200Bhere')
      .replace(/<@!?[0-9]+>/g, input => {
        const id = input.replace(/<|!|>|@/g, '');
        const member = this.channel.guild.members.get(id);
        if (member) {
          if (member.nickname) return `@${member.nickname}`;
          return `@${member.user.username}`;
        } else {
          const user = this.client.users.get(id);
          if (user) return `@${user.username}`;
          return input;
        }
      }).replace(/<#[0-9]+>/g, (input) => {
        const channel = this.client.channels.get(input.replace(/<|#|>/g, ''));
        if (channel) return `#${channel.name}`;
        return input;
      }).replace(/<@&[0-9]+>/g, (input) => {
        const role = this.guild.roles.get(input.replace(/<|@|>|&/g, ''));
        if (role) return `@${role.name}`;
        return input;
      });
  }

 /**
   * An array of cached versions of the message, including the current version.
   * Sorted from latest (first) to oldest (last).
   * @type {Message[]}
   */
  get edits() {
    return this._edits.slice().unshift(this);
  }

  patch(data) { // eslint-disable-line complexity
    if (data.author) {
      this.author = this.client.users.get(data.author.id);
      if (this.guild) this.member = this.guild.member(this.author);
    }
    if (data.content) this.content = data.content;
    if (data.timestamp) this._timestamp = new Date(data.timestamp).getTime();
    if (data.edited_timestamp) {
      this._editedTimestamp = data.edited_timestamp ? new Date(data.edited_timestamp).getTime() : null;
    }
    if ('tts' in data) this.tts = data.tts;
    if ('mention_everyone' in data) this.mentions.everyone = data.mention_everyone;
    if (data.nonce) this.nonce = data.nonce;
    if (data.embeds) this.embeds = data.embeds.map(e => new Embed(this, e));
    if (data.type > -1) {
      this.system = false;
      if (data.type === 6) {
        this.system = true;
      }
    }
    if (data.attachments) {
      this.attachments = new Collection();
      for (const attachment of data.attachments) {
        this.attachments.set(attachment.id, new Attachment(this, attachment));
      }
    }
    if (data.mentions) {
      for (const mention of data.mentions) {
        let user = this.client.users.get(mention.id);
        if (user) {
          this.mentions.users.set(user.id, user);
        } else {
          user = this.client.dataManager.newUser(mention);
          this.mentions.users.set(user.id, user);
        }
      }
    }
    if (data.mention_roles) {
      for (const mention of data.mention_roles) {
        const role = this.channel.guild.roles.get(mention);
        if (role) {
          this.mentions.roles.set(role.id, role);
        }
      }
    }
    if (data.id) this.id = data.id;
    if (this.channel.guild && data.content) {
      const channMentionsRaw = data.content.match(/<#([0-9]{14,20})>/g) || [];
      for (const raw of channMentionsRaw) {
        const chan = this.channel.guild.channels.get(raw.match(/([0-9]{14,20})/g)[0]);
        if (chan) {
          this.mentions.channels.set(chan.id, chan);
        }
      }
    }
  }

  /**
   * Deletes the message
   * @param {number} [timeout=0] How long to wait to delete the message in milliseconds
   * @returns {Promise<Message>}
   * @example
   * // delete a message
   * message.delete()
   *  .then(msg => console.log(`Deleted message from ${msg.author}`))
   *  .catch(console.log);
   */
  delete(timeout = 0) {
    return new Promise((resolve, reject) => {
      this.client.setTimeout(() => {
        this.client.rest.methods.deleteMessage(this)
          .then(resolve)
          .catch(reject);
      }, timeout);
    });
  }

  /**
   * Edit the content of a message
   * @param {StringResolvable} content The new content for the message
   * @returns {Promise<Message>}
   * @example
   * // update the content of a message
   * message.edit('This is my new content!')
   *  .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
   *  .catch(console.log);
   */
  edit(content) {
    return this.client.rest.methods.updateMessage(this, content);
  }

  /**
   * Reply to a message
   * @param {StringResolvable} content The content for the message
   * @param {MessageOptions} [options = {}] The options to provide
   * @returns {Promise<Message>}
   * @example
   * // reply to a message
   * message.reply('Hey, I'm a reply!')
   *  .then(msg => console.log(`Sent a reply to ${msg.author}`))
   *  .catch(console.log);
   */
  reply(content, options = {}) {
    content = this.client.resolver.resolveString(content);
    const newContent = this.guild ? `${this.author}, ${content}` : content;
    return this.client.rest.methods.sendMessage(this.channel, newContent, options.tts);
  }

  /**
   * Whether or not a user, channel or role is mentioned in this message.
   * @param {GuildChannel|User|Role|string} data either a guild channel, user or a role object, or a string representing
   * the ID of any of these.
   * @returns {boolean}
   */
  isMentioned(data) {
    data = data.id ? data.id : data;
    return this.mentions.users.has(data) || this.mentions.channels.has(data) || this.mentions.roles.has(data);
  }

  /**
   * Pins this message to the channel's pinned messages
   * @returns {Promise<Message>}
   */
  pin() {
    return this.client.rest.methods.pinMessage(this);
  }

  /**
   * Unpins this message from the channel's pinned messages
   * @returns {Promise<Message>}
   */
  unpin() {
    return this.client.rest.methods.unpinMessage(this);
  }

  /**
   * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages
   * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This
   * method allows you to see if there are differences in content, embeds, attachments, nonce and tts properties.
   * @param {Message} message The message to compare it to
   * @param {Object} rawData Raw data passed through the WebSocket about this message
   * @returns {boolean}
   */
  equals(message, rawData) {
    if (!message) return false;
    const embedUpdate = !message.author && !message.attachments;
    if (embedUpdate) return this.id === message.id && this.embeds.length === message.embeds.length;

    let equal = this.id === message.id &&
        this.author.id === message.author.id &&
        this.content === message.content &&
        this.tts === message.tts &&
        this.nonce === message.nonce &&
        this.embeds.length === message.embeds.length &&
        this.attachments.length === message.attachments.length;

    if (equal && rawData) {
      equal = this.mentions.everyone === message.mentions.everyone &&
        this._timestamp === new Date(rawData.timestamp).getTime() &&
        this._editedTimestamp === new Date(rawData.edited_timestamp).getTime();
    }

    return equal;
  }
}

module.exports = Message;
<|endoftext|>


<commit_before>export default function initCookies() {
  const cc = initCookieConsent();
  cc.run({
    current_lang: 'ru',
    page_scripts: true,
    cookie_expiration: 182,
    languages: {
      'ru': {
        consent_modal: {
          title: 'Переходи на тёмную стороу, у нас есть 🍪',
          description: "Блог использует Google Analytics для статистики, чтобы понимать, кто его читает. " +
            "Поэтому мне нужно ваше разрешение на использование соотвествующих куков. " +
            "Политика конфиденциальности (на английском).",
          primary_btn: {
            text: 'Согласен',
            role: 'accept_all'      //'accept_selected' or 'accept_all'
          },
          secondary_btn: {
            text: 'Настройки',
            role: 'settings'       //'settings' or 'accept_necessary'
          },
        },
        settings_modal: {
          title: 'Настройки',
          save_settings_btn: 'Сохранить',
          accept_all_btn: 'Принять все',
          reject_all_btn: 'Отклонить все',
          close_btn_label: 'Закрыть',
          blocks: [
            {
              title: 'Аналитика',
              description: 'С помощью этих куков собирается информация, как вы пользуетесь сайтом, какие страницы ' +
                'посещаете и какие ссылки нажимаете. Все данные обезличены и не могут быть использованы для ' +
                'вашей идентификации.',
              toggle: {
                value: 'analytics',
                enabled: false,
                readonly: false
              },
            }, {
              title: 'Остались вопросы?',
              description: 'Посмотрите политику конфиденциальности или напишите мне.',
            }
          ]
        }
      }
    }
  });
}
<commit_msg>Fix strings.<commit_after>export default function initCookies() {
  const cc = initCookieConsent();
  cc.run({
    current_lang: 'ru',
    page_scripts: true,
    cookie_expiration: 182,
    languages: {
      'ru': {
        consent_modal: {
          title: 'Переходи на тёмную сторону, у нас есть 🍪',
          description: "Блог использует Google Analytics для статистики, чтобы понимать, кто его читает. " +
            "Поэтому мне нужно ваше разрешение на использование соотвествующих куков. " +
            "Политика конфиденциальности (на английском).",
          primary_btn: {
            text: 'Принять',
            role: 'accept_all'      //'accept_selected' or 'accept_all'
          },
          secondary_btn: {
            text: 'Настройки',
            role: 'settings'       //'settings' or 'accept_necessary'
          },
        },
        settings_modal: {
          title: 'Настройки',
          save_settings_btn: 'Сохранить',
          accept_all_btn: 'Принять все',
          reject_all_btn: 'Отклонить все',
          close_btn_label: 'Закрыть',
          blocks: [
            {
              title: 'Аналитика',
              description: 'С помощью этих куков собирается информация, как вы пользуетесь сайтом, какие страницы ' +
                'посещаете и какие ссылки нажимаете. Все данные обезличены и не могут быть использованы для ' +
                'вашей идентификации.',
              toggle: {
                value: 'analytics',
                enabled: false,
                readonly: false
              },
            }, {
              title: 'Остались вопросы?',
              description: 'Посмотрите политику конфиденциальности или напишите мне.',
            }
          ]
        }
      }
    }
  });
}
<|endoftext|>