<commit_before>import request from './request';
import config  from '../config';

/**
 * CRN
 *
 * A library for interacting with the CRN server side
 * application.
 */
export default {

// Users -----------------------------------------------------------------------------------

	/**
	 * Create User
	 *
	 */
	createUser(user, callback) {
		request.post(config.crn.url + 'users', {body: user}, callback);
	},

	/**
	 * Get Blacklist
	 */
	getBlacklist(callback) {
		request.get(config.crn.url + 'users/blacklist', {}, callback);
	},

	/**
	 * Blacklist User
	 */
	blacklistUser(user, callback) {
		request.post(config.crn.url + 'users/blacklist', {body: user}, callback);
	},

	/**
	 * Un Blacklist
	 */
	unBlacklistUser(userId, callback) {
		request.del(config.crn.url + 'users/blacklist/' + userId, callback);
	},

// Jobs ------------------------------------------------------------------------------------

	/**
	 * Get Apps
	 *
	 * Returns a list of available apps that
	 * can be run on AGAVE
	 */
	getApps(callback) {
		request.get(config.crn.url + 'apps', {}, callback);
	},

	/**
	 * Create Job
	 *
	 * Takes an options object with a name, appId
	 * datasetId and userId and starts a Job.
	 */
	createJob(options, callback) {
		request.post(config.crn.url + 'jobs', {body: options}, callback);
	},

	/**
	 * Get Dataset Jobs
	 */
	getDatasetJobs(datasetId, callback) {
		request.get(config.crn.url + 'jobs/' + datasetId, {}, callback);
	},

	/**
	 * Get Result Download Ticket
	 */
	getResultDownloadTicket(jobId, fileName, callback) {
		request.get('http://localhost:8765/api/v1/jobs/' + jobId + '/results/' + fileName + '/ticket', {}, callback);
	}

}<commit_msg>Update urls back from local dev to hosted<commit_after>import request from './request';
import config  from '../config';

/**
 * CRN
 *
 * A library for interacting with the CRN server side
 * application.
 */
export default {

// Users -----------------------------------------------------------------------------------

	/**
	 * Create User
	 *
	 */
	createUser(user, callback) {
		request.post(config.crn.url + 'users', {body: user}, callback);
	},

	/**
	 * Get Blacklist
	 */
	getBlacklist(callback) {
		request.get(config.crn.url + 'users/blacklist', {}, callback);
	},

	/**
	 * Blacklist User
	 */
	blacklistUser(user, callback) {
		request.post(config.crn.url + 'users/blacklist', {body: user}, callback);
	},

	/**
	 * Un Blacklist
	 */
	unBlacklistUser(userId, callback) {
		request.del(config.crn.url + 'users/blacklist/' + userId, callback);
	},

// Jobs ------------------------------------------------------------------------------------

	/**
	 * Get Apps
	 *
	 * Returns a list of available apps that
	 * can be run on AGAVE
	 */
	getApps(callback) {
		request.get(config.crn.url + 'apps', {}, callback);
	},

	/**
	 * Create Job
	 *
	 * Takes an options object with a name, appId
	 * datasetId and userId and starts a Job.
	 */
	createJob(options, callback) {
		request.post(config.crn.url + 'jobs', {body: options}, callback);
	},

	/**
	 * Get Dataset Jobs
	 */
	getDatasetJobs(datasetId, callback) {
		request.get(config.crn.url + 'jobs/' + datasetId, {}, callback);
	},

	/**
	 * Get Result Download Ticket
	 */
	getResultDownloadTicket(jobId, fileName, callback) {
		request.get(config.crn.url + 'jobs/' + jobId + '/results/' + fileName + '/ticket', {}, callback);
	}

}<|endoftext|>


<commit_before>
// Wait for DOM to be ready!
$(document).ready(function(){
	$.ajax({
		// Get all movies from our database.
    url:"movies/getallmovies",
		dataType:"json",
		cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
    success:function (data) {
    		//Adds the movies to the slider
    		//Adds the links
			renderData(data);

			//Load Contact Details
			loadProjectInformation();

			//Loads the facebook page events
			loadEvents();

			// Remove min-height on iOS after slideshow initialization
			var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true;
			if(iOS && $(".banner").css("min-height") == "100vh"){
				$(".banner").css("min-height", "0");
			}
		},
    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the data." + errorData.error);
		}
	});
});

function renderData (movies) {
  // Loop to add stuff from JSON-data (movies variable) to the DOM.

	//Loads the description from the Machine Title
	loadDescription(movies[0]['machinetitle']);

	for (var i = 0;i < movies.length;i++) {

		// Add only the thumbnail (not the actuall movie) to our div with class bxslider.
		$('.bxslider').append(

			// We give the thumbnail img a #id that is the same as the Youtube ID.
			'

'

		);

		//When the thumbnail is clicked...
		//The thumbnails id looks like this after concatenation: #4Vvd875V (hashtag + YouTube ID)
		$('#' + movies[i].link).click(function(){

			//The movies variable in renderData() is no longer available when this thumbnail is clicked.
			//So we get the Youtube video ID from the ID Hashtag instead of the movie variable!
			var videoID = $(this).attr('id');

			//(this). returns the  that was clicked. We cant put the video into the image....
			//So we replace the content in the parent of  ie the
!
			$(this).parent().html('');

		});
		// Add the titles+author to div with ID bx-pager.
		/* The best solution but the jQuery slider doesn't allow us to change the DOM structure links live in
		if(i < 5) {

			$('.left-column').append(
				'
'+ movies[i].title +' Av '+movies[i].author+'
 '
			);
     	}else{

			$('.right-column').append(
				'
'+ movies[i].title +' Av '+movies[i].author+'
 '
			);
    	}
    	*/

    	/*So were forced to work in a single div instead of a two column div system*/
    	$('#bx-pager').append(
			'
'+ movies[i].title +'
 '
		);

	};

	videoSlider();
	clickhandler();
}

// Activate the videoslider. This has to be done after the videos has been added to our DOM.
function videoSlider(){
	$('.bxslider').bxSlider({
		video: true,
		useCSS: false,
		pagerCustom: '#bx-pager'
	});
}
// makes the anchor scroll on the page smooth as silk
function clickhandler () {
	$('a').click(function(){

		//Get the movie title from the links ID and call loadDescription()
		var machineTitle = $(this).attr('id')
		loadDescription(machineTitle);

		//Reset all playing movies on slide change
		resetVideoPlayersToThumbnails();

		//Slide the correct movie into place
		$('html, body').animate({
			scrollTop: $( $(this).attr('href') ).offset().top
		}, 600);

		return false;
	});
}

//Loads the correct description and fades it in
function loadDescription(machineTitle){

	if(typeof machineTitle === 'undefined' || !machineTitle){
		return false;
	}

	$.ajax({
		// Get all movies from our database.
	    url:"movies/getmovie/" + machineTitle,
			dataType:"json",
			cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
	    success:function (data) {
	    	//If ajax call was successfull but no/wrong data was returned show error
	    	if(typeof data[0]['description'] === 'undefined' || !data[0]['description']){
		    	$('#moviedescription > p').fadeOut(function(){
					$('#moviedescription > p').text('Ingen info finns');
					$('#moviedescription > p').fadeIn();
		    	})
	    	//Everything good! Show description
	    	}else{
		    	$('#moviedescription > p').fadeOut(function(){
					$('#moviedescription > p').text(data[0]['description']);
					$('#moviedescription > p').fadeIn();
		    	})
	    	}
		},
	    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the description. " + errorData.error);
	    	$('#moviedescription > p').fadeOut(function(){
				$('#moviedescription > p').text('Ingen beskrivning hittades');
				$('#moviedescription > p').fadeIn();
	    	})
		}
	});
}

//Loads the correct description and fades it in
function loadEvents(){

	$.ajax({
		// Get all movies from our database.
	    url:"movies/getmovieevents/",
			dataType:"json",
			cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
	    success:function (data) {
	    	//If ajax call was successfull but no/wrong data was returned show error
	    	if(typeof data === 'undefined' || !data){
		    	$('#eventbox').fadeOut(function(){
					$('#eventbox > p').text('Evenemangen kommer snart');
					$('#eventbox > p').fadeIn();
		    	})
	    	//Everything good! Show description
	    	}else{

				//If we have more then one event we need to create rows with two columns
				//and place the events divs in them.
				if(data.length > 1){
					var rowNbr = 0;
					for(var i=0;i<data.length;i++){

						//If there is a end_time add a little formatting to it
						if(data[i]['end_time']){
							data[i]['end_time'] = ' - ' + data[i]['end_time'];
						}

						var paragraph = '
' + data[i]['name'] + '

' +
										'' + data[i]['start_date'] + ' ' + data[i]['start_time'] + data[i]['end_time'] + '
' +
										'' + data[i]['location'] + '

' +
										'

Länk till Eventet' + '
'

						var alignment = '';

						//when i is even send the div to the right
						//when i is odd send it to the left
						if(i % 2 == 0){

							alignment = 'event-left';

							rowNbr += 1;

							//Create a new row for two events
							$('#eventbox').append('

');

						}else{
							alignment = 'event-right';
						}

						//Create the eventdiv with the proper allignment and the event paragraph
						var eventDiv = '
' + paragraph + '
';

						//Appends the eventdiv the the current row
						$('#eventbox > .row-' + rowNbr).append(eventDiv).fadeIn();
					}

				//We only have one event so we just center it. Easy =)
				}else{
						//If there is a end_time add a little formatting to it
						if(data[0]['end_time']){
							data[0]['end_time'] = ' - ' + data[0]['end_time'];
						}

						var paragraph = '
' + data[0]['name'] + '

' +
										'' + data[0]['start_date'] + ' ' + data[0]['start_time'] + data[0]['end_time'] + '
' +
										'' + data[0]['location'] + '

' +
										'

Länk till Eventet' + '
'

					$('#eventbox').append('

 div').append(paragraph).fadeIn();
				}

	    	}
		},
	    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the event list. " + errorData.error);
	    	$('#eventbox > p').fadeOut(function(){
				$('#eventbox > p').text('Evenemangen kommer snart');
				$('#eventbox > p').fadeIn();
	    	})
		}
	});
}

function loadProjectInformation(){
	$.ajax({
		// Get all movies from our database.
	    url:"http://gdata.youtube.com/feeds/api/playlists/SwXUlPkaY_FZ7R9AsTo1yJi7wRthcBtx?v=2&alt=json",
			dataType:"json",
			cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
	    success:function (data) {
	    	//If ajax call was successfull but no/wrong data was returned show error
	    	if(typeof data['feed']['media$group']['media$description']['$t'] === 'undefined' || !data['feed']['media$group']['media$description']['$t']){
		    	$('#information > p').fadeOut(function(){
					$('#information > p').text('Ingen beskrivning hittades');
					$('#information > p').fadeIn();
		    	})
	    	//Everything good! Show Contact Details
	    	}else{
		    	$('#information > p').fadeOut(function(){
					$('#information > p').html(data['feed']['media$group']['media$description']['$t']);
					$('#information > p').fadeIn();
		    	})
	    	}
		},
	    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the project details. " + errorData.error);
	    	$('#information > p').fadeOut(function(){
				$('#information > p').text('Ingen beskrivning hittades');
				$('#information > p').fadeIn();
	    	})
		}
	});
}

