<commit_before>
var AudioController = function(container, ui, scroller) {

	var block = $(
				'
' +
					'<audio></audio>' +
					'
' +
						'

' +
						'

' +
						'' +
					'
' +
					'<input type="button" class="audio-prev" value="Prev" />' +
					'<input type="button" class="audio-play" value="Play" />' +
					'<input type="button" class="audio-next" value="Next" />' +
					'00:00' +
					'00:00' +
					'' +
					'' +
					'<input type="button" class="audio-options-button" />' +
				'
'
				).appendTo(container),
		optionsButton = block.find('.audio-options-button'),
		options = $(
				'
' +
					'' +
					'Audio Options' +
					'<input type="checkbox" class="audio-scroll" checked />Sync Text (beta)' +
					'<input type="checkbox" class="audio-autoplay" checked />Autoplay Next' +
				'
'
				).appendTo(container),
		scrollCheckbox = options.find('.audio-scroll').prop('checked', false),
		autoplayCheckbox = options.find('.audio-autoplay').prop('checked', false),
		optionsCloseButton = options.find('.audio-options-close'),

		audio = block.find('audio')[0],

		playButton = block.find('.audio-play'),
		nextButton = block.find('.audio-next'),
		prevButton = block.find('.audio-prev'),
		currenttime = block.find('.audio-currenttime'),
		duration = block.find('.audio-duration'),
		title = block.find('.audio-title'),
		subtitle = block.find('.audio-subtitle'),

		audioSlider = block.find('.audio-slider'),
		audioSliderCurrent = block.find('.audio-slider-current'),
		audioSliderHandle = block.find('.audio-slider-handle'),

		isDraggingSliderHandle = false,

		textInfo = null,
		audioInfo = null,
		locationInfo = null,
		sectionid = '',
		fragmentid = '',
		fragmentAudioData = null,
		sectionHeight = 0,
		sectionNode = null,
		hasAudio = false,
		audioDataManager = new AudioDataManager();

	//autoplayCheckbox.prop('checked',false);
	//autoplayCheckbox.parent().hide();

	block.hide();
	ui.hide();
	options.hide();

	// OPTIONS
	optionsButton.on('click', function() {
		if (options.is(':visible')) {
			options.hide();
		} else {
			options.show();
		}
	});
	optionsCloseButton.on('click', function() {
		options.hide();
	});

	// MAIN
	ui.on('click', function() {
		if (block.is(':visible')) {
			block.hide();
		} else {
			block.show();
		}
	});

	playButton.on('click', function() {

		if (audio.src == '') {
			return;
		}

		if (audio.paused || audio.ended) {
			audio.play();
		} else {
			audio.pause();
		}
	});

	prevButton.on('click', function() {

		audioDataManager.getPrevFragment(textInfo, audioInfo, fragmentid, function(prevFragmentid) {

			console.log('prev', fragmentid, prevFragmentid);

			if (prevFragmentid == null) {
				return;
			}

			if (scrollCheckbox.is(':checked')) {
				//scroller.scrollTo(nextSectionid + '_1', -10);
				scroller.load('text', prevFragmentid.split('_')[0], prevFragmentid);
			}

			if (fragmentAudioData == null || prevFragmentid != fragmentAudioData.fragmentid) {
				loadAudio(prevFragmentid);
				$(audio).on('loadeddata', playWhenLoaded);
			}
		});

	});
	nextButton.on('click', function() {

		audioDataManager.getNextFragment(textInfo, audioInfo, fragmentid, function(nextFragmentid) {

			console.log('next', fragmentid, nextFragmentid);

			if (nextFragmentid == null) {
				return;
			}

			if (scrollCheckbox.is(':checked')) {
				//scroller.scrollTo(nextSectionid + '_1', -10);
				scroller.load('text', nextFragmentid.split('_')[0], nextFragmentid);
			}

			if (fragmentAudioData == null || nextFragmentid != fragmentAudioData.fragmentid) {
				loadAudio(nextFragmentid);
				$(audio).on('loadeddata', playWhenLoaded);
			}
		});

		//alert('no implementation');
		return;

	});

	scroller.on('locationchange', updateLocation);
	//scroller.on('load', loadAudio);

	function updateLocation(e) {

		var newLocationInfo = e.data;

		//console.log('AUDIO:locationchange', e, newLocationInfo);

		// found a fragment
		if (newLocationInfo != null) {

			locationInfo = newLocationInfo;

			loadAudio(locationInfo.fragmentid);
		}
	}

	function loadAudio(newFragmentid) {

		if (!hasAudio) {
			return;
		}

		if (fragmentid != newFragmentid) {

			//console.log('loading',newSectionid,textInfo, hasAudio);

			fragmentid = newFragmentid;

			var newSectionid = fragmentid.split('_')[0],
				dbsBookCode =  sectionid.substring(0,2),
				chaperNumber = sectionid.substring(2),
				loadNewData = audioInfo.pericopeBased || newSectionid != sectionid;

			sectionid = newSectionid;

			// only do a checks when we need to!
			if (loadNewData) {
				audioDataManager.getFragmentAudio(textInfo, audioInfo, fragmentid, function(newFragmentAudioData) {

					// only update if this is new data
					if (fragmentAudioData == null || newFragmentAudioData == null || fragmentAudioData.id != newFragmentAudioData.id) {

						// if no data was received, then we need to hide everything
						if (!newFragmentAudioData || newFragmentAudioData.url == null) {
							audio.src = null;

							title.html('[No audio]');

							ui.hide();
							block.hide();

							fragmentAudioData = newFragmentAudioData;
							return;
						} else {

							// if we got a URL, then show the ear icon again
							ui.show();

							// only when the previous data was null, do we re-show the control bar
							if (fragmentAudioData == null) {
								// let's not do it ever
								//block.show();
							}

							fragmentAudioData = newFragmentAudioData;
						}

						//audio.currentTime = 0;
						audio.src = fragmentAudioData.url;
						audio.load();

						// store height info

						// TODO: start and finish verse instead!
						sectionNode = container.find('.section[data-id="' + sectionid + '"]');
						sectionHeight = sectionNode.height();

						// give feedback
						//title.html( new bible.Reference(sectionid).toString() + (audioInfo.title ? ' [' + audioInfo.title + ']' : ''));
						title.html( new bible.Reference(sectionid).toString() );
						subtitle.html( audioInfo.title );

					}
				});
			}
		}
	}

	function playWhenLoaded(){
		//console.log('playWhenLoaded');
		audio.play();
		$(audio).off('loadeddata', playWhenLoaded);
	}

	$(audio)
		.on('play playing', function() {

			var thisAudio = this;

			playButton
				.attr('value','Pause')
				.addClass('playing');

			// pause all other media on the page
			$('audio,video').each(function() {
				var audioOrVideoNode = this;

				if (audioOrVideoNode != thisAudio && !audioOrVideoNode.paused && !audioOrVideoNode.ended) {
					audioOrVideoNode.pause();
				}
			});

		})
		.on('pause ended', function() {
			playButton
				.attr('value','Play')
				.removeClass('playing');
		})
		.on('loadstart', function() {
			playButton
				.attr('value','Play')
				.removeClass('playing');

			audioSliderHandle.css({left: '0%' });
			currenttime.html( secondsToTimeCode(0) );
			duration.html( secondsToTimeCode(0) );
		})
		.on('loadedmetadata', function() {
			duration.html( secondsToTimeCode(audio.duration) );
		})
		.on('ended', function() {
			if (autoplayCheckbox.is(':checked')) {

				$(audio).on('loadeddata', playWhenLoaded);

				nextButton.trigger('click');
				//audio.play();
			}
		})
		.on('timeupdate', function() {
			currenttime.html( secondsToTimeCode(audio.currentTime) );
			duration.html( secondsToTimeCode(audio.duration) );

			// slider
			audioSliderCurrent.css({width: (audio.currentTime / audio.duration * 100) + '%' });
			if (!isDraggingSliderHandle) {
				audioSliderHandle.css({left: (audio.currentTime / audio.duration * 100) + '%' });
			}

			if (!scrollCheckbox.is(':checked')) {
				return;
			}

			if (sectionNode.length == 0) {

				sectionNode = container.find('.section[data-id="' + sectionid + '"]');
				sectionHeight = sectionNode.height();
			}

			var
				// calculate percent to scroll
				chapter = parseInt(sectionid.substring(2), 10),
				skipSeconds = (chapter == 1) ? 10 : 8,
				fraction = (audio.currentTime-skipSeconds) / (audio.duration-skipSeconds);

				// calculate node position
				pane = container.find('.scroller-main'),
				paneTop = pane.offset().top,
				scrollTop = pane.scrollTop(),
				nodeTop = sectionNode.offset().top,
				nodeTopAdjusted = nodeTop - paneTop + scrollTop,

				offset = sectionHeight * fraction
							// adjust the offset by two lines or so
							- 20;

			if (offset <= 0) {
				offset = 0;
			}

			// go to it
			scroller.setFocus(true);
			pane.scrollTop(nodeTopAdjusted + offset);
		});

	audioSliderHandle.on('mousedown', function(e) {
		isDraggingSliderHandle = true;
		$(document).on('mousemove', documentMouseMove);
		$(document).on('mouseup', documentMouseUp);
	});

	function documentMouseUp(e) {
		console.log('mouseup');

		isDraggingSliderHandle = false;
		$(document).off('mousemove', documentMouseMove);
		$(document).off('mouseup', documentMouseUp);
	}

	function documentMouseMove(e) {

		var
			width = audioSlider.width(),
			pos = audioSlider.offset(),
			clientX = e.clientX,
			offsetX = clientX - pos.left,
			percent = offsetX/width,
			newTime = percent * audio.duration;

		audioSliderHandle.css({left: (percent*100) + '%'});

		audio.currentTime = newTime;
	}

	audioSlider.on('click', function(e) {

		console.log('slider:click');

		var
			width = audioSlider.width(),
			offsetX = e.offsetX,
			percent = offsetX/width,
			newTime = percent * audio.duration;

		audio.currentTime = newTime;

	});

	function setTextInfo(newTextInfo) {
		if (textInfo == null || textInfo.id != newTextInfo.id) {

			// reset
			title.html('');
			audioSliderCurrent.css({left: '0%' });
			audioSliderHandle.css({left: '0%' });
			currenttime.html( secondsToTimeCode(0) );
			duration.html( secondsToTimeCode(0) );

			textInfo = newTextInfo;

			if (!audio.paused && !audio.ended) {
				try {
					audio.pause();
					audio.src = null;
				} catch (e) {
					// boom!
				}
			}

			audioDataManager.getAudioInfo(textInfo, function(newAudioInfo) {

				if (newAudioInfo != null) {
					audioInfo = newAudioInfo;

					console.log('AUDIO: YES', textInfo.id, textInfo.lang, newAudioInfo);

					hasAudio = true;

					if (fragmentid != '') {
						console.log('info, loading', hasAudio, fragmentid);
						loadAudio(fragmentid);
					}

					// start load
					//block.show();
					ui.show();

				} else {
					hasAudio = false;

					console.log('AUDIO: NO', textInfo.id, textInfo.lang, newAudioInfo);

					block.hide();
					ui.hide();
				}
			});

		}

		// attempt to load
	}

	function secondsToTimeCode(time) {

		var hours = Math.floor(time / 3600) % 24,
			minutes = Math.floor(time / 60) % 60,
			seconds = Math.floor(time % 60),
			result =
   					(minutes < 10 ? '0' + minutes : minutes) + ':'
					+ (seconds < 10 ? '0' + seconds : seconds);

		return result;
	}

	function size(width, height) {

		block.outerWidth(width);
	}

	//$(window).on('resize')

	var ext = {
		setTextInfo: setTextInfo,
		size: size
	}
	ext = $.extend(true, ext, EventEmitter);

	return ext;
}
<commit_msg>more fixes for better timing<commit_after>
var AudioController = function(container, ui, scroller) {

	var block = $(
				'
' +
					'<audio></audio>' +
					'
' +
						'

' +
						'

' +
						'' +
					'
' +
					'<input type="button" class="audio-prev" value="Prev" />' +
					'<input type="button" class="audio-play" value="Play" />' +
					'<input type="button" class="audio-next" value="Next" />' +
					'00:00' +
					'00:00' +
					'' +
					'' +
					'<input type="button" class="audio-options-button" />' +
				'
'
				).appendTo(container),
		optionsButton = block.find('.audio-options-button'),
		options = $(
				'
' +
					'' +
					'Audio Options' +
					'<input type="checkbox" class="audio-scroll" checked />Sync Text (beta)' +
					'<input type="checkbox" class="audio-autoplay" checked />Autoplay Next' +
				'
'
				).appendTo(container),
		scrollCheckbox = options.find('.audio-scroll').prop('checked', false),
		autoplayCheckbox = options.find('.audio-autoplay').prop('checked', false),
		optionsCloseButton = options.find('.audio-options-close'),

		audio = block.find('audio')[0],

		playButton = block.find('.audio-play'),
		nextButton = block.find('.audio-next'),
		prevButton = block.find('.audio-prev'),
		currenttime = block.find('.audio-currenttime'),
		duration = block.find('.audio-duration'),
		title = block.find('.audio-title'),
		subtitle = block.find('.audio-subtitle'),

		audioSlider = block.find('.audio-slider'),
		audioSliderCurrent = block.find('.audio-slider-current'),
		audioSliderHandle = block.find('.audio-slider-handle'),

		isDraggingSliderHandle = false,

		textInfo = null,
		audioInfo = null,
		locationInfo = null,
		sectionid = '',
		fragmentid = '',
		fragmentAudioData = null,
		sectionHeight = 0,
		sectionNode = null,
		hasAudio = false,
		audioDataManager = new AudioDataManager();

	//autoplayCheckbox.prop('checked',false);
	//autoplayCheckbox.parent().hide();

	block.hide();
	ui.hide();
	options.hide();

	// OPTIONS
	optionsButton.on('click', function() {
		if (options.is(':visible')) {
			options.hide();
		} else {
			options.show();
		}
	});
	optionsCloseButton.on('click', function() {
		options.hide();
	});

	// MAIN
	ui.on('click', function() {
		if (block.is(':visible')) {
			block.hide();
		} else {
			block.show();
		}
	});

	playButton.on('click', function() {

		if (audio.src == '') {
			return;
		}

		if (audio.paused || audio.ended) {
			audio.play();
		} else {
			audio.pause();
		}
	});

	prevButton.on('click', function() {

		audioDataManager.getPrevFragment(textInfo, audioInfo, fragmentid, function(prevFragmentid) {

			console.log('prev', fragmentid, prevFragmentid);

			if (prevFragmentid == null) {
				return;
			}

			if (scrollCheckbox.is(':checked')) {
				//scroller.scrollTo(nextSectionid + '_1', -10);
				scroller.load('text', prevFragmentid.split('_')[0], prevFragmentid);
			}

			if (fragmentAudioData == null || prevFragmentid != fragmentAudioData.fragmentid) {
				loadAudio(prevFragmentid);
				$(audio).on('loadeddata', playWhenLoaded);
			}
		});

	});
	nextButton.on('click', function() {

		audioDataManager.getNextFragment(textInfo, audioInfo, fragmentid, function(nextFragmentid) {

			console.log('next', fragmentid, nextFragmentid);

			if (nextFragmentid == null) {
				return;
			}

			if (scrollCheckbox.is(':checked')) {
				//scroller.scrollTo(nextSectionid + '_1', -10);
				scroller.load('text', nextFragmentid.split('_')[0], nextFragmentid);
			}

			if (fragmentAudioData == null || nextFragmentid != fragmentAudioData.fragmentid) {
				loadAudio(nextFragmentid);
				$(audio).on('loadeddata', playWhenLoaded);
			}
		});

		//alert('no implementation');
		return;

	});

	scroller.on('locationchange', updateLocation);
	//scroller.on('load', loadAudio);

	function updateLocation(e) {

		var newLocationInfo = e.data;

		//console.log('AUDIO:locationchange', e, newLocationInfo);

		// found a fragment
		if (newLocationInfo != null) {

			locationInfo = newLocationInfo;

			loadAudio(locationInfo.fragmentid);
		}
	}

	function loadAudio(newFragmentid) {

		if (!hasAudio) {
			return;
		}

		if (fragmentid != newFragmentid) {

			//console.log('loading',newSectionid,textInfo, hasAudio);

			fragmentid = newFragmentid;

			var newSectionid = fragmentid.split('_')[0],
				dbsBookCode =  sectionid.substring(0,2),
				chaperNumber = sectionid.substring(2),
				loadNewData = audioInfo.pericopeBased || newSectionid != sectionid;

			sectionid = newSectionid;

			// only do a checks when we need to!
			if (loadNewData) {
				audioDataManager.getFragmentAudio(textInfo, audioInfo, fragmentid, function(newFragmentAudioData) {

					// only update if this is new data
					if (fragmentAudioData == null || newFragmentAudioData == null || fragmentAudioData.id != newFragmentAudioData.id) {

						// if no data was received, then we need to hide everything
						if (!newFragmentAudioData || newFragmentAudioData.url == null) {
							audio.src = null;

							title.html('[No audio]');

							ui.hide();
							block.hide();

							fragmentAudioData = newFragmentAudioData;
							return;
						} else {

							// if we got a URL, then show the ear icon again
							ui.show();

							// only when the previous data was null, do we re-show the control bar
							if (fragmentAudioData == null) {
								// let's not do it ever
								//block.show();
							}

							fragmentAudioData = newFragmentAudioData;
						}

						//audio.currentTime = 0;
						audio.src = fragmentAudioData.url;
						audio.load();

						// store height info

						// TODO: start and finish verse instead!
						sectionNode = container.find('.section[data-id="' + sectionid + '"]');
						sectionHeight = sectionNode.height();

						// give feedback
						//title.html( new bible.Reference(sectionid).toString() + (audioInfo.title ? ' [' + audioInfo.title + ']' : ''));
						title.html( new bible.Reference(sectionid).toString() );
						subtitle.html( audioInfo.title );

					}
				});
			}
		}
	}

	function playWhenLoaded(){
		//console.log('playWhenLoaded');
		audio.play();
		$(audio).off('loadeddata', playWhenLoaded);
	}

	$(audio)
		.on('play playing', function() {

			var thisAudio = this;

			playButton
				.attr('value','Pause')
				.addClass('playing');

			// pause all other media on the page
			$('audio,video').each(function() {
				var audioOrVideoNode = this;

				if (audioOrVideoNode != thisAudio && !audioOrVideoNode.paused && !audioOrVideoNode.ended) {
					audioOrVideoNode.pause();
				}
			});

		})
		.on('pause ended', function() {
			playButton
				.attr('value','Play')
				.removeClass('playing');
		})
		.on('loadstart', function() {
			playButton
				.attr('value','Play')
				.removeClass('playing');

			audioSliderHandle.css({left: '0%' });
			currenttime.html( secondsToTimeCode(0) );
			duration.html( secondsToTimeCode(0) );
		})
		.on('loadedmetadata', function() {
			duration.html( secondsToTimeCode(audio.duration) );
		})
		.on('ended', function() {
			if (autoplayCheckbox.is(':checked')) {

				$(audio).on('loadeddata', playWhenLoaded);

				nextButton.trigger('click');
				//audio.play();
			}
		})
		.on('timeupdate', function() {
			currenttime.html( secondsToTimeCode(audio.currentTime) );
			duration.html( secondsToTimeCode(audio.duration) );

			// slider
			audioSliderCurrent.css({width: (audio.currentTime / audio.duration * 100) + '%' });
			if (!isDraggingSliderHandle) {
				audioSliderHandle.css({left: (audio.currentTime / audio.duration * 100) + '%' });
			}

			if (!scrollCheckbox.is(':checked')) {
				return;
			}

			if (sectionNode.length == 0) {

				sectionNode = container.find('.section[data-id="' + sectionid + '"]');

			}

			sectionHeight = sectionNode.height();

			var
				// calculate percent to scroll
				chapter = parseInt(sectionid.substring(2), 10),
				skipSeconds = (chapter == 1) ? 10 : 8,
				fraction = (audio.currentTime-skipSeconds) / (audio.duration-skipSeconds);

				// calculate node position
				pane = container.find('.scroller-main'),
				paneTop = pane.offset().top,
				scrollTop = pane.scrollTop(),
				nodeTop = sectionNode.offset().top,
				nodeTopAdjusted = nodeTop - paneTop + scrollTop,

				offset = sectionHeight * fraction
							// adjust the offset by two lines or so
							// - 20
							- sectionNode.find('.v:first').height()
							- (sectionNode.find('.v:last').height()*fraction);

			console.log(fraction, sectionHeight, offset);

			if (offset <= 0) {
				offset = 0;
			}

			// go to it
			scroller.setFocus(true);
			pane.scrollTop(nodeTopAdjusted + offset);
		});

	audioSliderHandle.on('mousedown', function(e) {
		isDraggingSliderHandle = true;
		$(document).on('mousemove', documentMouseMove);
		$(document).on('mouseup', documentMouseUp);
	});

	function documentMouseUp(e) {
		console.log('mouseup');

		isDraggingSliderHandle = false;
		$(document).off('mousemove', documentMouseMove);
		$(document).off('mouseup', documentMouseUp);
	}

	function documentMouseMove(e) {

		var
			width = audioSlider.width(),
			pos = audioSlider.offset(),
			clientX = e.clientX,
			offsetX = clientX - pos.left,
			percent = offsetX/width,
			newTime = percent * audio.duration;

		audioSliderHandle.css({left: (percent*100) + '%'});

		audio.currentTime = newTime;
	}

	audioSlider.on('click', function(e) {

		console.log('slider:click');

		var
			width = audioSlider.width(),
			offsetX = e.offsetX,
			percent = offsetX/width,
			newTime = percent * audio.duration;

		audio.currentTime = newTime;

	});

	function setTextInfo(newTextInfo) {
		if (textInfo == null || textInfo.id != newTextInfo.id) {

			// reset
			title.html('');
			audioSliderCurrent.css({left: '0%' });
			audioSliderHandle.css({left: '0%' });
			currenttime.html( secondsToTimeCode(0) );
			duration.html( secondsToTimeCode(0) );

			textInfo = newTextInfo;

			if (!audio.paused && !audio.ended) {
				try {
					audio.pause();
					audio.src = null;
				} catch (e) {
					// boom!
				}
			}

			audioDataManager.getAudioInfo(textInfo, function(newAudioInfo) {

				if (newAudioInfo != null) {
					audioInfo = newAudioInfo;

					console.log('AUDIO: YES', textInfo.id, textInfo.lang, newAudioInfo);

					hasAudio = true;

					if (fragmentid != '') {
						console.log('info, loading', hasAudio, fragmentid);
						loadAudio(fragmentid);
					}

					// start load
					//block.show();
					ui.show();

				} else {
					hasAudio = false;

					console.log('AUDIO: NO', textInfo.id, textInfo.lang, newAudioInfo);

					block.hide();
					ui.hide();
				}
			});

		}

		// attempt to load
	}

	function secondsToTimeCode(time) {

		var hours = Math.floor(time / 3600) % 24,
			minutes = Math.floor(time / 60) % 60,
			seconds = Math.floor(time % 60),
			result =
   					(minutes < 10 ? '0' + minutes : minutes) + ':'
					+ (seconds < 10 ? '0' + seconds : seconds);

		return result;
	}

	function size(width, height) {

		block.outerWidth(width);
	}

	//$(window).on('resize')

	var ext = {
		setTextInfo: setTextInfo,
		size: size
	}
	ext = $.extend(true, ext, EventEmitter);

	return ext;
}
<|endoftext|>