//Returns the YoutubeID of the video currently visible in the slider.
function getMachineTitleFromLink(){
	/*Broken since link no longer get the .active class*/
	return $('#bx-pager > a.active').attr('id');
}

function resetVideoPlayersToThumbnails(){

	//If we find any iframes with the class .videolpayer then we switch 'em to images again!
	var videoID = $('.video-player').attr('id');

	if(videoID){

		//Transform our youtubevideo into a thumbnail image again
		$('.video-player').parent().html('');

		//Rebind the clickhandler so that if we click on our thumbnail again. It will be a youtube movie
		$('#' + videoID).click(function(){

			//The movies variable in renderData() is no longer available when this thumbnail is clicked.
			//So we get the Youtube video ID from the ID Hashtag instead of the movie variable!
			var videoID = $(this).attr('id');

			//(this). returns the  that was clicked. We cant put the video into the image....
			//So we replace the content in the parent of  ie the
!
			$(this).parent().html('');
		});
	}
}

function checkAndFixMissingImg(img){
	//the 404 image size is 120*40. If we find one try a lower resolution
	img.onload = '';
	if(img.naturalHeight == 90 && img.naturalWidth == 120){
		console.log(img.id + 'didnt have the specified thumbnail resolution, trying with lower res version');
		img.src = 'http://img.youtube.com/vi/' + img.id + '/hqdefault.jpg';
		img.className = 'small-thumbnail';

		//Theese two lines prevent you from dragging the image around with the mousebutton held down.
		img.ondragstart ="return false;";
		img.ondrop="return false;";

	}
}<commit_msg>safari buggfix v3<commit_after>
// Wait for DOM to be ready!
$(document).ready(function(){
	$.ajax({
		// Get all movies from our database.
    url:"movies/getallmovies",
		dataType:"json",
		cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
    success:function (data) {
    		//Adds the movies to the slider
    		//Adds the links
			renderData(data);

			//Load Contact Details
			loadProjectInformation();

			//Loads the facebook page events
			loadEvents();

			// Remove min-height on iOS after slideshow initialization
			var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true;
			if(iOS && $(".banner").css("min-height") == "100vh"){
				$(".banner").css("min-height", "0");
				console.log('fixing safari bugg....');
			}
		},
    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the data." + errorData.error);
		}
	});
});

function renderData (movies) {
  // Loop to add stuff from JSON-data (movies variable) to the DOM.

	//Loads the description from the Machine Title
	loadDescription(movies[0]['machinetitle']);

	for (var i = 0;i < movies.length;i++) {

		// Add only the thumbnail (not the actuall movie) to our div with class bxslider.
		$('.bxslider').append(

			// We give the thumbnail img a #id that is the same as the Youtube ID.
			'

'

		);

		//When the thumbnail is clicked...
		//The thumbnails id looks like this after concatenation: #4Vvd875V (hashtag + YouTube ID)
		$('#' + movies[i].link).click(function(){

			//The movies variable in renderData() is no longer available when this thumbnail is clicked.
			//So we get the Youtube video ID from the ID Hashtag instead of the movie variable!
			var videoID = $(this).attr('id');

			//(this). returns the  that was clicked. We cant put the video into the image....
			//So we replace the content in the parent of  ie the
!
			$(this).parent().html('');

		});
		// Add the titles+author to div with ID bx-pager.
		/* The best solution but the jQuery slider doesn't allow us to change the DOM structure links live in
		if(i < 5) {

			$('.left-column').append(
				'
'+ movies[i].title +' Av '+movies[i].author+'
 '
			);
     	}else{

			$('.right-column').append(
				'
'+ movies[i].title +' Av '+movies[i].author+'
 '
			);
    	}
    	*/

    	/*So were forced to work in a single div instead of a two column div system*/
    	$('#bx-pager').append(
			'
'+ movies[i].title +'
 '
		);

	};

	videoSlider();
	clickhandler();
}

// Activate the videoslider. This has to be done after the videos has been added to our DOM.
function videoSlider(){
	$('.bxslider').bxSlider({
		video: true,
		useCSS: false,
		pagerCustom: '#bx-pager'
	});
}
// makes the anchor scroll on the page smooth as silk
function clickhandler () {
	$('a').click(function(){

		//Get the movie title from the links ID and call loadDescription()
		var machineTitle = $(this).attr('id')
		loadDescription(machineTitle);

		//Reset all playing movies on slide change
		resetVideoPlayersToThumbnails();

		//Slide the correct movie into place
		$('html, body').animate({
			scrollTop: $( $(this).attr('href') ).offset().top
		}, 600);

		return false;
	});
}

//Loads the correct description and fades it in
function loadDescription(machineTitle){

	if(typeof machineTitle === 'undefined' || !machineTitle){
		return false;
	}

	$.ajax({
		// Get all movies from our database.
	    url:"movies/getmovie/" + machineTitle,
			dataType:"json",
			cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
	    success:function (data) {
	    	//If ajax call was successfull but no/wrong data was returned show error
	    	if(typeof data[0]['description'] === 'undefined' || !data[0]['description']){
		    	$('#moviedescription > p').fadeOut(function(){
					$('#moviedescription > p').text('Ingen info finns');
					$('#moviedescription > p').fadeIn();
		    	})
	    	//Everything good! Show description
	    	}else{
		    	$('#moviedescription > p').fadeOut(function(){
					$('#moviedescription > p').text(data[0]['description']);
					$('#moviedescription > p').fadeIn();
		    	})
	    	}
		},
	    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the description. " + errorData.error);
	    	$('#moviedescription > p').fadeOut(function(){
				$('#moviedescription > p').text('Ingen beskrivning hittades');
				$('#moviedescription > p').fadeIn();
	    	})
		}
	});
}