<commit_before>/**
 * Imports
 */

import ChallengeLoader from 'components/ChallengeLoader'
import VirtualList from 'components/VirtualList'
import {component, element, Window} from 'vdux'
import SortHeader from 'components/SortHeader'
import LinkModal from 'components/LinkModal'
import {Block, Flex, Menu} from 'vdux-ui'
import Button from 'components/Button'
import flatten from 'lodash/flatten'
import {refMethod} from 'vdux-fire'
import objEqual from '@f/equal-obj'
import sort from 'lodash/orderBy'
import chunk from 'lodash/chunk'
import map from '@f/map'

const caseInsensitiveName = (game) => game.name
  ? game.name.toLowerCase()
  : game.title.toLowerCase()

/**
 * <Challenge Feed/>
 */

export default component({
	initialState: {
		orderBy: 'lastEdited',
  	order: 'desc',
  	items: []
	},
	* onCreate ({props, actions, state}) {
		const sortedGames = sort(
			map((game, key) => ({...game, key}), props.games),
			state.orderBy,
			state.order
		)
  	yield actions.setItems(sortedGames)
	},
	* onUpdate (prev, {props, actions, state}) {
		if (prev.state.orderBy !== state.orderBy || prev.state.order !== state.order || !objEqual(prev.props.games, props.games) && props.games) {
	    const sortedGames = sort(
	    	map((game, key) => ({...game, key}), props.games),
	    	state.orderBy,
	    	state.order
	    )
	    yield actions.setItems(sortedGames)
	  }
	},
  render ({props, actions, context, state}) {
	  const {games, mine, search, ...rest} = props
	  const {uid} = context
	  const {order, orderBy, items} = state

	  const modalFooter = (
	    <Block>
	      <Button ml='m' onClick={context.closeModal()}>Done</Button>
	    </Block>
	  )

	  if (items.length < 1) {
	    return

	  }

	  const Challenges = ({
	    props
	  }) => (
	    <Block h={props.virtual.style.height} {...props.virtual.style} boxSizing='border-box'>
	      {props.virtual.items.map((game, i) => (
	        <ChallengeLoader
	          checkbox
	          lastEdited={game.lastEdited}
	          setModal={actions.showModal}
	          gameKey={game.key}
	          h={props.itemHeight}
	          remove={actions.remove}
	          key={game.key}
	          draggable={false}
	          uid={uid}
	          mine={mine}
	          ref={game.ref || game.key} />
	      ))}
	    </Block>
	  )
<<<<<<>>>>>> 76888d2e9d3f33461aced819a87b05fa7c880838
	  const CVL = VirtualList(Challenges, items, {
	  	height: 68,
	  	buffer: 30,
	  	container: search ? document.getElementById('top') : document.getElementById('profile-container')
	  })

	  return (
	    <Block wide margin='0 auto' {...rest}>
	      <Block
	        wide
	        display='block'
	        minWidth='820px'>
	        <Block pl='40px' pr='16px' py='8px' color='#999' fontWeight='800' align='start center' bgColor='white' border='1px solid divider' borderBottomWidth={0}>
	          <SortHeader
	            onClick={actions.handleClick(caseInsensitiveName)}
	            minWidth='220px'
	            flex
	            dir={orderBy === caseInsensitiveName && order}
	            label='NAME' />
	          <SortHeader
	            onClick={actions.handleClick('inputType')}
	            dir={orderBy === 'inputType' && order}
	            minWidth={colWidth}
	            w={colWidth}
	            label='CODE TYPE' />
	          <SortHeader
	            onClick={actions.handleClick('lastEdited')}
	            dir={orderBy === 'lastEdited' && order}
	            minWidth={colWidth}
	            w={colWidth}
	            label='LAST EDITED' />
	        </Block>
	        {
	        	<Block>
  	          <CVL />
  	        </Block>
  	      }
	      </Block>
	    </Block>
	  )
  },
  controller: {
  	* showModal ({context}, link) {
  		yield context.openModal(
  			<LinkModal
	        code={link}
	        footer={modalFooter} />
	    )
  	},
  	* handleClick ({state, actions}, cat) {
	    if (state.orderBy === cat) {
	      const newOrder = state.order === 'asc' ? 'desc' : 'asc'
	      yield actions.setOrder(newOrder)
	    } else {
	      yield actions.setOrderType(cat)
	    }
  	},
  	* remove ({context}, ref) {
  		const {uid, firebaseSet} = context
  		yield firebaseSet(`/users/${uid}/games/${ref}`, null)
  	}
  },
  reducer: {
  	setOrderType: (state, orderBy) => ({orderBy}),
	  setOrder: (state, order) => ({order}),
	  setItems: (state, items) => ({items})
  }
})
<commit_msg>fixed merge<commit_after>/**
 * Imports
 */