//Loads the correct description and fades it in
function loadEvents(){

	$.ajax({
		// Get all movies from our database.
	    url:"movies/getmovieevents/",
			dataType:"json",
			cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
	    success:function (data) {
	    	//If ajax call was successfull but no/wrong data was returned show error
	    	if(typeof data === 'undefined' || !data){
		    	$('#eventbox').fadeOut(function(){
					$('#eventbox > p').text('Evenemangen kommer snart');
					$('#eventbox > p').fadeIn();
		    	})
	    	//Everything good! Show description
	    	}else{

				//If we have more then one event we need to create rows with two columns
				//and place the events divs in them.
				if(data.length > 1){
					var rowNbr = 0;
					for(var i=0;i<data.length;i++){

						//If there is a end_time add a little formatting to it
						if(data[i]['end_time']){
							data[i]['end_time'] = ' - ' + data[i]['end_time'];
						}

						var paragraph = '
' + data[i]['name'] + '

' +
										'' + data[i]['start_date'] + ' ' + data[i]['start_time'] + data[i]['end_time'] + '
' +
										'' + data[i]['location'] + '

' +
										'

Länk till Eventet' + '
'

						var alignment = '';

						//when i is even send the div to the right
						//when i is odd send it to the left
						if(i % 2 == 0){

							alignment = 'event-left';

							rowNbr += 1;

							//Create a new row for two events
							$('#eventbox').append('

');

						}else{
							alignment = 'event-right';
						}

						//Create the eventdiv with the proper allignment and the event paragraph
						var eventDiv = '
' + paragraph + '
';

						//Appends the eventdiv the the current row
						$('#eventbox > .row-' + rowNbr).append(eventDiv).fadeIn();
					}

				//We only have one event so we just center it. Easy =)
				}else{
						//If there is a end_time add a little formatting to it
						if(data[0]['end_time']){
							data[0]['end_time'] = ' - ' + data[0]['end_time'];
						}

						var paragraph = '
' + data[0]['name'] + '

' +
										'' + data[0]['start_date'] + ' ' + data[0]['start_time'] + data[0]['end_time'] + '
' +
										'' + data[0]['location'] + '

' +
										'

Länk till Eventet' + '
'

					$('#eventbox').append('

 div').append(paragraph).fadeIn();
				}

	    	}
		},
	    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the event list. " + errorData.error);
	    	$('#eventbox > p').fadeOut(function(){
				$('#eventbox > p').text('Evenemangen kommer snart');
				$('#eventbox > p').fadeIn();
	    	})
		}
	});
}

function loadProjectInformation(){
	$.ajax({
		// Get all movies from our database.
	    url:"http://gdata.youtube.com/feeds/api/playlists/SwXUlPkaY_FZ7R9AsTo1yJi7wRthcBtx?v=2&alt=json",
			dataType:"json",
			cache: false,
		// If successfull, run our renderData function and send the data (a JSON-object) to it.
	    success:function (data) {
	    	//If ajax call was successfull but no/wrong data was returned show error
	    	if(typeof data['feed']['media$group']['media$description']['$t'] === 'undefined' || !data['feed']['media$group']['media$description']['$t']){
		    	$('#information > p').fadeOut(function(){
					$('#information > p').text('Ingen beskrivning hittades');
					$('#information > p').fadeIn();
		    	})
	    	//Everything good! Show Contact Details
	    	}else{
		    	$('#information > p').fadeOut(function(){
					$('#information > p').html(data['feed']['media$group']['media$description']['$t']);
					$('#information > p').fadeIn();
		    	})
	    	}
		},
	    // If error.
		error:function(errorData) {
			console.log("There seems to be an error fetching the project details. " + errorData.error);
	    	$('#information > p').fadeOut(function(){
				$('#information > p').text('Ingen beskrivning hittades');
				$('#information > p').fadeIn();
	    	})
		}
	});
}

//Returns the YoutubeID of the video currently visible in the slider.
function getMachineTitleFromLink(){
	/*Broken since link no longer get the .active class*/
	return $('#bx-pager > a.active').attr('id');
}

function resetVideoPlayersToThumbnails(){

	//If we find any iframes with the class .videolpayer then we switch 'em to images again!
	var videoID = $('.video-player').attr('id');

	if(videoID){

		//Transform our youtubevideo into a thumbnail image again
		$('.video-player').parent().html('');

		//Rebind the clickhandler so that if we click on our thumbnail again. It will be a youtube movie
		$('#' + videoID).click(function(){

			//The movies variable in renderData() is no longer available when this thumbnail is clicked.
			//So we get the Youtube video ID from the ID Hashtag instead of the movie variable!
			var videoID = $(this).attr('id');

			//(this). returns the  that was clicked. We cant put the video into the image....
			//So we replace the content in the parent of  ie the
!
			$(this).parent().html('');
		});
	}
}

function checkAndFixMissingImg(img){
	//the 404 image size is 120*40. If we find one try a lower resolution
	img.onload = '';
	if(img.naturalHeight == 90 && img.naturalWidth == 120){
		console.log(img.id + 'didnt have the specified thumbnail resolution, trying with lower res version');
		img.src = 'http://img.youtube.com/vi/' + img.id + '/hqdefault.jpg';
		img.className = 'small-thumbnail';

		//Theese two lines prevent you from dragging the image around with the mousebutton held down.
		img.ondragstart ="return false;";
		img.ondrop="return false;";

	}
}<|endoftext|>


<commit_before>module.exports = function SparqlGenerator() { return { stringify: toQuery }; };

// Converts the parsed query object into a SPARQL query
function toQuery(q) {
  var query = '';
  for (var key in q.prefixes)
    query += 'PREFIX ' + key + ': <' + q.prefixes[key] + '>\n';

  if (q.queryType)
    query += q.queryType.toUpperCase() + ' ';
  if (q.reduced)
    query += 'REDUCED ';
  if (q.distinct)
    query += 'DISTINCT ';

  if (q.variables)
    query += mapJoin(q.variables, function (variable) {
      return isString(variable) ? toEntity(variable) :
             '(' + toExpression(variable.expression) + ' AS ' + variable.variable + ')';
    }) + ' ';
  else if (q.template)
    query += '{\n' + toPattern(q.template) + '}\n';

  if (q.from)
    query += mapJoin(q.from.default || [], function (g) { return 'FROM ' + toEntity(g) + '\n'; }, '') +
             mapJoin(q.from.named || [], function (g) { return 'FROM NAMED ' + toEntity(g) + '\n'; }, '');
  if (q.where)
    query += 'WHERE {\n' + toPattern(q.where) + '}\n';

  if (q.updates)
    query += mapJoin(q.updates, toUpdate, '; ');
  else if (q.values)
    query += patterns.values(q);

  if (q.order)
    query += 'ORDER BY ' + mapJoin(q.order, function (it) {
      var expr = toExpression(it.expression);
      return !it.descending ? expr : 'DESC(' + expr + ')';
    }) + '\n';
  if (q.group)
    query += 'GROUP BY ' + mapJoin(q.group, function (it) {
      return isString(it.expression) ? it.expression : '(' + toExpression(it.expression) + ')';
    }) + '\n';
  if (q.having)
    query += 'HAVING (' + mapJoin(q.having, toExpression) + ')\n';

  if (q.offset)
    query += 'OFFSET ' + q.offset + '\n';
  if (q.limit)
    query += 'LIMIT ' + q.limit + '\n';
  return query;
}

// Converts the parsed SPARQL pattern into a SPARQL pattern
function toPattern(pattern) {
  var type = pattern.type || (pattern instanceof Array) && 'array' ||
             (pattern.subject && pattern.predicate && pattern.object ? 'triple' : '');
  if (!(type in patterns))
    throw new Error('Unknown entry type: ' + type);
  return patterns[type](pattern);
}
var patterns = {
  triple: function (t) {
    return toEntity(t.subject) + ' ' + toEntity(t.predicate) + ' ' + toEntity(t.object) + '.\n';
  },
  array: function (items) {
    return mapJoin(items, toPattern, '');
  },
  bgp: function (bgp) {
    return patterns.array(bgp.triples);
  },
  graph: function (graph) {
    return 'GRAPH ' + toEntity(graph.name) + ' ' + patterns.group(graph);
  },
  group: function (group) {
    return '{\n' + patterns.array(group.patterns || group.triples) + '}\n';
  },
  query: function (query) {
    return '{\n' + toQuery(query) + '}\n';
  },
  filter: function (filter) {
    return 'FILTER(' + toExpression(filter.expression) + ')\n';
  },
  bind: function (bind) {
    return 'BIND(' + toExpression(bind.expression) + ' AS ' + bind.variable + ')\n';
  },
  optional: function (optional) {
    return 'OPTIONAL ' + patterns.group(optional);
  },
  union: function (union) {
    return '{\n' + mapJoin(union.patterns, toPattern, '\n} UNION {\n') + '}\n';
  },
  minus: function (minus) {
    return 'MINUS ' + patterns.group(minus);
  },
  values: function (valuesList) {
    // Gather unique keys
    var keys = Object.keys(valuesList.values.reduce(function (keyHash, values) {
      for (var key in values) keyHash[key] = true;
      return keyHash;
    }, {}));
    // Create value rows
    return 'VALUES (' + keys.join(' ') + ') {\n' +
      mapJoin(valuesList.values, function (values) {
        return '(' + mapJoin(keys, function (key) {
          return values[key] !== undefined ? toEntity(values[key]) : 'UNDEF';
        }) + ')';
      }, '\n') + '}\n';
  },
  service: function (service) {
    return 'SERVICE ' + (service.silent ? 'SILENT ' : '') + toEntity(service.name) + ' ' +
           patterns.group(service);
  },
};

// Converts the parsed expression object into a SPARQL expression
function toExpression(expr) {
  if (isString(expr))
    return toEntity(expr);

  switch (expr.type.toLowerCase()) {
    case 'aggregate':
      return expr.aggregation.toUpperCase() + '(' + toExpression(expr.expression) + ')';
    case 'functioncall':
      return toEntity(expr.function) + '(' + mapJoin(expr.args, toExpression, ', ') + ')';
    case 'operation':
      var operator = expr.operator.toUpperCase(), args = expr.args || [], arg = args[0];
      switch (expr.operator.toLowerCase()) {
      // Infix operators
      case '<':
      case '>':
      case '>=':
      case '<=':
      case '&&':
      case '||':
      case '=':
      case '!=':
      case '+':
      case '-':
      case '*':
      case '/':
        return (isString(arg) ?  toEntity(arg) : '(' + toExpression(arg) + ')') + ' ' + operator + ' ' +
               (isString(arg = args[1]) ? toEntity(arg) : '(' + toExpression(arg) + ')');
      // Unary operators
      case '!':
        return '!' + toExpression(arg);
      // IN and NOT IN
      case 'notin':
        operator = 'NOT IN';
      case 'in':
        return toExpression(arg) + ' ' + operator +
               '(' + (isString(arg = args[1]) ? arg : mapJoin(arg, toExpression, ', ')) + ')';
      // EXISTS and NOT EXISTS
      case 'notexists':
        operator = 'NOT EXISTS';
      case 'exists':
        return operator + ' { ' + toPattern(args) + ' }';
      // Other expressions
      default:
        return operator + ' (' + mapJoin(args, toExpression, ', ') + ')';
      }
    default:
      throw new Error('Unknown expression type: ' + expr.type);
  }
}

// Converts the parsed entity (or property path) into a SPARQL entity
function toEntity(value) {
  // regular entity
  if (isString(value)) {
    switch (value[0]) {
    // variable, * selector, or blank node
    case '?':
    case '$':
    case '*':
    case '_':
      return value;
    // literal
    case '"':
      // TODO: correct escaping
      var match = value.match(/^"(.*)"(?:(@.+)|\^\^(.+))?$/) || ['', ''];
      value = '"' + match[1].replace(/([\\"])/g, '\\$1') + '"';
      if (match[2]) value += match[2];
      else if (match[3]) value += '^^<' + match[3] + '>';
      return value;
    // IRI
    default:
      return '<' + value + '>';
    }
  }
  // property path
  else {
    var items = value.items.map(toEntity), path = value.pathType;
    switch (path) {
    // prefix operator
    case '^':
    case '!':
      return path + items[0];
    // postfix operator
    case '*':
    case '+':
    case '?':
      return items[0] + path;
    // infix operator
    default:
      return '(' + items.join(path) + ')';
    }
  }
}

// Converts the parsed update object into a SPARQL update clause
function toUpdate(update) {
  switch (update.type || update.updateType) {
  case 'load':
    return 'LOAD' + (update.source ? ' ' + toEntity(update.source) : '') +
           (update.destination ? ' INTO GRAPH ' + toEntity(update.destination) : '');
  case 'insert':
    return 'INSERT DATA {\n'  + patterns.array(update.insert) + '}';
  case 'delete':
    return 'DELETE DATA {\n'  + patterns.array(update.delete) + '}';
  case 'deletewhere':
    return 'DELETE WHERE {\n' + patterns.array(update.delete) + '}';
  case 'insertdelete':
    return (update.graph ? 'WITH ' + toEntity(update.graph) + '\n' : '') +
           (update.delete.length ? 'DELETE {\n' + patterns.array(update.delete) + '}\n' : '') +
           (update.insert.length ? 'INSERT {\n' + patterns.array(update.insert) + '}\n' : '') +
           'WHERE {\n' + patterns.array(update.where) + '}';
  case 'add':
  case 'copy':
  case 'move':
    return update.type.toUpperCase() + (update.source.default ? ' DEFAULT ' : ' ') +
           'TO ' + toEntity(update.destination.name);
  default:
    throw new Error('Unknown update query type: ' + update.type);
  }
}

// Checks whether the object is a string
function isString(object) { return typeof object === 'string'; }

// Maps the array with the given function, and joins the results using the separator
function mapJoin(array, func, separator) { return array.map(func).join(separator || ' '); }
<commit_msg>Correct literal escaping.<commit_after>module.exports = function SparqlGenerator() { return { stringify: toQuery }; };

// Converts the parsed query object into a SPARQL query
function toQuery(q) {
  var query = '';
  for (var key in q.prefixes)
    query += 'PREFIX ' + key + ': <' + q.prefixes[key] + '>\n';

  if (q.queryType)
    query += q.queryType.toUpperCase() + ' ';
  if (q.reduced)
    query += 'REDUCED ';
  if (q.distinct)
    query += 'DISTINCT ';

  if (q.variables)
    query += mapJoin(q.variables, function (variable) {
      return isString(variable) ? toEntity(variable) :
             '(' + toExpression(variable.expression) + ' AS ' + variable.variable + ')';
    }) + ' ';
  else if (q.template)
    query += '{\n' + toPattern(q.template) + '}\n';

  if (q.from)
    query += mapJoin(q.from.default || [], function (g) { return 'FROM ' + toEntity(g) + '\n'; }, '') +
             mapJoin(q.from.named || [], function (g) { return 'FROM NAMED ' + toEntity(g) + '\n'; }, '');
  if (q.where)
    query += 'WHERE {\n' + toPattern(q.where) + '}\n';

  if (q.updates)
    query += mapJoin(q.updates, toUpdate, '; ');
  else if (q.values)
    query += patterns.values(q);

  if (q.order)
    query += 'ORDER BY ' + mapJoin(q.order, function (it) {
      var expr = toExpression(it.expression);
      return !it.descending ? expr : 'DESC(' + expr + ')';
    }) + '\n';
  if (q.group)
    query += 'GROUP BY ' + mapJoin(q.group, function (it) {
      return isString(it.expression) ? it.expression : '(' + toExpression(it.expression) + ')';
    }) + '\n';
  if (q.having)
    query += 'HAVING (' + mapJoin(q.having, toExpression) + ')\n';

  if (q.offset)
    query += 'OFFSET ' + q.offset + '\n';
  if (q.limit)
    query += 'LIMIT ' + q.limit + '\n';
  return query;
}

// Converts the parsed SPARQL pattern into a SPARQL pattern
function toPattern(pattern) {
  var type = pattern.type || (pattern instanceof Array) && 'array' ||
             (pattern.subject && pattern.predicate && pattern.object ? 'triple' : '');
  if (!(type in patterns))
    throw new Error('Unknown entry type: ' + type);
  return patterns[type](pattern);
}
var patterns = {
  triple: function (t) {
    return toEntity(t.subject) + ' ' + toEntity(t.predicate) + ' ' + toEntity(t.object) + '.\n';
  },
  array: function (items) {
    return mapJoin(items, toPattern, '');
  },
  bgp: function (bgp) {
    return patterns.array(bgp.triples);
  },
  graph: function (graph) {
    return 'GRAPH ' + toEntity(graph.name) + ' ' + patterns.group(graph);
  },
  group: function (group) {
    return '{\n' + patterns.array(group.patterns || group.triples) + '}\n';
  },
  query: function (query) {
    return '{\n' + toQuery(query) + '}\n';
  },
  filter: function (filter) {
    return 'FILTER(' + toExpression(filter.expression) + ')\n';
  },
  bind: function (bind) {
    return 'BIND(' + toExpression(bind.expression) + ' AS ' + bind.variable + ')\n';
  },
  optional: function (optional) {
    return 'OPTIONAL ' + patterns.group(optional);
  },
  union: function (union) {
    return '{\n' + mapJoin(union.patterns, toPattern, '\n} UNION {\n') + '}\n';
  },
  minus: function (minus) {
    return 'MINUS ' + patterns.group(minus);
  },
  values: function (valuesList) {
    // Gather unique keys
    var keys = Object.keys(valuesList.values.reduce(function (keyHash, values) {
      for (var key in values) keyHash[key] = true;
      return keyHash;
    }, {}));
    // Create value rows
    return 'VALUES (' + keys.join(' ') + ') {\n' +
      mapJoin(valuesList.values, function (values) {
        return '(' + mapJoin(keys, function (key) {
          return values[key] !== undefined ? toEntity(values[key]) : 'UNDEF';
        }) + ')';
      }, '\n') + '}\n';
  },
  service: function (service) {
    return 'SERVICE ' + (service.silent ? 'SILENT ' : '') + toEntity(service.name) + ' ' +
           patterns.group(service);
  },
};

// Converts the parsed expression object into a SPARQL expression
function toExpression(expr) {
  if (isString(expr))
    return toEntity(expr);

  switch (expr.type.toLowerCase()) {
    case 'aggregate':
      return expr.aggregation.toUpperCase() + '(' + toExpression(expr.expression) + ')';
    case 'functioncall':
      return toEntity(expr.function) + '(' + mapJoin(expr.args, toExpression, ', ') + ')';
    case 'operation':
      var operator = expr.operator.toUpperCase(), args = expr.args || [], arg = args[0];
      switch (expr.operator.toLowerCase()) {
      // Infix operators
      case '<':
      case '>':
      case '>=':
      case '<=':
      case '&&':
      case '||':
      case '=':
      case '!=':
      case '+':
      case '-':
      case '*':
      case '/':
        return (isString(arg) ?  toEntity(arg) : '(' + toExpression(arg) + ')') + ' ' + operator + ' ' +
               (isString(arg = args[1]) ? toEntity(arg) : '(' + toExpression(arg) + ')');
      // Unary operators
      case '!':
        return '!' + toExpression(arg);
      // IN and NOT IN
      case 'notin':
        operator = 'NOT IN';
      case 'in':
        return toExpression(arg) + ' ' + operator +
               '(' + (isString(arg = args[1]) ? arg : mapJoin(arg, toExpression, ', ')) + ')';
      // EXISTS and NOT EXISTS
      case 'notexists':
        operator = 'NOT EXISTS';
      case 'exists':
        return operator + ' { ' + toPattern(args) + ' }';
      // Other expressions
      default:
        return operator + ' (' + mapJoin(args, toExpression, ', ') + ')';
      }
    default:
      throw new Error('Unknown expression type: ' + expr.type);
  }
}

// Converts the parsed entity (or property path) into a SPARQL entity
function toEntity(value) {
  // regular entity
  if (isString(value)) {
    switch (value[0]) {
    // variable, * selector, or blank node
    case '?':
    case '$':
    case '*':
    case '_':
      return value;
    // literal
    case '"':
      var match = value.match(/^"(.*)"(?:(@.+)|\^\^(.+))?$/) || ['', ''];
      value = '"' + match[1].replace(escape, escapeReplacer) + '"';
      if (match[2]) value += match[2];
      else if (match[3]) value += '^^<' + match[3] + '>';
      return value;
    // IRI
    default:
      return '<' + value + '>';
    }
  }
  // property path
  else {
    var items = value.items.map(toEntity), path = value.pathType;
    switch (path) {
    // prefix operator
    case '^':
    case '!':
      return path + items[0];
    // postfix operator
    case '*':
    case '+':
    case '?':
      return items[0] + path;
    // infix operator
    default:
      return '(' + items.join(path) + ')';
    }
  }
}
var escape = /["\\\t\n\r\b\f]/g,
    escapeReplacer = function (c) { return escapeReplacements[c]; },
    escapeReplacements = { '\\': '\\\\', '"': '\\"', '\t': '\\t',
                           '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f' };

// Converts the parsed update object into a SPARQL update clause
function toUpdate(update) {
  switch (update.type || update.updateType) {
  case 'load':
    return 'LOAD' + (update.source ? ' ' + toEntity(update.source) : '') +
           (update.destination ? ' INTO GRAPH ' + toEntity(update.destination) : '');
  case 'insert':
    return 'INSERT DATA {\n'  + patterns.array(update.insert) + '}';
  case 'delete':
    return 'DELETE DATA {\n'  + patterns.array(update.delete) + '}';
  case 'deletewhere':
    return 'DELETE WHERE {\n' + patterns.array(update.delete) + '}';
  case 'insertdelete':
    return (update.graph ? 'WITH ' + toEntity(update.graph) + '\n' : '') +
           (update.delete.length ? 'DELETE {\n' + patterns.array(update.delete) + '}\n' : '') +
           (update.insert.length ? 'INSERT {\n' + patterns.array(update.insert) + '}\n' : '') +
           'WHERE {\n' + patterns.array(update.where) + '}';
  case 'add':
  case 'copy':
  case 'move':
    return update.type.toUpperCase() + (update.source.default ? ' DEFAULT ' : ' ') +
           'TO ' + toEntity(update.destination.name);
  default:
    throw new Error('Unknown update query type: ' + update.type);
  }
}

// Checks whether the object is a string
function isString(object) { return typeof object === 'string'; }

// Maps the array with the given function, and joins the results using the separator
function mapJoin(array, func, separator) { return array.map(func).join(separator || ' '); }
<|endoftext|>


<commit_before>'use strict'

import {Component}  from 'react'
import {Link} from 'react-router'
import Countdown from 'react-countdown-now';
import CountdownTimer from './CountdownTimer'

const registrationEndDate = "Wednesday, November 15 2017 15:00:00 EST"

class Alert extends Component {

  constructor(props) {
    super(props)
  }

  render() {
    return (

          <button type="button" className="close close-primary d-none d-sm-block" data-dismiss="alert" aria-label="Close">
            ×
          </button>
          The Blockstack Token Main Sale has started - Thu, Nov 16 at 12pm EST

    )
  }

}

export default Alert
<commit_msg>Update sale start time.<commit_after>'use strict'

import {Component}  from 'react'
import {Link} from 'react-router'
import Countdown from 'react-countdown-now';
import CountdownTimer from './CountdownTimer'

const registrationEndDate = "Wednesday, November 15 2017 15:00:00 EST"

class Alert extends Component {

  constructor(props) {
    super(props)
  }

  render() {
    return (

          <button type="button" className="close close-primary d-none d-sm-block" data-dismiss="alert" aria-label="Close">
            ×
          </button>
          Blockstack Token Main Sale Starts - Thu, Nov 16 at 12pm EST

    )
  }

}

export default Alert
<|endoftext|>


<commit_before>document.addEventListener( 'turbolinks:load', function() {
    flexdatalistInit();
});

function flexdatalistInit( el = $('body') ) {

    el.find('input.flexdatalist').flexdatalist();

    el.find('input.flexdatalist[data-units]').on( 'change:flexdatalist', function( event, set, options ) {
        var unitsEl = $(this).data('units');
            unitsUrl = 'app/quantities/' + set.value + '/units_of_measurement.json?locale=' + $('p#lang').text();
        $(unitsEl).data( 'data', unitsUrl )
        $(unitsEl).flexdatalist()
        $(unitsEl).attr( 'disabled', 'false' );
    });

};
<commit_msg>testing<commit_after>document.addEventListener( 'turbolinks:load', function() {
    flexdatalistInit();
});

function flexdatalistInit( el = $('body') ) {

    el.find('input.flexdatalist').flexdatalist();

    el.find('input.flexdatalist[data-units]').on( 'change:flexdatalist', function( event, set, options ) {
        var unitsEl = $(this).data('units'),
            unitsUrl = 'app/quantities/' + set.value + '/units_of_measurement.json?locale=' + $('p#lang').text();
        $(unitsEl).data( 'data', unitsUrl );
        $(unitsEl).flexdatalist();
        $(unitsEl).attr( 'disabled', 'false' );
    });

};
<|endoftext|>


<commit_before>import React, { useState, useEffect, useMemo } from 'react'
import { useSelector } from 'react-redux'
import { ipcRenderer, clipboard } from 'electron'
import OpengraphFetcher from 'opengraph-fetcher'

import tinymce from 'tinymce'
import { Editor } from '@tinymce/tinymce-react'

import 'tinymce-plugin-opengraph'
import 'tinymce-plugin-codeblock'
import './plugins/google-photos'
import './plugins/file-upload'

import GooglePhotosDialog from '../plugins/google-photos/GooglePhotosDialog'
import { makeThumbnail } from '../../../modules/ThumbnailHelper'

export default function TinymceEditor({ value, onImageHandler, onOpenFile, onChange }) {
  const currentAuth = useSelector(state => state.currentAuth)
  const currentBlog = useSelector(state => state.currentBlog)
  const [openGooglePhotos, setOpenGooglePhotos] = useState(false)
  const imageUploadEnabled = useMemo(() => currentAuth.provider == 'tistory', [currentAuth])

  const tinymcePlugins = useMemo(() => {
    if (imageUploadEnabled) {
      return 'link table textcolor hr lists paste codeblock opengraph google-photos file-upload autoresize searchreplace'
    } else {
      return 'link table textcolor hr lists paste codeblock opengraph autoresize searchreplace'
    }
  }, [imageUploadEnabled])
  const tinymceToolbar = useMemo(() => {
    if (imageUploadEnabled) {
      return 'formatselect bold italic link inlinecode | alignleft aligncenter alignright | bullist numlist | blockquote codeblock google-photos file-upload opengraph hr removeformat'
    } else {
      return 'formatselect bold italic link inlinecode | alignleft aligncenter alignright | bullist numlist | blockquote codeblock opengraph hr removeformat'
    }
  }, [imageUploadEnabled])

  function handleFinishUploadFile(e, fileUrl) {
		console.log("finishUploadFile", fileUrl)
		const editor = tinymce.activeEditor
		editor.execCommand('mceInsertContent', false, '');
		let $img = editor.$('#__photos_new')
		$img.removeAttr('id')
		$img.on('load', e => {
			editor.nodeChanged()
			$img.off('load')
		})
  }

  function handlePaste(e) {
    if (!imageUploadEnabled) {
      return
    }

		let image = clipboard.readImage()
		if (!image.isEmpty()) {
			ipcRenderer.send("add-clipboard-image", currentAuth.uuid, currentBlog.name)
		}
	}

	function handleDrop(e) {
    if (e.dataTransfer && e.dataTransfer.files) {
      onImageHandler(Array.prototype.slice.call(e.dataTransfer.files))
    }
	}

	function handleFetchOpengraph(url, callback) {
		OpengraphFetcher.fetch(url, og => {
      og.image = makeThumbnail('S200x200', og.image)
      callback(og)
    })
  }

  function handleInsertImage(url, filename) {
    if (!imageUploadEnabled) {
      return
    }
    ipcRenderer.send("add-image-url", currentAuth.uuid, currentBlog.name, url, filename)
  }

  function handleToggleGooglePhotos() {
    setOpenGooglePhotos(!openGooglePhotos)
  }

  useEffect(() => {
    ipcRenderer.on("finish-add-file", handleFinishUploadFile)

    return () => {
      ipcRenderer.removeListener("finish-add-file", handleFinishUploadFile)
    }
  }, [])

  return (
    <>
      <Editor
        id='tinymce'
        className='content'
        onChange={e => onChange(e.target.getContent())}
        init={{
          plugins: tinymcePlugins,
          toolbar: tinymceToolbar,
          branding: false,
          statusbar: false,
          menubar: false,
          paste_data_images: true,
          valid_children : 'p[s|strike|span|b|u|i|a|#text|br|code|em|sup|sub|q],-h2[img|div|figure|b],figcaption[#text],figure[img|figcaption|br|a|div|span|p|iframe],-span[img],+a[div],-li[blockquote|h2|h3]',
          extended_valid_elements: 'span/font[style],i/em,b/strong,iframe[mapdata|src|id|width|height|frameborder|scrolling|data-*|allowfullscreen],script[type|src]',
          text_inline_elements: 'span strong b em i font s u var cite dfn code mark q sup sub samp',
          width: 600,
          min_height: 500,
          file_picker_type: 'image',
          block_formats: '문단=p;주제=h2;소주제=h3',
          body_class: 'content',
          content_css: [
            '../src/css/content.css',
            '../src/css/tistory-content.css',
            'https://fonts.googleapis.com/css?family=Nanum+Gothic'
          ],
          paste_preprocess: (plugin, args) => {
            let image = clipboard.readImage()
            if (!image.isEmpty()) {
              args.preventDefault()
            }
          },
          codeblock: {
            highlightStyle: '../node_modules/highlight.js/styles/atom-one-dark.css'
          },
          opengraph: {
            fetch_handler: handleFetchOpengraph
          },
          google_photos: {
            open_handler: handleToggleGooglePhotos
          },
          open_file_handler: onOpenFile,
          init_instance_callback: (editor) => {
            editor.ui.registry.addIcon('media', 'M')
            editor.on("paste", handlePaste)
            editor.on("drop", handleDrop)
            editor.setContent(value)
          }
        }}
      />

      <GooglePhotosDialog
        open={openGooglePhotos}
        onClose={handleToggleGooglePhotos}
        onSelectImage={handleInsertImage}
      />
    </>
  )
}
<commit_msg>파일을 drop한 경우 blob:file는 첨부되지 않도록 수정<commit_after>import React, { useState, useEffect, useMemo } from 'react'
import { useSelector } from 'react-redux'
import { ipcRenderer, clipboard } from 'electron'
import OpengraphFetcher from 'opengraph-fetcher'

import tinymce from 'tinymce'
import { Editor } from '@tinymce/tinymce-react'

import 'tinymce-plugin-opengraph'
import 'tinymce-plugin-codeblock'
import './plugins/google-photos'
import './plugins/file-upload'

import GooglePhotosDialog from '../plugins/google-photos/GooglePhotosDialog'
import { makeThumbnail } from '../../../modules/ThumbnailHelper'

export default function TinymceEditor({ value, onImageHandler, onOpenFile, onChange }) {
  const currentAuth = useSelector(state => state.currentAuth)
  const currentBlog = useSelector(state => state.currentBlog)
  const [openGooglePhotos, setOpenGooglePhotos] = useState(false)
  const imageUploadEnabled = useMemo(() => currentAuth.provider == 'tistory', [currentAuth])

  const tinymcePlugins = useMemo(() => {
    if (imageUploadEnabled) {
      return 'link table textcolor hr lists paste codeblock opengraph google-photos file-upload autoresize searchreplace'
    } else {
      return 'link table textcolor hr lists paste codeblock opengraph autoresize searchreplace'
    }
  }, [imageUploadEnabled])
  const tinymceToolbar = useMemo(() => {
    if (imageUploadEnabled) {
      return 'formatselect bold italic link inlinecode | alignleft aligncenter alignright | bullist numlist | blockquote codeblock google-photos file-upload opengraph hr removeformat'
    } else {
      return 'formatselect bold italic link inlinecode | alignleft aligncenter alignright | bullist numlist | blockquote codeblock opengraph hr removeformat'
    }
  }, [imageUploadEnabled])

  function handleFinishUploadFile(e, fileUrl) {
		console.log("finishUploadFile", fileUrl)
		const editor = tinymce.activeEditor
		editor.execCommand('mceInsertContent', false, '');
		let $img = editor.$('#__photos_new')
		$img.removeAttr('id')
		$img.on('load', e => {
			editor.nodeChanged()
			$img.off('load')
		})
  }

  function handlePastePreprocess(plugin, args) {
    let image = clipboard.readImage()
		if (!image.isEmpty()) {
      if (imageUploadEnabled) {
        ipcRenderer.send("add-clipboard-image", currentAuth.uuid, currentBlog.name)
      }
      args.preventDefault()
    } else {
      if (args.content.indexOf(" 0) {
        args.preventDefault()
      }
    }
  }

	function handleDrop(e) {
    if (e.dataTransfer && e.dataTransfer.files) {
      onImageHandler(Array.prototype.slice.call(e.dataTransfer.files))
    }
	}

	function handleFetchOpengraph(url, callback) {
		OpengraphFetcher.fetch(url, og => {
      og.image = makeThumbnail('S200x200', og.image)
      callback(og)
    })
  }

  function handleInsertImage(url, filename) {
    if (!imageUploadEnabled) {
      return
    }
    ipcRenderer.send("add-image-url", currentAuth.uuid, currentBlog.name, url, filename)
  }

  function handleToggleGooglePhotos() {
    setOpenGooglePhotos(!openGooglePhotos)
  }

  useEffect(() => {
    ipcRenderer.on("finish-add-file", handleFinishUploadFile)

    return () => {
      ipcRenderer.removeListener("finish-add-file", handleFinishUploadFile)
    }
  }, [])

  return (
    <>
      <Editor
        id='tinymce'
        className='content'
        onChange={e => onChange(e.target.getContent())}
        init={{
          plugins: tinymcePlugins,
          toolbar: tinymceToolbar,
          branding: false,
          statusbar: false,
          menubar: false,
          paste_data_images: true,
          valid_children : 'p[s|strike|span|b|u|i|a|#text|br|code|em|sup|sub|q],-h2[img|div|figure|b],figcaption[#text],figure[img|figcaption|br|a|div|span|p|iframe],-span[img],+a[div],-li[blockquote|h2|h3]',
          extended_valid_elements: 'span/font[style],i/em,b/strong,iframe[mapdata|src|id|width|height|frameborder|scrolling|data-*|allowfullscreen],script[type|src]',
          text_inline_elements: 'span strong b em i font s u var cite dfn code mark q sup sub samp',
          width: 600,
          min_height: 500,
          file_picker_type: 'image',
          block_formats: '문단=p;주제=h2;소주제=h3',
          body_class: 'content',
          content_css: [
            '../src/css/content.css',
            '../src/css/tistory-content.css',
            'https://fonts.googleapis.com/css?family=Nanum+Gothic'
          ],
          paste_preprocess: handlePastePreprocess,
          codeblock: {
            highlightStyle: '../node_modules/highlight.js/styles/atom-one-dark.css'
          },
          opengraph: {
            fetch_handler: handleFetchOpengraph
          },
          google_photos: {
            open_handler: handleToggleGooglePhotos
          },
          open_file_handler: onOpenFile,
          init_instance_callback: (editor) => {
            editor.ui.registry.addIcon('media', 'M')
            editor.on("drop", handleDrop)
            editor.setContent(value)
          }
        }}
      />

      <GooglePhotosDialog
        open={openGooglePhotos}
        onClose={handleToggleGooglePhotos}
        onSelectImage={handleInsertImage}
      />
    </>
  )
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { ThemeProvider } from 'styled-components';
import { mount as enzymeMount } from 'enzyme';
import { simulate, assertStyleMatch } from '../../__spec_helper__/test-utils';
import theme from '../../style/themes/small';
import { ActionPopover, ActionPopoverDivider, ActionPopoverItem } from './index';
import { MenuButton, Menu } from './action-popover.style';
import Icon from '../icon';

jest.useFakeTimers();

describe('ActionPopover', () => {
  const container = { current: null };
  const wrapper = { current: null };

  const mount = (jsx) => {
    wrapper.current = enzymeMount(jsx, { attachTo: container.current });
  };

  const DOM = (jsx) => {
    ReactDOM.render(jsx, container.current);
  };

  const onClick = jest.fn();
  const onOpen = jest.fn();
  const onClose = jest.fn();
  const onClickWrapper = arg => () => onClick(arg);

  function render(props = {}, renderer = mount) {
    const defaultProps = {
      children: [
        <ActionPopoverItem icon='email' { ...{ onClick: onClickWrapper('email') } }>Email Invoice</ActionPopoverItem>,
        <ActionPopoverItem icon='print' { ...{ onClick: onClickWrapper('print') } }>Print Invoice</ActionPopoverItem>,
        <ActionPopoverItem
          icon='pdf' { ...{ onClick: onClickWrapper('pdf') } }
          disabled
        >Download PDF
        </ActionPopoverItem>,
        <ActionPopoverDivider />,
        <ActionPopoverItem icon='csv' { ...{ onClick: onClickWrapper('csv') } }>Download CSV</ActionPopoverItem>
      ],
      onOpen,
      onClose,
      ...props
    };

    renderer(
      <ThemeProvider { ...{ theme } }>
        <React.Fragment>
          <input id='before' />
          <ActionPopover { ...defaultProps } { ...props } />
          <input id='after' />
        </React.Fragment>
      </ThemeProvider>
    );
  }

  function getElements() {
    const cw = wrapper.current;
    if (cw) {
      return {
        items: cw.find(ActionPopoverItem),
        menubutton: cw.find(MenuButton),
        menu: cw.find(Menu)
      };
    }
    const button = document.querySelector('div[id^=ActionPopoverButton]');
    const icon = button.querySelector('span[data-component=icon');
    return { button, icon };
  }

  beforeEach(() => {
    container.current = document.createElement('div');
    document.body.appendChild(container.current);
    onClick.mockReset();
    onOpen.mockReset();
    onClose.mockReset();
  });

  afterEach(() => {
    document.body.removeChild(container.current);
    container.current = null;
    if (wrapper.current) {
      wrapper.current.unmount();
      wrapper.current = null;
    }
  });

  it('renders in ReactDOM', () => {
    render(null, DOM);
  });

  it('displays the horizontal elipsis icon as the menu button', () => {
    render();
    const { menubutton } = getElements();
    const icon = menubutton.find(Icon).first();
    expect(icon.prop('type')).toBe('ellipsis_vertical');
  });

  it('has a default aria-label', () => {
    render();
    const { menubutton } = getElements();
    expect(menubutton.prop('aria-label')).toBe('actions');
  });

  it('renders with the menu closed by default', () => {
    render();
    const { menu } = getElements();
    assertStyleMatch({
      display: 'none'
    }, menu);
    expect(onOpen).not.toHaveBeenCalledTimes(1);
    expect(onClose).not.toHaveBeenCalledTimes(1);
  });

  it('renders when there is no onOpen, onClose provided', () => {
    expect(() => {
      render({ onOpen: undefined, onClose: undefined });
      const { menubutton } = getElements();
      menubutton.simulate('click');
      menubutton.simulate('click');
    }).not.toThrow();
  });

  describe.each([
    ['Click handlers', 'Clicking', item => item.simulate('click')],
    ['Keypress handlers', 'Pressing Enter', item => simulate.keydown.pressEnter(item)]
  ])('%s', (group, prefix, mutator) => {
    beforeEach(() => {
      render();
      const { menubutton } = getElements();

      menubutton.simulate('click');
    });
    describe('MenuItem', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(1));
      });
      it(`${prefix} calls the onClick handler`, () => {
        expect(onClick).toHaveBeenCalledWith('print');
      });

      it(`${prefix} closes the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
      });

      it(`${prefix} focuses the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).toBeFocused();
      });
    });
    describe('MenuItem (disabled)', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(2));
      });
      it(`${prefix} does not call the onClick handler`, () => {
        expect(onClick).not.toHaveBeenCalled();
      });

      it(`${prefix} does not close the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onClose).not.toHaveBeenCalled();
      });

      it(`${prefix} does not focus the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).not.toBeFocused();
      });
    });
  });

  describe('Click handlers', () => {
    describe('MenuButton', () => {
      beforeEach(() => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');
      });
      it('Clicking opens the menu', () => {
        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onOpen).toHaveBeenCalledTimes(1);
      });

      it('Clicking focuses the first element', () => {
        const { items } = getElements();

        expect(items.at(0)).toBeFocused();
      });

      it('Clicking closes the menu', () => {
        const { menubutton } = getElements();
        act(() => {
          menubutton.simulate('click');
        });

        act(() => {
          wrapper.current.update();
        });
        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
      });
    });

    describe('document', () => {
      it('Clicking on the component does not close the menu using the document listener', () => {
        // This test doesn't really replicate the functionality but it is useful to check that the document listener
        // is filtering based on what element is clicked
        // In a normal situation the React SynteticEvent will bubble and trigger a handler which will close the menu
        // This triggers a DOMEvent bypassing the SynteticEvent listeners
        render();
        const { menubutton, items } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: items.first().getDOMNode()
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(0);
      });

      it('Clicking elsewhere on the document closes the menu', () => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: document.body
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
      });
    });
  });

  describe('Keypress handlers', () => {
    describe('MenuButton', () => {
      it.each(
        ['DownArrow', 'Space', 'Enter', 'UpArrow']
      )('Pressing %s key opens the menu', (key) => {
        render();
        const { menubutton } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onOpen).toHaveBeenCalledTimes(1);
      });

      it.each(
        ['DownArrow', 'Space', 'Enter']
      )('Pressing %s key selects the first item', (key) => {
        render();
        const { menubutton, items } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        expect(items.first()).toBeFocused();
      });

      it('Pressing UpArrow selects the last item', () => {
        render();
        const { menubutton, items } = getElements();

        simulate.keydown.pressUpArrow(menubutton);

        expect(items.last()).toBeFocused();
      });
    });

    describe('MenuItem', () => {
      it.each([
        ['Tab', item => simulate.keydown.pressTab(item)],
        ['Shift+Tab', (item) => {
          simulate.keydown.pressTab(item, { shiftKey: true });

          act(() => {
            jest.runAllTimers();
          });

          act(() => {
            wrapper.current.update();
          });
        }],
        ['Escape', item => simulate.keydown.pressEscape(item)]
      ])('Pressing %s key closes the menu', (key, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items.first());

        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
        // FIXME: Test pressing Tab moves focus to the next element
        // FIXME: Test pressing Shift+Tab moves focus to the previous element
        // It's not possible to test this in enzyme because JSDOM does not support user events. It's also not
        // possible to test it in cypress because cypress uses syntetic events. We should add a test for this when
        // support for native events is implemented in cypress https://github.com/cypress-io/cypress/issues/311
      });

      it('Pressing Escape focuses the MenuButton', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressEscape(items.first());

        expect(menubutton).toBeFocused();
      });

      it.each([
        ['Down', 'next', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressDownArrow(items.at(1));
          // we're checking that we can focus the disabled item, this is intentional behaviour
          expect(items.at(2)).toBeFocused();
          expect(items.at(2).prop('disabled')).toBe(true);
          expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

          simulate.keydown.pressDownArrow(items.at(2));
          expect(items.at(3)).toBeFocused();
        }],
        ['Down', 'first', 'if the focus on the last item', (items) => {
          simulate.keydown.pressEnd(items.first());
          expect(items.last()).toBeFocused();

          simulate.keydown.pressDownArrow(items.last());
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'previous', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          simulate.keydown.pressDownArrow(items.at(1));
          simulate.keydown.pressDownArrow(items.at(2));

          simulate.keydown.pressUpArrow(items.at(3));
          expect(items.at(2)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(2));
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(1));
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'last', 'if the focus is on the first item', (items) => {
          simulate.keydown.pressUpArrow(items.first());

          expect(items.last()).toBeFocused();
        }],
        ['Home', 'first', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());

          simulate.keydown.pressHome(items.at(1));

          expect(items.first()).toBeFocused();
        }],
        ['End', 'first', '', (items) => {
          simulate.keydown.pressEnd(items.first());

          expect(items.last()).toBeFocused();
        }]
      ])('Pressing %s focuses the %s item %s', (key, direction, condition, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items);
      });

      it('Pressing Space does nothing', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressSpace(items.at(0));

        const { menu } = getElements();
        expect(items.at(0)).toBeFocused();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Pressing a-z selects the next item in the list starting with the letter that was pressed', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        // moves to first element starting with P
        simulate.keydown.pressP(items.first());
        expect(items.at(1)).toBeFocused();

        // moves to first element starting with D
        simulate.keydown.pressD(items.at(1));
        // we're checking that we can focus the disabled item, this is intentional behaviour
        expect(items.at(2)).toBeFocused();
        expect(items.at(2).prop('disabled')).toBe(true);
        expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

        // moves to next element starting with D
        simulate.keydown.pressD(items.at(2));
        expect(items.at(3)).toBeFocused();

        // moves to next element starting with D wrapping back to the first element
        simulate.keydown.pressD(items.at(3));
        expect(items.at(2)).toBeFocused();

        // does nothing when there are no matches
        simulate.keydown.pressZ(items.at(2));
        expect(items.at(2)).toBeFocused();

        // does nothing withen a number key is pressed
        simulate.keydown.press1(items.at(2));
        expect(items.at(2)).toBeFocused();
      });
    });
  });

  describe('styles', () => {
    it('renders the button with a white background when the menu is open', () => {
      render();

      const { menubutton } = getElements();

      expect(menubutton).not.toHaveStyleRule('background-color');

      simulate.keydown.pressDownArrow(menubutton);

      expect(getElements().menubutton).toHaveStyleRule('background-color', theme.colors.white);
    });
  });

  it('validates the children prop', () => {
    jest.spyOn(global.console, 'error').mockImplementation(() => {});
    ReactDOM.render(
      <ThemeProvider { ...{ theme } }>
        <ActionPopover>
invalid children
</ActionPopover>
      </ThemeProvider>, container.current
    );
    expect(console.error).toHaveBeenCalledWith('Warning: Failed prop type: `ActionPopover` only accepts children of'
    + ' type `ActionPopoverItem` and `ActionPopoverDivider`.\n    in ActionPopover');
    global.console.error.mockReset();
  });
});
<commit_msg>Added missing data attributes test<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { ThemeProvider } from 'styled-components';
import { mount as enzymeMount } from 'enzyme';
import { simulate, assertStyleMatch } from '../../__spec_helper__/test-utils';
import theme from '../../style/themes/small';
import { ActionPopover, ActionPopoverDivider, ActionPopoverItem } from './index';
import { MenuButton, Menu } from './action-popover.style';
import { rootTagTest } from '../../utils/helpers/tags/tags-specs';
import Icon from '../icon';