import ChallengeLoader from 'components/ChallengeLoader'
import VirtualList from 'components/VirtualList'
import {component, element, Window} from 'vdux'
import SortHeader from 'components/SortHeader'
import LinkModal from 'components/LinkModal'
import {Block, Flex, Menu} from 'vdux-ui'
import Button from 'components/Button'
import flatten from 'lodash/flatten'
import {refMethod} from 'vdux-fire'
import objEqual from '@f/equal-obj'
import sort from 'lodash/orderBy'
import chunk from 'lodash/chunk'
import map from '@f/map'

const caseInsensitiveName = (game) => game.name
  ? game.name.toLowerCase()
  : game.title.toLowerCase()

/**
 * <Challenge Feed/>
 */

export default component({
	initialState: {
		orderBy: 'lastEdited',
  	order: 'desc',
  	items: []
	},
	* onCreate ({props, actions, state}) {
		const sortedGames = sort(
			map((game, key) => ({...game, key}), props.games),
			state.orderBy,
			state.order
		)
  	yield actions.setItems(sortedGames)
	},
	* onUpdate (prev, {props, actions, state}) {
		if (prev.state.orderBy !== state.orderBy || prev.state.order !== state.order || !objEqual(prev.props.games, props.games) && props.games) {
	    const sortedGames = sort(
	    	map((game, key) => ({...game, key}), props.games),
	    	state.orderBy,
	    	state.order
	    )
	    yield actions.setItems(sortedGames)
	  }
	},
  render ({props, actions, context, state}) {
	  const {games, mine, search, ...rest} = props
	  const {uid} = context
	  const {order, orderBy, items} = state

	  const modalFooter = (
	    <Block>
	      <Button ml='m' onClick={context.closeModal()}>Done</Button>
	    </Block>
	  )

	  if (items.length < 1) {
	    return

	  }

	  const Challenges = ({
	    props
	  }) => (
	    <Block h={props.virtual.style.height} {...props.virtual.style} boxSizing='border-box'>
	      {props.virtual.items.map((game, i) => (
	        <ChallengeLoader
	          checkbox
	          lastEdited={game.lastEdited}
	          setModal={actions.showModal}
	          gameKey={game.key}
	          h={props.itemHeight}
	          remove={actions.remove}
	          key={game.key}
	          draggable={false}
	          uid={uid}
	          mine={mine}
	          ref={game.ref || game.key} />
	      ))}
	    </Block>
	  )

	  const colWidth = '250px'
	  const CVL = VirtualList(Challenges, items, {
	  	height: 68,
	  	buffer: 30,
	  	container: search ? document.getElementById('top') : document.getElementById('profile-container')
	  })

	  return (
	    <Block wide margin='0 auto' {...rest}>
	      <Block
	        wide
	        display='block'
	        minWidth='820px'>
	        <Block pl='40px' pr='16px' py='8px' color='#999' fontWeight='800' align='start center' bgColor='white' border='1px solid divider' borderBottomWidth={0}>
	          <SortHeader
	            onClick={actions.handleClick(caseInsensitiveName)}
	            minWidth='220px'
	            flex
	            dir={orderBy === caseInsensitiveName && order}
	            label='NAME' />
	          <SortHeader
	            onClick={actions.handleClick('inputType')}
	            dir={orderBy === 'inputType' && order}
	            minWidth={colWidth}
	            w={colWidth}
	            label='CODE TYPE' />
	          <SortHeader
	            onClick={actions.handleClick('lastEdited')}
	            dir={orderBy === 'lastEdited' && order}
	            minWidth={colWidth}
	            w={colWidth}
	            label='LAST EDITED' />
	        </Block>
	        {
	        	<Block>
  	          <CVL />
  	        </Block>
  	      }
	      </Block>
	    </Block>
	  )
  },
  controller: {
  	* showModal ({context}, link) {
  		yield context.openModal(
  			<LinkModal
	        code={link}
	        footer={modalFooter} />
	    )
  	},
  	* handleClick ({state, actions}, cat) {
	    if (state.orderBy === cat) {
	      const newOrder = state.order === 'asc' ? 'desc' : 'asc'
	      yield actions.setOrder(newOrder)
	    } else {
	      yield actions.setOrderType(cat)
	    }
  	},
  	* remove ({context}, ref) {
  		const {uid, firebaseSet} = context
  		yield firebaseSet(`/users/${uid}/games/${ref}`, null)
  	}
  },
  reducer: {
  	setOrderType: (state, orderBy) => ({orderBy}),
	  setOrder: (state, order) => ({order}),
	  setItems: (state, items) => ({items})
  }
})
<|endoftext|>


<commit_before>define(function () {
  return function (Parsley) {
    describe('ParsleyAbstract', function () {
      it('should provide a actualizeOptions() method', function () {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element', { foo: 'bar' });
        expect(parsleyField.options).have.key('pattern');
        expect(parsleyField.options).have.key('required');

        $('#element').removeAttr('data-parsley-pattern');
        parsleyField.actualizeOptions();

        expect(parsleyField.options).not.to.have.key('pattern');
        expect(parsleyField.options).have.key('required');
      });
      it('should use subscribe()', function (done) {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element');

        parsleyField.subscribe('parsley:field:foo', function (instance, val) {
          expect(instance.__id__).to.be(parsleyField.__id__);
          expect(val).to.be('baz');
          done();
        });

        $.emit('parsley:field:foo', 'bar');
        $.emit('parsley:field:foo', parsleyField, 'baz');
      });
      it.skip('should use unsubscribe()');
      it('should use reset() on field', function () {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element');
        parsleyField.validate();
        expect($('#parsley-id-' + parsleyField.__id__ + ' li').length).to.be(1);

        parsleyField.reset();
        expect($('#parsley-id-' + parsleyField.__id__ + ' li').length).to.be(0);
      });
      it('should use reset() on form', function (done) {
        $('body').append(
          '<form id="element">'                           +
            '<input id="field1" type="text" required />'  +
            '

'                     +
            '<textarea id="field2"></textarea>'           +
          '</form>');
        var parsleyForm = new Parsley($('#element'));
        parsleyForm.validate();
        expect($('#parsley-id-' + $('#field1').psly().__id__ + ' li').length).to.be(1);

        parsleyForm.subscribe('parsley:form:reset', function () {
          done();
        });

        parsleyForm.reset();
        expect($('#parsley-id-' + $('#field1').psly().__id__ + ' li').length).to.be(0);
      });
      it('should use destroy() on field', function (done) {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element');

        parsleyField.subscribe('parsley:field:destroy', function () {
          done();
        });

        expect($('#element').data('Parsley')).to.have.key('__class__');
        expect($('#element').data('Parsley').__class__).to.be('ParsleyField');
        parsleyField.destroy();
        expect($('#element').data('Parsley')).to.be(undefined);
      });
      it('should use destroy() on form', function (done) {
        var triggered = 0;

        $('body').append(
          '<form id="element">'                 +
            '<input id="field1" type="text"/>'  +
            '

'           +
            '<textarea id="field2"></textarea>' +
          '</form>');
        var parsleyForm = new Parsley($('#element'));
        var fieldEventsCount = 0;

        // Test that a subscribed field event on parent form would be triggered by fields too
        // Here we only have field1 and field2 as valid parsley fields
        parsleyForm.subscribe('parsley:field:destroy', function () {
          fieldEventsCount++;
        });

        parsleyForm.subscribe('parsley:form:destroy', function () {
          // we properly triggered before the 2 field:destroy events for this form
          expect(fieldEventsCount).to.be(2);

          // we should never enter here since parsley form instance is destroyed
          $.listen('parsley:form:validate', function () {
            expect(true).to.be(false);
          });

          // test that a submit event does not trigger parsley validation anymore
          $('#element').on('submit', function (e) {
            e.preventDefault();

            expect($('#element').data('Parsley')).to.be(undefined);
            expect($('#field1').data('Parsley')).to.be(undefined);

            $.unsubscribeAll('parsley:form:validate');
            done();
          });

          $('#element').submit();
        });

        expect($('#element').data('Parsley')).to.have.key('__class__');
        expect($('#element').data('Parsley').__class__).to.be('ParsleyForm');
        expect($('#field1').data('Parsley')).to.have.key('__class__');
        expect($('#field1').data('Parsley').__class__).to.be('ParsleyField');

        parsleyForm.destroy();
      });
      afterEach(function () {
        if ($('#element').length)
          $('#element').remove();

        if ($('.parsley-errors-list').length)
          $('.parsley-errors-list').remove();
      });
    });
  };
});
<commit_msg>- unskipped `unsubscribe()` test<commit_after>define(function () {
  return function (Parsley) {
    describe('ParsleyAbstract', function () {
      it('should provide a actualizeOptions() method', function () {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element', { foo: 'bar' });
        expect(parsleyField.options).have.key('pattern');
        expect(parsleyField.options).have.key('required');

        $('#element').removeAttr('data-parsley-pattern');
        parsleyField.actualizeOptions();

        expect(parsleyField.options).not.to.have.key('pattern');
        expect(parsleyField.options).have.key('required');
      });
      it('should use subscribe()', function (done) {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element');

        parsleyField.subscribe('parsley:field:foo', function (instance, val) {
          expect(instance.__id__).to.be(parsleyField.__id__);
          expect(val).to.be('baz');
          done();
        });

        $.emit('parsley:field:foo', 'bar');
        $.emit('parsley:field:foo', parsleyField, 'baz');
      });
      it('should use unsubscribe()', function () {
        $('body').append('<input type="email" id="element" />');
        var parsleyInstance = $('#element').parsley();
        parsleyInstance.subscribe('foo', function () { return 'bar'; });
        expect($.subscribed().foo.length).to.be(1);
        parsleyInstance.unsubscribe('foo');
        expect($.subscribed().foo.length).to.be(0);
      });
      it('should use reset() on field', function () {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element');
        parsleyField.validate();
        expect($('#parsley-id-' + parsleyField.__id__ + ' li').length).to.be(1);

        parsleyField.reset();
        expect($('#parsley-id-' + parsleyField.__id__ + ' li').length).to.be(0);
      });
      it('should use reset() on form', function (done) {
        $('body').append(
          '<form id="element">'                           +
            '<input id="field1" type="text" required />'  +
            '

'                     +
            '<textarea id="field2"></textarea>'           +
          '</form>');
        var parsleyForm = new Parsley($('#element'));
        parsleyForm.validate();
        expect($('#parsley-id-' + $('#field1').psly().__id__ + ' li').length).to.be(1);

        parsleyForm.subscribe('parsley:form:reset', function () {
          done();
        });

        parsleyForm.reset();
        expect($('#parsley-id-' + $('#field1').psly().__id__ + ' li').length).to.be(0);
      });
      it('should use destroy() on field', function (done) {
        $('body').append('<input type="email" data-parsley-pattern="[A-F][0-9]{5}" data-parsley-required id="element" />');
        var parsleyField = new Parsley('#element');

        parsleyField.subscribe('parsley:field:destroy', function () {
          done();
        });

        expect($('#element').data('Parsley')).to.have.key('__class__');
        expect($('#element').data('Parsley').__class__).to.be('ParsleyField');
        parsleyField.destroy();
        expect($('#element').data('Parsley')).to.be(undefined);
      });
      it('should use destroy() on form', function (done) {
        var triggered = 0;

        $('body').append(
          '<form id="element">'                 +
            '<input id="field1" type="text"/>'  +
            '

'           +
            '<textarea id="field2"></textarea>' +
          '</form>');
        var parsleyForm = new Parsley($('#element'));
        var fieldEventsCount = 0;

        // Test that a subscribed field event on parent form would be triggered by fields too
        // Here we only have field1 and field2 as valid parsley fields
        parsleyForm.subscribe('parsley:field:destroy', function () {
          fieldEventsCount++;
        });

        parsleyForm.subscribe('parsley:form:destroy', function () {
          // we properly triggered before the 2 field:destroy events for this form
          expect(fieldEventsCount).to.be(2);

          // we should never enter here since parsley form instance is destroyed
          $.listen('parsley:form:validate', function () {
            expect(true).to.be(false);
          });

          // test that a submit event does not trigger parsley validation anymore
          $('#element').on('submit', function (e) {
            e.preventDefault();

            expect($('#element').data('Parsley')).to.be(undefined);
            expect($('#field1').data('Parsley')).to.be(undefined);

            $.unsubscribeAll('parsley:form:validate');
            done();
          });

          $('#element').submit();
        });

        expect($('#element').data('Parsley')).to.have.key('__class__');
        expect($('#element').data('Parsley').__class__).to.be('ParsleyForm');
        expect($('#field1').data('Parsley')).to.have.key('__class__');
        expect($('#field1').data('Parsley').__class__).to.be('ParsleyField');

        parsleyForm.destroy();
      });
      afterEach(function () {
        if ($('#element').length)
          $('#element').remove();

        if ($('.parsley-errors-list').length)
          $('.parsley-errors-list').remove();
      });
    });
  };
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

console.log ('Trustroots admin shell: show threads marked as not in spirit');

// Ensuring that we're in the right directory
process.chdir(__dirname);
process.chdir('../../');

var _ = require('lodash'),
    async = require('async'),
    mongoose = require('mongoose'),
    path = require('path'),
    mongooseService = require(path.resolve('config/lib/mongoose'));

mongooseService.connect();
mongooseService.loadModels();

var Message = mongoose.model('Message'),
    Thread = mongoose.model('Thread'),
    User = mongoose.model('User'),
    ReferenceThread = mongoose.model('ReferenceThread');

var htmlFormat = function(s) {
  // Quick'n'dirty way of ditching HTML
  return (s.replace(/\<.*?\>/gi, ''));
}

var showMessage = function(id) {
  Message.find(
    {'_id': id},
    function(err, docs) {
      _.map(docs, function(m) {
        console.log(m.userFrom);
        console.log(m.userTo);
        console.log(htmlFormat(m.content));
      })
    }
  )
}

var showThread = function(id) {
  Thread.find(
    {'_id': id},
    function(err, docs) {
      _.map(docs, function(t) {
        showMessage(t.message);
      })
    }
  )
}

ReferenceThread.find(
  {'reference': 'no'},
  function(err, docs) {
    _.map(docs, function(rt) {
      showThread(rt.thread);
    });
  }
);

// At some point it should disconnect. Not yet sure how to do this.
// mongooseService.disconnect();
<commit_msg>badRefs.js: a bit nicer already, almost usable even :)<commit_after>#!/usr/bin/env node

console.log ('Trustroots admin shell: show threads marked as not in spirit');

// Ensuring that we're in the right directory
process.chdir(__dirname);
process.chdir('../../');

var _ = require('lodash'),
    async = require('async'),
    mongoose = require('mongoose'),
    path = require('path'),
    mongooseService = require(path.resolve('config/lib/mongoose'));

// TODO: turn off mongoose logging feedback

mongooseService.connect();
mongooseService.loadModels();
mongoose.set('debug', false);

var Message = mongoose.model('Message'),
    Thread = mongoose.model('Thread'),
    User = mongoose.model('User'),
    ReferenceThread = mongoose.model('ReferenceThread');

const htmlFormat = function(s) {
  // Quick'n'dirty way of ditching HTML
  return (s.replace(/\<.*?\>/gi, ''));
}

const findUser = async function (userId) {
  try {
    return await User.findOne({_id: userId});
  }
  catch(err) {
    console.log(err);
  }
}

var showMessage = function(id) {
  Message.find(
    {'_id': id},
    function(err, docs) {
      _.map(docs, async function(m) {
        var stuff = [await findUser(m.userFrom),
                     await findUser(m.userTo),
                     htmlFormat(m.content)
                    ];
        var promise = await Promise.all(stuff);
        console.log('from ' + promise[0].username + '\n',
                    'to ' + promise[1].username + '\n',
                    promise[2] + '\n\n');
      })
    }
  )
}

var showThread = async function(id) {
  Thread.find(
    {'_id': id},
    function(err, docs) {
      _.map(docs, function(t) {
        showMessage(t.message);
      })
    }
  )
}

// At some point it should disconnect. Not yet sure how to do this.
const disconnect = async function() {
  await mongooseService.disconnect();
}

ReferenceThread.find(
  {'reference': 'no'},
  async function(err, docs) {
    await _.map(_.slice(docs, 0, 10), async function(rt) {
      await showThread(rt.thread);
    });
  }
);
<|endoftext|>


<commit_before>import 'parsleyjs/src/parsley';

// Configure Parsley
const parsleyConfig = {
    errorsWrapper: '

',
    errorTemplate: '

',
    errorClass: 'error',
    classHandler: function (parsleyField) {
        return parsleyField.$element.closest('.form-row');
    },
    errorsContainer: function (parsleyField) {
        return parsleyField.$element.closest('.form-row').find('.parsley-errors');
    }
};

const form = $('form[name="dashboard_bundle_entity_type"]');

if (form.length) {
    // Use parsley for url and email verification
    $(form).parsley(parsleyConfig);

    // Custom validation is added to show the attribute motivation warnings
    $('input.motivation').on('keyup', function () {

        const parent = $(this).closest('.form-row');
        const errorContainer = parent.parents('.form-row').find('.parsley-errors').first();
        const translatedValidationText = $(this).data('motivation-keep-talking');

        if ($(this).val().length <= 10) {
            errorContainer.parent('.form-row').addClass('warning');
            errorContainer.text(translatedValidationText);
        } else {
            errorContainer.parent('.form-row').removeClass('warning');
            errorContainer.text('');
        }
    });
}

// Add a stricter url validator, this one ensures the protocol is set on the input value
window.Parsley.addValidator('urlstrict', function (value, requirement) {
    var regExp = /^(https?|s?ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
    return '' !== value ? regExp.test( value ) : false;
}, 32).addMessage('en', 'urlstrict', 'This value should be a valid url.');<commit_msg>Prevent validation on unwanted events<commit_after>import 'parsleyjs/src/parsley';

// Configure Parsley
const parsleyConfig = {
    errorsWrapper: '

',
    errorTemplate: '

',
    errorClass: 'error',
    classHandler: function (parsleyField) {
        return parsleyField.$element.closest('.form-row');
    },
    errorsContainer: function (parsleyField) {
        return parsleyField.$element.closest('.form-row').find('.parsley-errors');
    }
};

const form = $('form[name="dashboard_bundle_entity_type"]');

if (form.length) {
    // Use parsley for url and email verification
    $(form).parsley(parsleyConfig);

    // Custom validation is added to show the attribute motivation warnings
    $('input.motivation').on('keyup', function () {

        const parent = $(this).closest('.form-row');
        const errorContainer = parent.parents('.form-row').find('.parsley-errors').first();
        const translatedValidationText = $(this).data('motivation-keep-talking');

        if ($(this).val().length <= 10) {
            errorContainer.parent('.form-row').addClass('warning');
            errorContainer.text(translatedValidationText);
        } else {
            errorContainer.parent('.form-row').removeClass('warning');
            errorContainer.text('');
        }
    });

    // When clicking import, save or cancel, do not validate the form using frontend validation, so disable parsley and
    // submit the form.
    $('#dashboard_bundle_entity_type_metadata_importButton, #dashboard_bundle_entity_type_save, #dashboard_bundle_entity_type_cancel').click(function(){
        $(form).parsley().destroy();
        $(form).submit();
    });
}

// Add a stricter url validator, this one ensures the protocol is set on the input value
window.Parsley.addValidator('urlstrict', function (value, requirement) {
    var regExp = /^(https?|s?ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
    return '' !== value ? regExp.test( value ) : false;
}, 32).addMessage('en', 'urlstrict', 'This value should be a valid url.');<|endoftext|>


<commit_before>// This file is part of coffeekit.  for licensing information, see the LICENSE file

console.log ("UIButton");
exports.UIButton = UIButton = UIControl.extendClass ("UIButton", {

  // Creating Buttons
  buttonWithType:                     objc.staticSelector("buttonWithType:"),

  // Configuring Button Title
  titleLabel:                         objc.instanceProperty(),
  reversesTitleShadowWhenHighlighted: objc.instanceProperty(),
  setTitle:            		      objc.instanceSelector("setTitle:forState:"),
  getTitle:            		      objc.instanceSelector("titleForState:"),
  setTitleColor:       		      objc.instanceSelector("setTitleColor:forState:").makeUIAppearance(),
  getTitleColor:       		      objc.instanceSelector("titleColorForState:").makeUIAppearance(),
  setTitleShadowColor: 		      objc.instanceSelector("setTitleShadowColor:forState:"),
  getTitleShadowColor: 		      objc.instanceSelector("titleShadowColorForState:"),

  font:              objc.instanceProperty(), // Deprecated in iOS 3.0
  lineBreakMode:     objc.instanceProperty(), // Deprecated in iOS 3.0
  titleShadowOffset: objc.instanceProperty(), // Deprecated in iOS 3.0

  // Configuring Button Presentation
  adjustsImageWhenHighlighted: objc.instanceProperty(),
  adjustsImageWhenDisabled:    objc.instanceProperty(),
  showsTouchWhenHighlighted:   objc.instanceProperty(),

  setBackgroundImage:  objc.instanceSelector("setBackgroundImage:forState:"),
  getBbackgroundImage: objc.instanceSelector("backgroundImageForState:"),
  setImage:            objc.instanceSelector("setImage:forState:"),
  getImage:            objc.instanceSelector("imageForState:"),
  tintColor:           objc.instanceProperty().makeUIAppearance(),

  // Configuring Edge Insets
  contentEdgeInsets:   objc.instanceProperty(),
  titleEdgeInsets:     objc.instanceProperty(),
  imageEdgeInsets:     objc.instanceProperty(),

  // Getting the Current State
  buttonType:              objc.instanceProperty(),
  currentTitle:            objc.instanceProperty(),
  currentTitleColor:       objc.instanceProperty(),
  currentTitleShadowColor: objc.instanceProperty(),
  currentImage:            objc.instanceProperty(),
  currentBackgroundImage:  objc.instanceProperty(),
  imageView:               objc.instanceProperty(),

  // Getting Dimensions
  backgroundRectForBounds: objc.instanceSelector("backgroundRectForBounds:"),
  contentRectForBounds:    objc.instanceSelector("contentRectForBounds:"),
  titleRectForContentRect: objc.instanceSelector("titleRectForContentRect:"),
  imageRectForContentRect: objc.instanceSelector("imageRectForContentRect:"),

  clicked: objc.instanceProperty({
				 set: function (v) {
				   if (v) {
				     this.proxy = new UIControlProxy(v);
				     this.addTarget(this.proxy, this.proxy.proxyAction, UIControlEvent.TouchUpInside);
				   }
				   else {
				     this.proxy = null;
				   }
				 },
				 get: null // this should really be an actual getter that returns the callback...
			       })
});
<commit_msg>add convenience UIButton.title property that uses setTitle with UIControlState.normal<commit_after>// This file is part of coffeekit.  for licensing information, see the LICENSE file

console.log ("UIButton");
exports.UIButton = UIButton = UIControl.extendClass ("UIButton", {

  // Creating Buttons
  buttonWithType:                     objc.staticSelector("buttonWithType:"),

  // Configuring Button Title
  titleLabel:                         objc.instanceProperty(),
  reversesTitleShadowWhenHighlighted: objc.instanceProperty(),
  setTitle:            		      objc.instanceSelector("setTitle:forState:"),
  getTitle:            		      objc.instanceSelector("titleForState:"),
  setTitleColor:       		      objc.instanceSelector("setTitleColor:forState:").makeUIAppearance(),
  getTitleColor:       		      objc.instanceSelector("titleColorForState:").makeUIAppearance(),
  setTitleShadowColor: 		      objc.instanceSelector("setTitleShadowColor:forState:"),
  getTitleShadowColor: 		      objc.instanceSelector("titleShadowColorForState:"),

  font:              objc.instanceProperty(), // Deprecated in iOS 3.0
  lineBreakMode:     objc.instanceProperty(), // Deprecated in iOS 3.0
  titleShadowOffset: objc.instanceProperty(), // Deprecated in iOS 3.0

  // Configuring Button Presentation
  adjustsImageWhenHighlighted: objc.instanceProperty(),
  adjustsImageWhenDisabled:    objc.instanceProperty(),
  showsTouchWhenHighlighted:   objc.instanceProperty(),

  setBackgroundImage:  objc.instanceSelector("setBackgroundImage:forState:"),
  getBbackgroundImage: objc.instanceSelector("backgroundImageForState:"),
  setImage:            objc.instanceSelector("setImage:forState:"),
  getImage:            objc.instanceSelector("imageForState:"),
  tintColor:           objc.instanceProperty().makeUIAppearance(),

  // Configuring Edge Insets
  contentEdgeInsets:   objc.instanceProperty(),
  titleEdgeInsets:     objc.instanceProperty(),
  imageEdgeInsets:     objc.instanceProperty(),

  // Getting the Current State
  buttonType:              objc.instanceProperty(),
  currentTitle:            objc.instanceProperty(),
  currentTitleColor:       objc.instanceProperty(),
  currentTitleShadowColor: objc.instanceProperty(),
  currentImage:            objc.instanceProperty(),
  currentBackgroundImage:  objc.instanceProperty(),
  imageView:               objc.instanceProperty(),

  // Getting Dimensions
  backgroundRectForBounds: objc.instanceSelector("backgroundRectForBounds:"),
  contentRectForBounds:    objc.instanceSelector("contentRectForBounds:"),
  titleRectForContentRect: objc.instanceSelector("titleRectForContentRect:"),
  imageRectForContentRect: objc.instanceSelector("imageRectForContentRect:"),

  clicked: objc.instanceProperty({
				 set: function (v) {
				   if (v) {
				     this.proxy = new UIControlProxy(v);
				     this.addTarget(this.proxy, this.proxy.proxyAction, UIControlEvent.TouchUpInside);
				   }
				   else {
				     this.proxy = null;
				   }
				 },
				 get: null // this should really be an actual getter that returns the callback...
  }),

  // handle accessors for normal state
  title: objc.instanceProperty({
      set: function (v) { this.setTitle(v, UIControlState.normal); },
      get: function () { return this.getTitle(UIControlState.normal); }
  })

});
<|endoftext|>


<commit_before>/*global define*/
/*jslint white: true*/
define(['jquery',
        'narrativeConfig',
        'kbwidget',
        'bootstrap'],
function($, Config) {
    'use strict';
    $.KBWidget({
        name: 'kbaseNarrativeCellMenu',
        parent: 'kbaseWidget',
        options: {cell: null, kbWidget: null, kbWidgetType: null},
        genId: function () {
            if (!this.lastId) {
                this.lastId = 1;
            } else {
                this.lastId += 1;
            }
            return 'kbaseNarrativeCellMenu_' + this.lastId;
        },
        init: function (options) {
            var self = this;
            this._super(options);

            // console.log(['cell menu', this.options.cell]);
//            var outputPane = this.$elem.closest('.cell').find('.inner_cell > div:nth-child(2)').get(0);
//            if (!outputPane.id) {
//                outputPane.id = this.genId();
//            }
//            this.$elem.data('ouputPaneId', outputPane.id);

            var $deleteBtn = $('<button type="button" class="btn btn-default btn-xs" data-toggle="tooltip" data-placement="left" Title="Delete Cell">')
                .append($(''))
                .click($.proxy(function () {
                    this.trigger('deleteCell.Narrative', Jupyter.notebook.get_selected_index());
                }, this)),
                $menuBtn = $('<button type="button" data-toggle="dropdown" aria-haspopup="true" class="btn btn-default btn-xs">')
                .append($('')),
                // $collapseBtn = $('<button class="btn btn-default" role="button" data-toggle="collapse" href="#' + outputPane.id + '" aria-controls="' + $outputPane.id + '">Open</button>'),
                $collapseBtn = $('<button type="button" class="btn btn-default btn-xs" role="button" data-button="toggle"></button>')
                .on('click', function () {
                    self.$elem.trigger('toggle.toolbar');
                });

            var cell = this.options.cell;

            /*
             * Each cell type unfortunately has a different top level layout.
             * Not that it matters, but I don't see why there isn't a uniform layout
             * for the primary layout areas - prompt, toolbar, body, as they exist
             * now, and another nice one would be a message/notification are
             */
            this.$elem.on('toggle.toolbar', function () {
                var $cellNode = self.$elem.closest('.cell');
                $cellNode
                    .trigger('toggle.cell');
            });

//            this.$elem.on('toggle-output-all', function () {
//                self.$elem
//                    .closest('.notebook')
//                    .find('.cell .inner_cell > div:nth-child(3)')
//                    .toggle();
//            });

            this.$menu = $('
')
                .addClass('dropdown-menu dropdown-menu-right');

            if (Config.debug) {
                this.addMenuItem({
                    icon: 'fa fa-code',
                    text: 'View Job Submission',
                    action: function () {
                        var metadata = Jupyter.notebook.get_selected_cell().metadata,
                            stackTrace = [],
                            cell = Jupyter.notebook.insert_cell_below('code');
                        if (metadata['kb-cell'] && metadata['kb-cell'].stackTrace) {
                            stackTrace = metadata['kb-cell'].stackTrace;
                        }
                        console.log(stackTrace);
                        if (stackTrace instanceof Array) {
                            cell.set_text('job_info=' + stackTrace[stackTrace.length - 1] + '\njob_info');
                            Jupyter.notebook.get_selected_cell().execute();
                        } else {
                            cell.set_text('job_info=' + stackTrace);
                        }
                    }
                });
            }

            this.addMenuItem({
                icon: 'fa fa-arrow-up',
                text: 'Move Cell Up',
                action: function () {
                    Jupyter.notebook.move_cell_up();
                }
            });

            this.addMenuItem({
                icon: 'fa fa-arrow-down',
                text: 'Move Cell Down',
                action: function () {
                    Jupyter.notebook.move_cell_down();
                }
            });

            this.addMenuItem({
                icon: 'fa fa-caret-square-o-up',
                text: 'Insert Cell Above',
                action: function () {
                    Jupyter.notebook.insert_cell_above('markdown');
                }
            });

            this.addMenuItem({
                icon: 'fa fa-caret-square-o-down',
                text: 'Insert Cell Below',
                action: function () {
                    Jupyter.notebook.insert_cell_below('markdown');
                }
            });

            // only add this if it was controlled by a KBase Widget
            if (this.options.kbWidget && this.options.kbWidgetType) {
                this.addMenuItem({
                    icon: 'fa fa-copy',
                    text: 'Duplicate Cell',
                    action: $.proxy(function () {
                        // get the current state, and clear it of its running state
                        var kbWidget = options.kbWidget,
                            currentState = kbWidget.getState();
                        if (this.options.kbWidgetType === 'method') {
                            // put the method in the narrative
                            this.trigger('methodClicked.Narrative', kbWidget.method);

                            // the method initializes an internal method input widget, but in an async way
                            // so we have to wait and check when that is done.  When it is, we can update state
                            var newCell = Jupyter.notebook.get_selected_cell();
                            var newWidget = $('#' + $(newCell.get_text())[0].id).kbaseNarrativeMethodCell();
                            var updateState = function (state) {
                                if (newWidget.$inputWidget) {
                                    // if the $inputWidget is not null, we are good to go, so set the state
                                    newWidget.loadState(currentState.params);
                                } else {
                                    // not ready yet, keep waiting
                                    window.setTimeout(updateState, 500);
                                }
                            };
                            window.setTimeout(updateState, 50);
                        }
                    }, this)
                });
            }

            // if (this.options.cell && this.options.cell.metadata['kb-cell'] === undefined) {
            //     this.addMenuItem({
            //         icon: 'fa fa-terminal',
            //         text: 'Toggle Cell Type',
            //         action: function() {
            //             if (this.options.cell.cell_type === "markdown") {
            //                 Jupyter.notebook.to_code();
            //             }
            //             else {

            //             }
            //         },
            //         disable: true
            //     });
            // }
//
//            this.addMenuItem({
//                icon: 'fa fa-trash-o',
//                text: 'Delete Cell',
//                action: $.proxy(function () {
//                    this.trigger('deleteCell.Narrative', Jupyter.notebook.get_selected_index());
//                }, this)
//            });

            var self = this;

            // Job State Icon
            this.$jobStateIcon = $('');

            // this shows whether the app is running
            this.$runningIcon = $("")
                .addClass("fa fa-circle-o-notch fa-spin")
                .css({color: "rgb(42,121,191)"})
                .hide();
            this.$elem.data('runningIcon', this.$runningIcon);
            this.$elem.on('start-running', function () {
                self.$runningIcon.show();
            });
            this.$elem.on('stop-running', function () {
                self.$runningIcon.hide();
            });
            this.$elem.on('runningIndicator.toolbar', function (e, data) {
//                if (data.enabled) {
//                    self.$runningIcon.show();
//                } else {
//                    self.$runningIcon.hide();
//                }
//                if (data.enabled) {
//                    self.$jobStateIcon.html(makeIcon({
//                        class: 'wifi',
//                        color: 'orange',
//                        spin: true,
//                        label: 'Sending'
//                    }));
//                }
            });

            function makeIcon(icon) {
                var spinClass = icon.spin ? 'fa-spin' : '',
                    label = icon.label ? icon.label + ' ' : '',
                    iconHtml = ''+label+'';
                return iconHtml;
            }

            this.$elem.on('run-state.toolbar', function (e, data) {
                switch (data.status) {
                    case 'submitted':
                        self.$jobStateIcon.html(makeIcon({
                            class: 'asterisk',
                            color: 'orange',
                            spin: true,
                            label: 'Queued'
                        }));
                        break;
                    case 'running':
                        self.$jobStateIcon.html(makeIcon({class: 'circle-o-notch', color: 'blue', spin: true, label: 'Running'}));
                        break;
                    case 'complete':
                        self.$jobStateIcon.html(makeIcon({class: 'check', color: 'green', label: 'Finished'}));
                        break;
                    case 'error':
                        self.$jobStateIcon.html('ERROR');
                        break;
                    default:
                        self.$jobStateIcon.html('?: ' + data.status);
                }
            })

            this.$elem.on('job-state.toolbar', function (e, data) {
                switch (data.status) {
                    case 'queued':
                        self.$jobStateIcon.html(makeIcon({
                            class: 'asterisk',
                            color: 'orange',
                            spin: true,
                            label: 'Queued'
                        }));
                        break;
                    case 'in-progress':
                    case 'running':
                        self.$jobStateIcon.html(makeIcon({class: 'circle-o-notch', color: 'blue', spin: true, label: 'Running'}));
                        break;
                    case 'error':
                        self.$jobStateIcon.html('ERROR');
                        break;
                    case 'complete':
                    case 'completed':
                        self.$jobStateIcon.html(makeIcon({class: 'check', color: 'green', label: 'Finished'}));
                        break;
                    default:
                        self.$jobStateIcon.html('?: ' + data.status);
                }
            });

            // this shows on error
            this.$errorIcon = $("")
                .addClass("fa fa-exclamation-triangle")
                .css({color: "red"})
                .hide();
            this.$elem.data('errorIcon', this.$errorIcon);
            this.$elem.on('show-error', function () {
                self.$errorIcon.hide();
            });
            this.$elem.on('hide-error', function () {
                self.$errorIcon.hide();
            });
            this.$elem.on('errorIndicator.toolbar', function (e, data) {
                if (data.enabled) {
                    self.$errorIcon.show();
                } else {
                    self.$errorIcon.hide();
                }
            });

            var $dropdownMenu = $('')
                .append($menuBtn)
                .append(this.$menu);

            this.$elem.append(
                $('
')
                .append($('
')
                    .append($('
')
                        .append($('
')
                            .append($collapseBtn)
                        )
                    )
                    .append($('
')
                        .append($('
')
                            .append('') // title here
                        )
                    )
                    .append($('
')
                        .append($('
')
                            .append(this.$runningIcon)
                            .append(this.$errorIcon)
                            .append(this.$jobStateIcon)
                            .append($deleteBtn)
                            .append($dropdownMenu)
                        )
                    )
                )
            );
            $deleteBtn.tooltip();

            /*
             * Events emitted by the cell to indicate that the toolbar should be
             * selected or unselected. Or rather, that the cell has been selected
             * or unselected.
             */
            this.$elem.on('selected.toolbar', function (e) {
                e.stopPropagation();
                $deleteBtn.removeClass('disabled');
                $dropdownMenu.find('.btn').removeClass('disabled');
            });
            this.$elem.on('unselected.toolbar', function (e) {
                e.stopPropagation();
                $deleteBtn.addClass('disabled');
                $dropdownMenu.find('.btn').addClass('disabled');
            });

            /*
             * A workaround to provide the default state to buttons, et al.
             * jupyter should call select/unselect on each cell as they are added,
             * to allow the cell to set up state. There is an unselect cell method,
             * but no unselect event upon which to trigger actions.
             * Actually, there should be a 'can unselect' as well, since a cell
             * might no be happy with being left in an unfinished state.
            */
            $dropdownMenu.find('.btn').addClass('disabled');
            $deleteBtn.addClass('disabled');

            // Set up title.
            var $titleNode = this.$elem.find('[data-element="title"]');
            this.$elem.on('set-title.toolbar', function (e, title) {
                e.stopPropagation();
//                if (typeof title === 'object') {
//                    if (title.suffix) {
//                        $titleNode.html($titleNode.html() + '(' + title.suffix + ')');
//                    }
//                } else {
//                    $titleNode.html(title);
//                }
                $titleNode.html(title);
            });

            /* And an icon -- hack to go into the input prompt for now...
             *
             * Get the cell node from the cell passed in, rather than use our
             * node and find it.
             * $cell =  = $(options.cell.element);
            */

            this.$elem.on('set-icon.toolbar', function (e, icon) {
                var $cell = $(self.options.cell.element),
                    $iconNode = $cell.find('.prompt');
                e.stopPropagation();
                var wrapped = '
' + icon + '
';
                $iconNode.html(wrapped);
            });

            var $cell = (options && options.cell && $(options.cell.element)) || self.$elem.closest('.cell'),
                icon = $cell.data('icon');

            if (icon) {
                this.$elem.trigger('set-icon.toolbar', [icon]);
            }

            // but maybe have the title already.
            var title = $cell.data('title');
            // var title = cell.metadata.kbstate.title;
            if (title) {
                this.$elem.trigger('set-title.toolbar', [title]);
            }

            // Rendering done, now add events.

            // Events done, not call actions.
            // this.options.cell.celltoobar.renderToggleState();

            return this;
        },
        addMenuItem: function (item) {
            var label = '';
            if (item.icon) {
                label += ' ';
            }
            if (item.text) {
                label += ' ' + item.text;
            }
            var $item = $('')
                .append(label)
                .click($.proxy(function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                    if (!item.disable) {
                        if (item.action)
                            item.action();
                        this.$menu.dropdown('toggle');
                    }
                }, this));
            var $itemElem = $('
').append($item);
            if (item.disable) {
                $itemElem.addClass('disabled');
            }
            this.$menu.append($itemElem);
        }
    });
});
<commit_msg>Fixed wrong cell being deleted.<commit_after>/*global define*/
/*jslint white: true*/
define(['jquery',
        'narrativeConfig',
        'kbwidget',
        'bootstrap'],
function($, Config) {
    'use strict';
    $.KBWidget({
        name: 'kbaseNarrativeCellMenu',
        parent: 'kbaseWidget',
        options: {cell: null, kbWidget: null, kbWidgetType: null},
        genId: function () {
            if (!this.lastId) {
                this.lastId = 1;
            } else {
                this.lastId += 1;
            }
            return 'kbaseNarrativeCellMenu_' + this.lastId;
        },
        init: function (options) {
            var self = this;
            this._super(options);

            var $deleteBtn = $('<button type="button" class="btn btn-default btn-xs" data-toggle="tooltip" data-placement="left" Title="Delete Cell">')
                .append($(''))
                .click($.proxy(function () {
                    this.trigger('deleteCell.Narrative', Jupyter.notebook.find_cell_index(this.options.cell));
                }, this));

            var $menuBtn = $('<button type="button" data-toggle="dropdown" aria-haspopup="true" class="btn btn-default btn-xs">')
                .append($(''));

            var $collapseBtn = $('<button type="button" class="btn btn-default btn-xs" role="button" data-button="toggle"></button>')
                .on('click', function () {
                    self.$elem.trigger('toggle.toolbar');
                });

            var cell = this.options.cell;

            /*
             * Each cell type unfortunately has a different top level layout.
             * Not that it matters, but I don't see why there isn't a uniform layout
             * for the primary layout areas - prompt, toolbar, body, as they exist
             * now, and another nice one would be a message/notification area.
             */
            this.$elem.on('toggle.toolbar', function () {
                var $cellNode = self.$elem.closest('.cell');
                $cellNode
                    .trigger('toggle.cell');
            });

//            this.$elem.on('toggle-output-all', function () {
//                self.$elem
//                    .closest('.notebook')
//                    .find('.cell .inner_cell > div:nth-child(3)')
//                    .toggle();
//            });

            this.$menu = $('
')
                .addClass('dropdown-menu dropdown-menu-right');

            if (Config.debug) {
                this.addMenuItem({
                    icon: 'fa fa-code',
                    text: 'View Job Submission',
                    action: function () {
                        var metadata = Jupyter.notebook.get_selected_cell().metadata,
                            stackTrace = [],
                            cell = Jupyter.notebook.insert_cell_below('code');
                        if (metadata['kb-cell'] && metadata['kb-cell'].stackTrace) {
                            stackTrace = metadata['kb-cell'].stackTrace;
                        }
                        console.log(stackTrace);
                        if (stackTrace instanceof Array) {
                            cell.set_text('job_info=' + stackTrace[stackTrace.length - 1] + '\njob_info');
                            Jupyter.notebook.get_selected_cell().execute();
                        } else {
                            cell.set_text('job_info=' + stackTrace);
                        }
                    }
                });
            }

            this.addMenuItem({
                icon: 'fa fa-arrow-up',
                text: 'Move Cell Up',
                action: function () {
                    Jupyter.notebook.move_cell_up();
                }
            });

            this.addMenuItem({
                icon: 'fa fa-arrow-down',
                text: 'Move Cell Down',
                action: function () {
                    Jupyter.notebook.move_cell_down();
                }
            });

            this.addMenuItem({
                icon: 'fa fa-caret-square-o-up',
                text: 'Insert Cell Above',
                action: function () {
                    Jupyter.notebook.insert_cell_above('markdown');
                }
            });

            this.addMenuItem({
                icon: 'fa fa-caret-square-o-down',
                text: 'Insert Cell Below',
                action: function () {
                    Jupyter.notebook.insert_cell_below('markdown');
                }
            });

            // only add this if it was controlled by a KBase Widget
            if (this.options.kbWidget && this.options.kbWidgetType) {
                this.addMenuItem({
                    icon: 'fa fa-copy',
                    text: 'Duplicate Cell',
                    action: $.proxy(function () {
                        // get the current state, and clear it of its running state
                        var kbWidget = options.kbWidget,
                            currentState = kbWidget.getState();
                        if (this.options.kbWidgetType === 'method') {
                            // put the method in the narrative
                            this.trigger('methodClicked.Narrative', kbWidget.method);

                            // the method initializes an internal method input widget, but in an async way
                            // so we have to wait and check when that is done.  When it is, we can update state
                            var newCell = Jupyter.notebook.get_selected_cell();
                            var newWidget = $('#' + $(newCell.get_text())[0].id).kbaseNarrativeMethodCell();
                            var updateState = function (state) {
                                if (newWidget.$inputWidget) {
                                    // if the $inputWidget is not null, we are good to go, so set the state
                                    newWidget.loadState(currentState.params);
                                } else {
                                    // not ready yet, keep waiting
                                    window.setTimeout(updateState, 500);
                                }
                            };
                            window.setTimeout(updateState, 50);
                        }
                    }, this)
                });
            }

            // if (this.options.cell && this.options.cell.metadata['kb-cell'] === undefined) {
            //     this.addMenuItem({
            //         icon: 'fa fa-terminal',
            //         text: 'Toggle Cell Type',
            //         action: function() {
            //             if (this.options.cell.cell_type === "markdown") {
            //                 Jupyter.notebook.to_code();
            //             }
            //             else {

            //             }
            //         },
            //         disable: true
            //     });
            // }
//
//            this.addMenuItem({
//                icon: 'fa fa-trash-o',
//                text: 'Delete Cell',
//                action: $.proxy(function () {
//                    this.trigger('deleteCell.Narrative', Jupyter.notebook.get_selected_index());
//                }, this)
//            });

            var self = this;

            // Job State Icon
            this.$jobStateIcon = $('');

            // this shows whether the app is running
            this.$runningIcon = $("")
                .addClass("fa fa-circle-o-notch fa-spin")
                .css({color: "rgb(42,121,191)"})
                .hide();
            this.$elem.data('runningIcon', this.$runningIcon);
            this.$elem.on('start-running', function () {
                self.$runningIcon.show();
            });
            this.$elem.on('stop-running', function () {
                self.$runningIcon.hide();
            });
            this.$elem.on('runningIndicator.toolbar', function (e, data) {
//                if (data.enabled) {
//                    self.$runningIcon.show();
//                } else {
//                    self.$runningIcon.hide();
//                }
//                if (data.enabled) {
//                    self.$jobStateIcon.html(makeIcon({
//                        class: 'wifi',
//                        color: 'orange',
//                        spin: true,
//                        label: 'Sending'
//                    }));
//                }
            });

            function makeIcon(icon) {
                var spinClass = icon.spin ? 'fa-spin' : '',
                    label = icon.label ? icon.label + ' ' : '',
                    iconHtml = ''+label+'';
                return iconHtml;
            }

            this.$elem.on('run-state.toolbar', function (e, data) {
                switch (data.status) {
                    case 'submitted':
                        self.$jobStateIcon.html(makeIcon({
                            class: 'asterisk',
                            color: 'orange',
                            spin: true,
                            label: 'Queued'
                        }));
                        break;
                    case 'running':
                        self.$jobStateIcon.html(makeIcon({class: 'circle-o-notch', color: 'blue', spin: true, label: 'Running'}));
                        break;
                    case 'complete':
                        self.$jobStateIcon.html(makeIcon({class: 'check', color: 'green', label: 'Finished'}));
                        break;
                    case 'error':
                        self.$jobStateIcon.html('ERROR');
                        break;
                    default:
                        self.$jobStateIcon.html('?: ' + data.status);
                }
            })

            this.$elem.on('job-state.toolbar', function (e, data) {
                switch (data.status) {
                    case 'queued':
                        self.$jobStateIcon.html(makeIcon({
                            class: 'asterisk',
                            color: 'orange',
                            spin: true,
                            label: 'Queued'
                        }));
                        break;
                    case 'in-progress':
                    case 'running':
                        self.$jobStateIcon.html(makeIcon({class: 'circle-o-notch', color: 'blue', spin: true, label: 'Running'}));
                        break;
                    case 'error':
                        self.$jobStateIcon.html('ERROR');
                        break;
                    case 'complete':
                    case 'completed':
                        self.$jobStateIcon.html(makeIcon({class: 'check', color: 'green', label: 'Finished'}));
                        break;
                    default:
                        self.$jobStateIcon.html('?: ' + data.status);
                }
            });

            // this shows on error
            this.$errorIcon = $("")
                .addClass("fa fa-exclamation-triangle")
                .css({color: "red"})
                .hide();
            this.$elem.data('errorIcon', this.$errorIcon);
            this.$elem.on('show-error', function () {
                self.$errorIcon.hide();
            });
            this.$elem.on('hide-error', function () {
                self.$errorIcon.hide();
            });
            this.$elem.on('errorIndicator.toolbar', function (e, data) {
                if (data.enabled) {
                    self.$errorIcon.show();
                } else {
                    self.$errorIcon.hide();
                }
            });

            var $dropdownMenu = $('')
                .append($menuBtn)
                .append(this.$menu);

            this.$elem.append(
                $('
')
                .append($('
')
                    .append($('
')
                        .append($('
')
                            .append($collapseBtn)
                        )
                    )
                    .append($('
')
                        .append($('
')
                            .append('') // title here
                        )
                    )
                    .append($('
')
                        .append($('
')
                            .append(this.$runningIcon)
                            .append(this.$errorIcon)
                            .append(this.$jobStateIcon)
                            .append($deleteBtn)
                            .append($dropdownMenu)
                        )
                    )
                )
            );
            $deleteBtn.tooltip();

            /*
             * Events emitted by the cell to indicate that the toolbar should be
             * selected or unselected. Or rather, that the cell has been selected
             * or unselected.
             */
            this.$elem.on('selected.toolbar', function (e) {
                e.stopPropagation();
                $deleteBtn.removeClass('disabled');
                $dropdownMenu.find('.btn').removeClass('disabled');
            });
            this.$elem.on('unselected.toolbar', function (e) {
                e.stopPropagation();
                $deleteBtn.addClass('disabled');
                $dropdownMenu.find('.btn').addClass('disabled');
            });

            /*
             * A workaround to provide the default state to buttons, et al.
             * jupyter should call select/unselect on each cell as they are added,
             * to allow the cell to set up state. There is an unselect cell method,
             * but no unselect event upon which to trigger actions.
             * Actually, there should be a 'can unselect' as well, since a cell
             * might no be happy with being left in an unfinished state.
            */
            $dropdownMenu.find('.btn').addClass('disabled');
            $deleteBtn.addClass('disabled');

            // Set up title.
            var $titleNode = this.$elem.find('[data-element="title"]');
            this.$elem.on('set-title.toolbar', function (e, title) {
                e.stopPropagation();
//                if (typeof title === 'object') {
//                    if (title.suffix) {
//                        $titleNode.html($titleNode.html() + '(' + title.suffix + ')');
//                    }
//                } else {
//                    $titleNode.html(title);
//                }
                $titleNode.html(title);
            });

            /* And an icon -- hack to go into the input prompt for now...
             *
             * Get the cell node from the cell passed in, rather than use our
             * node and find it.
             * $cell =  = $(options.cell.element);
            */

            this.$elem.on('set-icon.toolbar', function (e, icon) {
                var $cell = $(self.options.cell.element),
                    $iconNode = $cell.find('.prompt');
                e.stopPropagation();
                var wrapped = '
' + icon + '
';
                $iconNode.html(wrapped);
            });

            var $cell = (options && options.cell && $(options.cell.element)) || self.$elem.closest('.cell'),
                icon = $cell.data('icon');

            if (icon) {
                this.$elem.trigger('set-icon.toolbar', [icon]);
            }

            // but maybe have the title already.
            var title = $cell.data('title');
            // var title = cell.metadata.kbstate.title;
            if (title) {
                this.$elem.trigger('set-title.toolbar', [title]);
            }

            // Rendering done, now add events.

            // Events done, not call actions.
            // this.options.cell.celltoobar.renderToggleState();

            return this;
        },
        addMenuItem: function (item) {
            var label = '';
            if (item.icon) {
                label += ' ';
            }
            if (item.text) {
                label += ' ' + item.text;
            }
            var $item = $('')
                .append(label)
                .click($.proxy(function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                    if (!item.disable) {
                        if (item.action)
                            item.action();
                        this.$menu.dropdown('toggle');
                    }
                }, this));
            var $itemElem = $('
').append($item);
            if (item.disable) {
                $itemElem.addClass('disabled');
            }
            this.$menu.append($itemElem);
        }
    });
});
<|endoftext|>


<commit_before>/* global it */
/* global describe */

var logLevel = 'error';
if (process.env.NODE_ENV == 'TEST') logLevel = 'info';
var should = require('should');
var sandboxPath = 'test/sandbox';

describe('Matching epub: ', function () {

  it('should index test data into the index', function (done) {
    var data = [
      {
        "title": "Accessible EPUB 3",
        "body": "EPUB  is great.",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s06.xhtml"
      },
      {
        "title": "Even More Accessible EPUB 3",
        "body": "EPUB is epubtastic",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s07.xhtml"
      },
      {
        "title": "EPUB 3 FTW",
        "body": "EPUB is fantabulous",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s08.xhtml"
      },
      {
        "title": "中文的标题",
        "body": "中文的字符",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s09.xhtml"
      }
    ];
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
                                logLevel: logLevel});
    si.add({
      batchName: 'epubdata',
      fieldOptions: [{
        fieldName: 'spineItemPath',
        searchable: false
      }]
    }, data, function (err) {
      (err === null).should.be.exactly(true);
      si.close(function (err) {
        if (err) false.should.eql(true);
        done();
      });
    });
  });

  it('should search on all fields and get results', function (done) {
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
                                logLevel: logLevel});
    var str = 'epub';
    si.match(str, function (err, matches) {
      console.log(matches);
      should.exist(matches);
      (err === null).should.be.exactly(true);
      matches.length.should.be.exactly(2);
      matches[0].should.be.exactly('epub');
      matches[1].should.be.exactly('epubtastic');
      si.close(function (err) {
        if (err) false.should.eql(true);done();
      });
    });
  });

  it('should work for Unicode', function (done) {
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
      logLevel: logLevel});
    var str = '中文的';
    si.match(str, function (err, matches) {
      console.log(matches);
      should.exist(matches);
      (err === null).should.be.exactly(true);
      matches.length.should.be.exactly(2);
      matches.should.containEql('中文的标题');
      matches.should.containEql('中文的字符');
      si.close(function (err) {
        if (err) false.should.eql(true);done();
      });
    });
  });

  it('handles match strings that are below threshold', function (done) {
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
                                logLevel: logLevel});
    var str = 'lo';
    si.match(str, function (err, matches) {
      should.exist(matches);
      matches.length.should.be.exactly(0);
      (err instanceof Error).should.be.exactly(true);
      err.toString().should.be.exactly('Error: string below threshold length (3)');
      si.close(function (err) {
        if (err) false.should.eql(true);done();
      });
    });
  });

});
<commit_msg>Add a test case for unsearchable doc ID.<commit_after>/* global it */
/* global describe */

var logLevel = 'error';
if (process.env.NODE_ENV == 'TEST') logLevel = 'info';
var should = require('should');
var sandboxPath = 'test/sandbox';

describe('Matching epub: ', function () {

  it('should index test data into the index', function (done) {
    var data = [
      {
        "id": "doc101",
        "title": "Accessible EPUB 3",
        "body": "EPUB  is great.",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s06.xhtml"
      },
      {
        "id": "doc102",
        "title": "Even More Accessible EPUB 3",
        "body": "EPUB is epubtastic",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s07.xhtml"
      },
      {
        "id": "doc103",
        "title": "EPUB 3 FTW",
        "body": "EPUB is fantabulous",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s08.xhtml"
      },
      {
        "id": "doc104",
        "title": "中文的标题",
        "body": "中文的字符",
        "spineItemPath": "epub_content/accessible_epub_3/EPUB/ch03s09.xhtml"
      }
    ];
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
                                logLevel: logLevel});
    si.add({
      batchName: 'epubdata',
      fieldOptions: [{
        fieldName: 'id',
        searchable: false
      }, {
        fieldName: 'spineItemPath',
        searchable: false
      }]
    }, data, function (err) {
      (err === null).should.be.exactly(true);
      si.close(function (err) {
        if (err) false.should.eql(true);
        done();
      });
    });
  });

  it('should search on all fields and get results', function (done) {
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
                                logLevel: logLevel});
    var str = 'epub';
    si.match(str, function (err, matches) {
      console.log(matches);
      should.exist(matches);
      (err === null).should.be.exactly(true);
      matches.length.should.be.exactly(2);
      matches[0].should.be.exactly('epub');
      matches[1].should.be.exactly('epubtastic');
      si.close(function (err) {
        if (err) false.should.eql(true);done();
      });
    });
  });

  it('should work for Unicode', function (done) {
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
      logLevel: logLevel});
    var str = '中文的';
    si.match(str, function (err, matches) {
      console.log(matches);
      should.exist(matches);
      (err === null).should.be.exactly(true);
      matches.length.should.be.exactly(2);
      matches.should.containEql('中文的标题');
      matches.should.containEql('中文的字符');
      si.close(function (err) {
        if (err) false.should.eql(true);done();
      });
    });
  });

  it('handles match strings that are below threshold', function (done) {
    var si = require('../../')({indexPath: sandboxPath + '/si-epub-matching-test',
                                logLevel: logLevel});
    var str = 'lo';
    si.match(str, function (err, matches) {
      should.exist(matches);
      matches.length.should.be.exactly(0);
      (err instanceof Error).should.be.exactly(true);
      err.toString().should.be.exactly('Error: string below threshold length (3)');
      si.close(function (err) {
        if (err) false.should.eql(true);done();
      });
    });
  });

});
<|endoftext|>