jest.useFakeTimers();

describe('ActionPopover', () => {
  const container = { current: null };
  const wrapper = { current: null };

  const mount = (jsx) => {
    wrapper.current = enzymeMount(jsx, { attachTo: container.current });
  };

  const DOM = (jsx) => {
    ReactDOM.render(jsx, container.current);
  };

  const onClick = jest.fn();
  const onOpen = jest.fn();
  const onClose = jest.fn();
  const onClickWrapper = arg => () => onClick(arg);

  function render(props = {}, renderer = mount) {
    const defaultProps = {
      children: [
        <ActionPopoverItem icon='email' { ...{ onClick: onClickWrapper('email') } }>Email Invoice</ActionPopoverItem>,
        <ActionPopoverItem icon='print' { ...{ onClick: onClickWrapper('print') } }>Print Invoice</ActionPopoverItem>,
        <ActionPopoverItem
          icon='pdf' { ...{ onClick: onClickWrapper('pdf') } }
          disabled
        >Download PDF
        </ActionPopoverItem>,
        <ActionPopoverDivider />,
        <ActionPopoverItem icon='csv' { ...{ onClick: onClickWrapper('csv') } }>Download CSV</ActionPopoverItem>
      ],
      onOpen,
      onClose,
      ...props
    };

    renderer(
      <ThemeProvider { ...{ theme } }>
        <React.Fragment>
          <input id='before' />
          <ActionPopover { ...defaultProps } { ...props } />
          <input id='after' />
        </React.Fragment>
      </ThemeProvider>
    );
  }

  function getElements() {
    const cw = wrapper.current;
    if (cw) {
      return {
        items: cw.find(ActionPopoverItem),
        menubutton: cw.find(MenuButton),
        menu: cw.find(Menu),
        divider: cw.find(ActionPopoverDivider)
      };
    }
    const button = document.querySelector('div[id^=ActionPopoverButton]');
    const icon = button.querySelector('span[data-component=icon');
    return { button, icon };
  }

  beforeEach(() => {
    container.current = document.createElement('div');
    document.body.appendChild(container.current);
    onClick.mockReset();
    onOpen.mockReset();
    onClose.mockReset();
  });

  afterEach(() => {
    document.body.removeChild(container.current);
    container.current = null;
    if (wrapper.current) {
      wrapper.current.unmount();
      wrapper.current = null;
    }
  });

  it('renders in ReactDOM', () => {
    render(null, DOM);
  });

  it('displays the horizontal elipsis icon as the menu button', () => {
    render();
    const { menubutton } = getElements();
    const icon = menubutton.find(Icon).first();
    expect(icon.prop('type')).toBe('ellipsis_vertical');
  });

  it('has proper data attributes applied to elements', () => {
    render();
    const { menubutton, menu, divider } = getElements();
    rootTagTest(menubutton, 'action-popover-button');
    rootTagTest(menu, 'action-popover');
    expect(divider.getDOMNode().getAttribute('data-element')).toBe('action-popover-divider');
  });

  it('has a default aria-label', () => {
    render();
    const { menubutton } = getElements();
    expect(menubutton.prop('aria-label')).toBe('actions');
  });

  it('renders with the menu closed by default', () => {
    render();
    const { menu } = getElements();
    assertStyleMatch({
      display: 'none'
    }, menu);
    expect(onOpen).not.toHaveBeenCalledTimes(1);
    expect(onClose).not.toHaveBeenCalledTimes(1);
  });

  it('renders when there is no onOpen, onClose provided', () => {
    expect(() => {
      render({ onOpen: undefined, onClose: undefined });
      const { menubutton } = getElements();
      menubutton.simulate('click');
      menubutton.simulate('click');
    }).not.toThrow();
  });

  describe.each([
    ['Click handlers', 'Clicking', item => item.simulate('click')],
    ['Keypress handlers', 'Pressing Enter', item => simulate.keydown.pressEnter(item)]
  ])('%s', (group, prefix, mutator) => {
    beforeEach(() => {
      render();
      const { menubutton } = getElements();

      menubutton.simulate('click');
    });
    describe('MenuItem', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(1));
      });
      it(`${prefix} calls the onClick handler`, () => {
        expect(onClick).toHaveBeenCalledWith('print');
      });

      it(`${prefix} closes the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
      });

      it(`${prefix} focuses the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).toBeFocused();
      });
    });
    describe('MenuItem (disabled)', () => {
      beforeEach(() => {
        const { items } = getElements();

        mutator(items.at(2));
      });
      it(`${prefix} does not call the onClick handler`, () => {
        expect(onClick).not.toHaveBeenCalled();
      });

      it(`${prefix} does not close the menu`, () => {
        const { menu } = getElements();

        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onClose).not.toHaveBeenCalled();
      });

      it(`${prefix} does not focus the Menubutton`, () => {
        const { menubutton } = getElements();

        expect(menubutton).not.toBeFocused();
      });
    });
  });

  describe('Click handlers', () => {
    describe('MenuButton', () => {
      beforeEach(() => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');
      });
      it('Clicking opens the menu', () => {
        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onOpen).toHaveBeenCalledTimes(1);
      });

      it('Clicking focuses the first element', () => {
        const { items } = getElements();

        expect(items.at(0)).toBeFocused();
      });

      it('Clicking closes the menu', () => {
        const { menubutton } = getElements();
        act(() => {
          menubutton.simulate('click');
        });

        act(() => {
          wrapper.current.update();
        });
        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
      });
    });

    describe('document', () => {
      it('Clicking on the component does not close the menu using the document listener', () => {
        // This test doesn't really replicate the functionality but it is useful to check that the document listener
        // is filtering based on what element is clicked
        // In a normal situation the React SynteticEvent will bubble and trigger a handler which will close the menu
        // This triggers a DOMEvent bypassing the SynteticEvent listeners
        render();
        const { menubutton, items } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: items.first().getDOMNode()
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(0);
      });

      it('Clicking elsewhere on the document closes the menu', () => {
        render();
        const { menubutton } = getElements();

        menubutton.simulate('click');

        act(() => {
          document.dispatchEvent(new CustomEvent('click', {
            detail: {
              enzymeTestingTarget: document.body
            }
          }));
        });

        act(() => {
          wrapper.current.update();
        });

        const { menu } = getElements();
        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
      });
    });
  });

  describe('Keypress handlers', () => {
    describe('MenuButton', () => {
      it.each(
        ['DownArrow', 'Space', 'Enter', 'UpArrow']
      )('Pressing %s key opens the menu', (key) => {
        render();
        const { menubutton } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        const { menu } = getElements();
        assertStyleMatch({
          display: 'block'
        }, menu);
        expect(onOpen).toHaveBeenCalledTimes(1);
      });

      it.each(
        ['DownArrow', 'Space', 'Enter']
      )('Pressing %s key selects the first item', (key) => {
        render();
        const { menubutton, items } = getElements();

        simulate.keydown[`press${key}`](menubutton);

        expect(items.first()).toBeFocused();
      });

      it('Pressing UpArrow selects the last item', () => {
        render();
        const { menubutton, items } = getElements();

        simulate.keydown.pressUpArrow(menubutton);

        expect(items.last()).toBeFocused();
      });
    });

    describe('MenuItem', () => {
      it.each([
        ['Tab', item => simulate.keydown.pressTab(item)],
        ['Shift+Tab', (item) => {
          simulate.keydown.pressTab(item, { shiftKey: true });

          act(() => {
            jest.runAllTimers();
          });

          act(() => {
            wrapper.current.update();
          });
        }],
        ['Escape', item => simulate.keydown.pressEscape(item)]
      ])('Pressing %s key closes the menu', (key, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items.first());

        const { menu } = getElements();

        assertStyleMatch({
          display: 'none'
        }, menu);
        expect(onClose).toHaveBeenCalledTimes(1);
        // FIXME: Test pressing Tab moves focus to the next element
        // FIXME: Test pressing Shift+Tab moves focus to the previous element
        // It's not possible to test this in enzyme because JSDOM does not support user events. It's also not
        // possible to test it in cypress because cypress uses syntetic events. We should add a test for this when
        // support for native events is implemented in cypress https://github.com/cypress-io/cypress/issues/311
      });

      it('Pressing Escape focuses the MenuButton', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressEscape(items.first());

        expect(menubutton).toBeFocused();
      });

      it.each([
        ['Down', 'next', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressDownArrow(items.at(1));
          // we're checking that we can focus the disabled item, this is intentional behaviour
          expect(items.at(2)).toBeFocused();
          expect(items.at(2).prop('disabled')).toBe(true);
          expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

          simulate.keydown.pressDownArrow(items.at(2));
          expect(items.at(3)).toBeFocused();
        }],
        ['Down', 'first', 'if the focus on the last item', (items) => {
          simulate.keydown.pressEnd(items.first());
          expect(items.last()).toBeFocused();

          simulate.keydown.pressDownArrow(items.last());
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'previous', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());
          simulate.keydown.pressDownArrow(items.at(1));
          simulate.keydown.pressDownArrow(items.at(2));

          simulate.keydown.pressUpArrow(items.at(3));
          expect(items.at(2)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(2));
          expect(items.at(1)).toBeFocused();

          simulate.keydown.pressUpArrow(items.at(1));
          expect(items.first()).toBeFocused();
        }],
        ['Up', 'last', 'if the focus is on the first item', (items) => {
          simulate.keydown.pressUpArrow(items.first());

          expect(items.last()).toBeFocused();
        }],
        ['Home', 'first', '', (items) => {
          simulate.keydown.pressDownArrow(items.first());

          simulate.keydown.pressHome(items.at(1));

          expect(items.first()).toBeFocused();
        }],
        ['End', 'first', '', (items) => {
          simulate.keydown.pressEnd(items.first());

          expect(items.last()).toBeFocused();
        }]
      ])('Pressing %s focuses the %s item %s', (key, direction, condition, mutator) => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        mutator(items);
      });

      it('Pressing Space does nothing', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        simulate.keydown.pressSpace(items.at(0));

        const { menu } = getElements();
        expect(items.at(0)).toBeFocused();
        assertStyleMatch({
          display: 'block'
        }, menu);
      });

      it('Pressing a-z selects the next item in the list starting with the letter that was pressed', () => {
        render();
        const { menubutton } = getElements();
        simulate.keydown.pressDownArrow(menubutton);
        const { items } = getElements();

        // moves to first element starting with P
        simulate.keydown.pressP(items.first());
        expect(items.at(1)).toBeFocused();

        // moves to first element starting with D
        simulate.keydown.pressD(items.at(1));
        // we're checking that we can focus the disabled item, this is intentional behaviour
        expect(items.at(2)).toBeFocused();
        expect(items.at(2).prop('disabled')).toBe(true);
        expect(items.at(2).getDOMNode().getAttribute('aria-disabled')).toBe('true');

        // moves to next element starting with D
        simulate.keydown.pressD(items.at(2));
        expect(items.at(3)).toBeFocused();

        // moves to next element starting with D wrapping back to the first element
        simulate.keydown.pressD(items.at(3));
        expect(items.at(2)).toBeFocused();

        // does nothing when there are no matches
        simulate.keydown.pressZ(items.at(2));
        expect(items.at(2)).toBeFocused();

        // does nothing withen a number key is pressed
        simulate.keydown.press1(items.at(2));
        expect(items.at(2)).toBeFocused();
      });
    });
  });

  describe('styles', () => {
    it('renders the button with a white background when the menu is open', () => {
      render();

      const { menubutton } = getElements();

      expect(menubutton).not.toHaveStyleRule('background-color');

      simulate.keydown.pressDownArrow(menubutton);

      expect(getElements().menubutton).toHaveStyleRule('background-color', theme.colors.white);
    });
  });

  it('validates the children prop', () => {
    jest.spyOn(global.console, 'error').mockImplementation(() => {});
    ReactDOM.render(
      <ThemeProvider { ...{ theme } }>
        <ActionPopover>
invalid children
</ActionPopover>
      </ThemeProvider>, container.current
    );
    expect(console.error).toHaveBeenCalledWith('Warning: Failed prop type: `ActionPopover` only accepts children of'
    + ' type `ActionPopoverItem` and `ActionPopoverDivider`.\n    in ActionPopover');
    global.console.error.mockReset();
  });
});
<|endoftext|>


<commit_before>var should = require('chai').should();
var expect = require('chai').expect;
require('fake-dom');
//setup start
window = {
  setLocation: function(full) {
    window.location={href: "https://127.0.0.1/example"};
    if(full) {
      window.location={
        href: "https://127.0.0.1/example",
        protocol: "http",
        path: "/ex",
        domainname: "localhost"
      };
    }
  },
  getSocketMock: function(name) {
    window[name] = function(url){
      this.url=url;
      return this;
    };
    return window[name];
  }
};
window.setLocation(true);
require('../src/yawsSocketConnect.js');
//setup end
describe('yaws', function() {
  it('yaws should be an object', function() {
    window.yaws.should.be.a('object');
  });
  it('yaws should have socketConnect', function() {
    window.yaws.should.have.property('socketConnect');
  });
  describe('yaws.socketConnect', function() {
    it('yaws.socketConnect should be a function', function() {
      window.yaws.socketConnect.should.be.a('function');
    });
    describe('yaws.socketConnect()', function() {
      it('yaws.socketConnect() should throw an error on missing argument', function() {
        expect(window.yaws.socketConnect).to.throw("The first parameter 'onMessage' is required to be a function accepting a single parameter of type Blob.");
      });
      it('yaws.socketConnect() should throw an error on missing Websocket', function() {
        expect(function(){window.yaws.socketConnect(function(blob){},{},'/')}).to.throw("No Websocket Implementation found");
      });
      describe('yaws.socketConnect() @ Websocket', function() {
        Websocket = window.getSocketMock("Websocket");
        it('yaws.socketConnect() should be an object', function() {
          window.yaws.socketConnect(function(blob){},{},'/').should.be.a('object');
        });
        it('yaws.socketConnect() should be a Websocket', function() {
          window.yaws.socketConnect(function(blob){},{},'/').should.be.instanceof(Websocket);
        });
        describe('yaws.socketConnect().getUrl() @ Websocket', function() {
          it('yaws.socketConnect().url should be wss://127.0.0.1/example', function() {
            window.setLocation(false);
            expect(window.yaws.socketConnect(function(blob){},{})).to.have.property('url','wss://127.0.0.1/example');
            window.setLocation(true);
          });
          it('yaws.socketConnect().url should be ws://localhost/ex', function() {
            expect(window.yaws.socketConnect(function(blob){},{})).to.have.property('url','ws://localhost/ex');
          });
          it('yaws.socketConnect().url should be ws://localhost/socket/ex', function() {
            expect(window.yaws.socketConnect(function(blob){},{},'{protocol}://{domain}/socket{path}')).to.have.property('url','ws://localhost/socket/ex');
          });
        });
        describe('yaws.socketConnect() @ ReconnnectingWebSocket', function() {
          ReconnectingWebSocket = window.getSocketMock("ReconnectingWebSocket");
          it('yaws.socketConnect() should be an object', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.a('object');
          });
          it('yaws.socketConnect() should be a ReconnectingWebSocket', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.instanceof(ReconnectingWebSocket);
          });
          delete window["ReconnectingWebSocket"];
        });
        describe('yaws.socketConnect() @ RobustWebSocket', function() {
          RobustWebSocket = window.getSocketMock("RobustWebSocket");
          it('yaws.socketConnect() should be an object', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.a('object');
          });
          it('yaws.socketConnect() should be a RobustWebSocket', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.instanceof(RobustWebSocket);
          });
          delete window["RobustWebSocket"];
        });
      });
    });
  });
});
<commit_msg>Update yawsSocketConnect.js<commit_after>var should = require('chai').should();
var expect = require('chai').expect;
require('fake-dom');
//setup start
window = {
  setLocation: function(full) {
    window.location={href: "https://127.0.0.1/example"};
    if(full) {
      window.location={
        href: "https://127.0.0.1/example",
        protocol: "http",
        path: "/ex",
        domainname: "localhost"
      };
    }
  },
  getSocketMock: function(name) {
    window[name] = function(url){
      this.url=url;
      return this;
    };
    return window[name];
  }
};
window.setLocation(true);
require('../src/yawsSocketConnect.js');
//setup end
describe('yaws', function() {
  it('yaws should be an object', function() {
    window.yaws.should.be.a('object');
  });
  it('yaws should have socketConnect', function() {
    window.yaws.should.have.property('socketConnect');
  });
  describe('yaws.socketConnect', function() {
    it('yaws.socketConnect should be a function', function() {
      window.yaws.socketConnect.should.be.a('function');
    });
    describe('yaws.socketConnect()', function() {
      it('yaws.socketConnect() should throw an error on missing argument', function() {
        expect(window.yaws.socketConnect).to.throw("The first parameter 'onMessage' is required to be a function accepting a single parameter of type Blob.");
      });
      it('yaws.socketConnect() should throw an error on missing Websocket', function() {
        expect(function(){window.yaws.socketConnect(function(blob){},{},'/')}).to.throw("No Websocket Implementation found");
      });
      describe('yaws.socketConnect() @ Websocket', function() {
        Websocket = window.getSocketMock("Websocket");
        it('yaws.socketConnect() should be an object', function() {
          window.yaws.socketConnect(function(blob){},{},'/').should.be.a('object');
        });
        it('yaws.socketConnect() should be a Websocket', function() {
          window.yaws.socketConnect(function(blob){},{},'/').should.be.instanceof(Websocket);
        });
        describe('yaws.socketConnect().getUrl() @ Websocket', function() {
          it('yaws.socketConnect().url should be wss://127.0.0.1/example', function() {
            window.setLocation(false);
            expect(window.yaws.socketConnect(function(blob){},{})).to.have.property('url','wss://127.0.0.1/example');
            window.setLocation(true);
          });
          it('yaws.socketConnect().url should be ws://localhost/ex', function() {
            expect(window.yaws.socketConnect(function(blob){},{})).to.have.property('url','ws://localhost/ex');
          });
          it('yaws.socketConnect().url should be ws://localhost/socket/ex', function() {
            expect(window.yaws.socketConnect(function(blob){},{},'{protocol}://{domain}/socket{path}')).to.have.property('url','ws://localhost/socket/ex');
          });
        });
        describe('yaws.socketConnect() @ ReconnnectingWebSocket', function() {
          ReconnectingWebSocket = window.getSocketMock("ReconnectingWebSocket");
          it('yaws.socketConnect() should be an object', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.a('object');
          });
          it('yaws.socketConnect() should be a ReconnectingWebSocket', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.instanceof(ReconnectingWebSocket);
          });
          delete window["ReconnectingWebSocket"];
        });
        describe('yaws.socketConnect() @ RobustWebSocket', function() {
          RobustWebSocket = window.getSocketMock("RobustWebSocket");
          it('yaws.socketConnect() should be an object', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.a('object');
          });
          it('yaws.socketConnect() should be a RobustWebSocket', function() {
            window.yaws.socketConnect(function(blob){},{},'/').should.be.instanceof(RobustWebSocket);
          });
          delete window["RobustWebSocket"];
        });
        delete window["Websocket"];
      });
    });
  });
});
<|endoftext|>