/*
* Exposure
* Copyright (c) 2010 Kristoffer Jelbring
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function($) {
	
/**
* @name Exposure
* @author Kristoffer Jelbring (kris@blogocracy.org)
* @version 0.2
*
* @type jQuery
* @cat plugins/Media
*
* @desc Turn a simple HTML list into a rich and smart photo viewer that handles very large amounts of photos.
*
* @example $('#images').exposure({options});
*
* @options
*	target:	(selector string) Where to insert the image being displayed. Defaults to '#exposure'. If no target is found, one will be created
* 	showThumbs:	(boolean) Display thumbnails or not. Defaults to true, but will be set to false if not thumbnails are found. 
*	showControls: (boolean) Display paging controls or not. Defaults to true, but will be set to false if missing controlsTarget.
*	showCaptions: (boolean) Display captions or not. Captions are added by setting a title attribute on the items in the list.
* 	showExtraData: (boolean) Display extra image data or not. This data is added by inserting inner HTML to the items in the list.
*	controlsTarget: (selector string) Where to insert the paging controls.
*	onThumb: (function) Callback function that is called when a thumbnail is displayed.
*	onImage: (function) Callback function that is called when an image is displayed.
*	onNext: (function) Callback function that is called when nextImage is called.
*	onPrev: (function) Callback function that is called when prevImage is called.
*	onPageChanged: (function) Callback function that is called when goToPage is called.
* 	loop: (boolean) Start over when last image is reached.
*	onEndOfLoop: (function) Callback function that is called when the last image is reached and loop option is set to false.
*	pageSize: (number) Maximum number of images (thumbnails) per page. Defaults to 5.
*	preloadBuffer: (number) Maximum number of images to keep in load queue at any given time. Defaults to 3.
*	keyboardNavigation: (boolean) Enable keyboard navigation.
*	clickingNavigation: (boolean) Enable browsing by clicking the image being shown.
*	onKeyDown: (function) Callback function to check if Exposure should catch the keydown event. Change to return false when focus is set a text input field.
*/
$$ = $.fn.exposure = function($args) {

	var $defaults = {
		target : '#exposure',
		showThumbs : true,
		showControls : true,
		showCaptions : true,
		showExtraData : true,
		controlsTarget : null,
		onThumb : function(thumb) {},
		onImage : function(image, imageData, thumb) {},
		onNext : function() {},
		onPrev : function() {},
		onPageChanged : function() {},
		loop : true,
		onEndOfLoop : function() {},
		pageSize : 5,
		preloadBuffer : 3,
		keyboardNavigation : true,
		clickingNavigation : true,
		onKeyDown : function() { return true; },
	};

	var opts = $.extend($defaults, $args);
	for (var i in opts) {
		$.exposure[i]  = opts[i];
	}
	
	var wrapper = $('<div class="exposureWrapper"></div>');
	var dataContainer = $('<div class="exposureData"><div class="caption"></div><div class="extra"></div></div>');
	
	if (!$(opts.target).length) {
		// The target element is missing so it needs to be created.
		$('<div id="exposure"></div>').insertBefore($(this));	
	}
	
	var target = $(opts.target).addClass('exposureTarget').append(wrapper).append(dataContainer);
	
	// Don't show controls if there is no controls target.
	if (!$.exposure.controlsTarget) {
		$.exposure.showControls = false;
	}
	
	// Render controls.
	if ($.exposure.showControls) {
		
		var prevButton = $('<a class="exposurePrevPage" href="javascript:void(0);">Marinella Confezioni</a>').click($.exposure.prevPage);			
		var pagingButtons = $('<div class="exposurePaging"></div>');
		var nextButton = $('<a class="exposureNextPage" href="javascript:void(0);"></a>').click($.exposure.nextPage);	
		$($.exposure.controlsTarget).addClass('exposureControls').append(prevButton).append(pagingButtons).append(nextButton);
	}
	
	// Bind keys for navigation (using Hotkeys Plugin).
	if ($.exposure.keyboardNavigation) {
		$(document).bind('keydown', 'left', function() { 
			if ($.exposure.onKeyDown()) {
				$.exposure.prevImage();
			}
		});
		$(document).bind('keydown', 'right', function() {
			if ($.exposure.onKeyDown()) {
				$.exposure.nextImage();
			}
		});
		$(document).bind('keydown', 'ctrl+left', function() {
			if ($.exposure.onKeyDown()) {
				$.exposure.prevPage();
			}
		});
		$(document).bind('keydown', 'ctrl+right', function() {
			if ($.exposure.onKeyDown()) {
				$.exposure.nextPage();
			}
		});
	}
	
	return this.addClass('exposureThumbs').each(function(){
		
		var foundImage = false;	
		var foundThumb = false;		
		
		$(this).children('li').each(function(index) {
			foundImage = true;
			
			// The a tag contains all the need information about the image.
			var a = $(this).find('a')		
			var src = a.attr('href');
			var thumbSrc = a.attr('rel');
			var caption = a.attr('title');
			var thumbData = a.html();
			
			if (thumbSrc) {
				foundThumb = true;
			} else {
				// TODO: Use original image as thumb.	
			}
			
			// Add image to list of images.
			var image = new $$.Image(src, thumbSrc, caption, thumbData);
			var index = $.exposure.images.push(image) - 1;
			
			// All information extracted, remove original list entry.
			$(this).remove();
			
			if ($.exposure.loadQueue.length < $.exposure.preloadBuffer) {
				// Preload buffer hasn't been filled yet, add image to load queue.				
				$.exposure.addToLoadQueue(index);
			}
		});
		
		if (!foundThumb) {
			// No thumbnails found, turn off thumbnails view.
			$.exposure.showThumbs = false;
		}
		
		if (!$.exposure.showThumbs) {
			// Thumbnails are turned off, change page size to 1.
			$.exposure.pageSize = 1;
			
			// Hide thumbnails container.
			$('.exposureThumbs').hide();
		}
		
		if (foundImage) {
			// Start preloading the first image.
			$.exposure.preloadNextInQueue();
			
			if ($.exposure.showControls) {
				// Create paging links.
				for (var i = 1; i <= $.exposure.numberOfPages(); i++) {
					$('.exposurePaging').append($$.newPagingLink(i));
				}
			}
			
			// View the first page (and the first image).
			$.exposure.goToPage(1);
		}
	});
}

/**
* Value object representing an image in the viewer.
*
* @param src Source to the full size image.
* @param thumb Source to thumbnail version of the image.
* @param caption Image caption.
* @param data Extra image data.
*/
$$.Image = function(src, thumb, caption, data) {
	this.src = src;
	this.thumb = thumb;
	this.caption = caption;
	this.data = data;
	this.loaded = false;
}

/**
* Create a new paging link for a specific page.
*
* @param page Number of the page to create the link for.
*/
$$.newPagingLink = function(page) {
	return $('<a href="javascript:void(0);" rel="' + page + '">' + page + '</a>').click(function() { 
		// View the page defined in the rel attribute of the link.
		$.exposure.goToPage(Number($(this).attr('rel')));
	});
}

$.extend({exposure :
	{
		/**
		* Create a thumbnail for a specific image.
		*
		* @param image Image object for the image.
		* @param image Index of the image.
		*/
		createThumbForImage : function(image, index) {
			if ($.exposure.showThumbs && image.thumb) {
				
				var thumb = $.exposure.getThumb(index);
				if (thumb == null || !thumb.length) {						
					// Create thumbnail container.
					var container = $('<li></li>');
					$('.exposureThumbs').append(container);
					
					// Create thumbnail img element.
					thumb = $('<img />').attr('src', image.thumb);
					if ($(this).hasClass('active')) {
						thumb.addClass('selected');	
					}
					container.append(thumb.css('display', 'block'));					
					
					// Add image index and caption as attributes.
					thumb.attr('rel', index).attr('title', image.caption);
					
					// Save extra image data in thumbnail data.
					thumb.data('data', image.data);
					
					thumb.click(function() {
						// When a thumbnail is clicked, view full version of that image.
						$.exposure.viewImage(index);
					});
					
					thumb.load(function() {
						// Set the height of the thumbnail container to the height of the thumbnail.
						// TODO: Handle this, in the case when the thumb just points to the original.
						var imageHeight = $(this).height();
						if (imageHeight > 0) {
							$(this).parent().height(imageHeight);
						}						
					});
					
					$.exposure.onThumb(thumb);
					
					return container;
				}
			}
			return null;
		},
		
		/**
		* Calculate the total number of pages using the set page size.
		*/
		numberOfPages : function() {
			return Math.ceil($.exposure.images.length / $.exposure.pageSize);
		},
		
		/**
		* Check if the the page currently being viewed is the first page.
		*/
		isAtFirstPage : function() {
			return $.exposure.currentPage == 1;
		},
		
		/**
		* Check if the the page currently being viewed is the last page.
		*/
		isAtLastPage : function() {
			return $.exposure.currentPage == $.exposure.numberOfPages();
		},	
		
		/**
		* Check if a specific page number is a valid page number.
		*
		* @param page Page number to check.
		*/
		isValidPage : function(page) {
			return page > 0 && page <= $.exposure.numberOfPages();
		},
		
		/**
		* Check if the image currently being viewed is the first image on its page.
		*/
		isAtFirstImageOnPage : function() {
			return $.exposure.pageSize == 1 || ($.exposure.current % $.exposure.pageSize == 0);
		},
		
		/**
		* Check if the image currently being viewed is the last image on its page.
		*/
		isAtLastImageOnPage : function() {
			if ($.exposure.pageSize == 1) {
				return true;	
			}
			if ($.exposure.current > 0 || $.exposure.current.length == 1) {
				var currentPageSize = $.exposure.pageSize;
				if ($.exposure.isAtLastPage()) {
					// Calculate the size of the last page as it may differ from the set page size.
					var newPageSize = $.exposure.images.length % $.exposure.pageSize;
					if (newPageSize > 0) {
						currentPageSize = newPageSize;
					}
				}
				// Check if the current image is the last image of the current page.
				return ($.exposure.current+1) % currentPageSize == 0;
			}
			return false;
		},
		
		/**
		* Number of the page currently being viewed.
		*/
		currentPage : 1,
		
		/**
		* Index of the image currently being viewed.
		*/
		current : 0,
		
		/**
		* All the images in the viewer. Holds an array of Image objects that are filled up when the plugin is loaded.
		*/
		images : new Array(),
		
		/**
		* Get a spefic image object from the images array.
		*
		* @param index Index of image to get.
		*/
		getImage : function(index) {
			if (index != null && index > -1 && index < $.exposure.images.length) {
				return $.exposure.images[index];
			}
			return null;
		},
		
		/**
		* The load queue, holds an array of indices of images to load.
		*/
		loadQueue : new Array(),
		
		/**
		* Add an image to the load queue.
		*
		* @param index Index of image to add.
		*/
		addToLoadQueue : function(index) {
			if (!$.exposure.isLoaded(index) && !$.exposure.isQueued(index)) {
				$.exposure.loadQueue.push(index);
			}
		},
		
		/**
		* Check if a specific image exists in the load queue.
		*
		* @param index Index of image to check.
		*/
		isQueued : function(index) {
			return $.inArray(index, $.exposure.loadQueue) > -1;	
		},
		
		/**
		* Check if a specific image has been loaded.
		*
		* @param index Index of image to check.
		*/
		isLoaded : function(index) {
			var image = $.exposure.getImage(index);
			if (image != null) {
				return image.loaded;
			}
			return false;
		},
		
		/**
		* Find the next, not already image, in the load queue. This function is recursive and will continue until
		* an image is found, or until the queue is empty.
		*/
		nextInLoadQueue : function() {
			if ($.exposure.loadQueue.length > 0) {
				var next = $.exposure.loadQueue.shift();
				if ($.exposure.isLoaded(next)) {				
					// Image already loaded, remove from load queue.
					var i = $.inArray(index, $.exposure.loadQueue);
					$.exposure.loadQueue.splice(i, 1);
					
					// Find next in queue.
					return $.exposure.nextInLoadQueue();
				}
				return next;
			}
			return null;
		},
		
		/**
		* Preload the next image in the load queue.
		*/	
		preloadNextInQueue : function() {
			if ($.exposure.loadQueue.length > 0) {				
				var nextIndex = $.exposure.nextInLoadQueue();
				if (nextIndex != null) {
					$.exposure.loadImage(nextIndex, $.exposure.preloadNextInQueue);
				}
			}
		},
		
		/**
		* Get the thumbnail img element for a specific image.
		*
		* @param index Index of image to find thumbnail for.
		*/
		getThumb : function(index) {
			return $('.exposureThumbs img[rel="'+index+'"]');
		},
		
		/**
		* Get the index of the next image.
		*/
		getNextImage : function() {
			if ($.exposure.current == $.exposure.images.length-1) {
				// Is at last image, return first image.
				if ($.exposure.loop) {
					return 0;
				} else {
					// Loop ended callback.
					$.exposure.onEndOfLoop();	
				}					
			} else {
				// Return next image.
				return $.exposure.current+1;
			}
			return null;
		},
		
		/**
		* Get the index of the previous image.
		*/
		getPrevImage : function() {
			if ($.exposure.current == 0) {
				// Is at first image, return last image.
				if ($.exposure.loop) {
					return $.exposure.images.length-1;
				}
			} else {					
				// Return previous image. 
				return $.exposure.current-1;
			}
			return null;
		},
	
		/**
		* View a specific page.
		*
		* @param page Number of the page to view.
		* @param backwards Set to true if browsing backwards.
		*/
		goToPage : function(page, backwards) {
			if ($.exposure.isValidPage(page)) {
				// Hide all thumbnail containers.
				$('.exposureThumbs li').hide();
				
				$.exposure.loadPage(page, backwards);
				
				if ($.exposure.showControls) {
					$('.exposurePaging span.active').replaceWith($$.newPagingLink($.exposure.currentPage));
					$('.exposurePaging a[rel="' + page + '"]').replaceWith($('<span>' + page + '</span>').addClass('active'));
				}
				
				$.exposure.currentPage = page;
				
				if (!$.exposure.loop) {
					// Hide previous page button.
					if ($.exposure.isAtFirstPage()) {
						$('.exposurePrevPage').hide();
					} else {
						$('.exposurePrevPage').show();
					}
					// Hide next page button.
					if ($.exposure.isAtLastPage()) {
						$('.exposureNextPage').hide();
					} else {
						$('.exposureNextPage').show();
					}
				}
				
				// Page changed callback.
				$.exposure.onPageChanged();
			}
		},

		/**
		* Load a specific page. Don't call this function directly! Use goToPage instead!
		*
		* @param page Number of the page to load.
		* @param backwards Set to true if browsing backwards.
		*/
		loadPage : function(page, backwards) {
			if ($.exposure.isValidPage(page)) {
				// Calculate first and last images on this page.
				var last = page * $.exposure.pageSize;
				var first = last - $.exposure.pageSize;
				
				if (last > $.exposure.images.length) {
					last = $.exposure.images.length;
				}

				// Go through images on page.
				for (var i = first; i < last; i++) {
					if ($.exposure.showThumbs) {
						var image = $.exposure.images[i];
						// Find thumbnail container.
						var container = $.exposure.getThumb(i).parent();
						if (!container.length) {
							// Create a thumbnail if one doesn't already exist.
							container = $.exposure.createThumbForImage(image, i);
							if (i == first) {
								// Decorate thumbnail container for first image on page.
								container.addClass('first');
							}
							if (i == last-1) {
								// Decorate thumbnail container for last image on page.
								container.addClass('last');
							}
						}
						if (container.length) {
							// Show thumbnail container.
							container.show();
						}
					}
				}			
				
				if (backwards) {
					// Moving backwards, set the last image on the page as active.
					$.exposure.viewImage(last-1);
				} else {
					// Set the first image on this page as active.			
					$.exposure.viewImage(first);
				}
			}
		},
		
		/**
		* View the previous page.
		*/
		prevPage : function() {
			if (!$.exposure.isAtFirstPage()) {
				// Go to previous page.
				$.exposure.goToPage($.exposure.currentPage-1);
			} else if ($.exposure.loop) {
				// At first page, go to last page.
				$.exposure.goToPage($.exposure.numberOfPages());
			}	
		},
		
		/**
		* View the next page.
		*/
		nextPage : function() {
			if (!$.exposure.isAtLastPage()) {
				// Go to next page.
				$.exposure.goToPage($.exposure.currentPage+1);
			} else if ($.exposure.loop) {
				// At last page, go back to first page.
				$.exposure.goToPage(1);
			}	
		},
		
		/**
		* View a specific image.
		*
		* @param Index of image to view.
		*/
		viewImage : function(index) {
			var image = $.exposure.images[index];
			if (image) {
				var src = image.src;
				var caption = image.caption;
				var extraImageData = image.data;
				
				var wrapper = $('.exposureWrapper');				
				if (src) {
					var hasThumb = $.exposure.showThumbs;
					if ($.exposure.showThumbs) {
						var thumb = $('.exposureThumbs img[rel="' + index + '"]');
						hasThumb = thumb && thumb.length;
						
						// Light up active thumbnail.
						if (hasThumb) {
							thumb.parent().siblings().removeClass('active');
							thumb.parent().addClass('active');
						} else {
							$('.exposureThumbs li.active').removeClass('active');
						}
					}
					
					// Show loading animation.
					wrapper.parent().removeClass('exposureLoaded');
					if ($.exposure.isLoaded(index)) {
						// Hide loading animation if image already loaded.				
						wrapper.parent().addClass('exposureLoaded');
					}
				
					var img = $.exposure.loadImage(index, function() {
						wrapper.empty().append($(this));
						
						// Enable browsing by clicking on the image.
						if ($.exposure.clickingNavigation) {
							$(this).click($.exposure.nextImage);
						}
						
						// Resize target element to fit image.
						$('.exposureTarget').width($(this).width()).height($(this).height());
						
						// Add caption and additional image data.							
						var imageDataContainer = wrapper.siblings('.exposureData');
						if (imageDataContainer.length) {
							if ($.exposure.showCaptions) {
								// Add caption to image data container.
								var captionContainer = imageDataContainer.find('.caption');
								if (captionContainer.length) {
									// Remove current caption from container.
									captionContainer.empty();
									if (!caption && hasThumb) {
										// Extract caption from thumbnail.
										caption	= thumb.attr('title');
									}
								}
								captionContainer.html(caption);
							}
							
							if ($.exposure.showExtraData) {
								// Add extra image data to image data container.
								var extraImageDataContainer = imageDataContainer.find('.extra');
								if (extraImageDataContainer.length) {
									// Remove current data from container.
									extraImageDataContainer.empty();
									if (!extraImageData && hasThumb) {
										// Extract data from thumbnail.
										extraImageData = thumb.data('data');
									}
									extraImageDataContainer.html(extraImageData);
								}
							}
						}
						
						// Image loaded callback.
						$.exposure.onImage($(this), imageDataContainer, thumb);

						// Preload next image.					
						$.exposure.preloadNextInQueue();
					});
				} else {
					wrapper.siblings().andSelf().empty();
					$('.exposureThumbs li.active').removeClass('active');
				}
			}
			$.exposure.current = index;
		},
		
		/**
		* Load a specific image. Don't call this function directly! Use viewImage instead!
		*
		* @param index Index of image to load.
		* @param onload Image onload callback function.
		*/
		loadImage : function(index, onload) {
			var image = $.exposure.getImage(index);					
			var img = $('<img />');
			if (image != null) {
				
				image.loaded = true;
				if ($.exposure.isQueued(index)) {
					// Since image already has been loaded, remove it from the load queue.
					var i = $.inArray(index, $.exposure.loadQueue);
					$.exposure.loadQueue.splice(i, 1);
				}
				if (typeof onload == 'function') {
					img.load(onload);
				}
				img.attr('src', image.src);
			}
			return img;		
		},
		
		/**
		* View next image.
		*/
		nextImage : function() {
			if ($.exposure.isAtLastImageOnPage()) {
				if ($.exposure.isAtLastPage()) {
					// At the last page, go back to first page.
					$.exposure.goToPage(1);
				} else {
					// Go to the next page.
					$.exposure.goToPage($.exposure.currentPage+1);
				}
				// Next image callback.
				$.exposure.onNext();
			} else {
				var next = $.exposure.getNextImage();
				if (next != null) {
					// Select next image.
					$.exposure.viewImage(next);
					// Next image callback.
					$.exposure.onNext();	
				}
			}
			var nextNext = $.exposure.getNextImage();
			if (nextNext != null) {
				// Add second next image to load queue.
				$.exposure.addToLoadQueue(nextNext);
			}
		},
		
		/**
		* View previous image.
		*/
		prevImage : function() {
			if ($.exposure.isAtFirstImageOnPage()) {
				if ($.exposure.isAtFirstPage()) {
					// At the first page, go to the last page.
					$.exposure.goToPage($.exposure.numberOfPages(), true);
				} else {
					// Go to the previous page.	
					$.exposure.goToPage($.exposure.currentPage-1, true);
				}
				// Previous image callback.
				$.exposure.onPrev();
			} else {
				var prev = $.exposure.getPrevImage();
				if (prev != null) {
					// Select next image.
					$.exposure.viewImage(prev);
					// Previous image callback.
					$.exposure.onPrev();
				}
			}
			var prevPrev = $.exposure.getPrevImage();
			if (prevPrev != null) {
				// Add second previous image to load queue.
				$.exposure.addToLoadQueue(prevPrev);
			}
		}
	}			
});
})(jQuery);

/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
(function(jQuery){jQuery.hotkeys={version:"0.8",specialKeys:{8:"backspace",9:"tab",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",191:"/",224:"meta"},shiftNums:{"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"_","=":"+",";":": ","'":"\"",",":"<",".":">","/":"?","\\":"|"}};function keyHandler(handleObj){if(typeof handleObj.data!=="string"){return}var origHandler=handleObj.handler,keys=handleObj.data.toLowerCase().split(" ");handleObj.handler=function(event){if(this!==event.target&&(/textarea|select/i.test(event.target.nodeName)||event.target.type==="text")){return}var special=event.type!=="keypress"&&jQuery.hotkeys.specialKeys[event.which],character=String.fromCharCode(event.which).toLowerCase(),key,modif="",possible={};if(event.altKey&&special!=="alt"){modif+="alt+"}if(event.ctrlKey&&special!=="ctrl"){modif+="ctrl+"}if(event.metaKey&&!event.ctrlKey&&special!=="meta"){modif+="meta+"}if(event.shiftKey&&special!=="shift"){modif+="shift+"}if(special){possible[modif+special]=true}else{possible[modif+character]=true;possible[modif+jQuery.hotkeys.shiftNums[character]]=true;if(modif==="shift+"){possible[jQuery.hotkeys.shiftNums[character]]=true}}for(var i=0,l=keys.length;i<l;i++){if(possible[keys[i]]){return origHandler.apply(this,arguments)}}}}jQuery.each(["keydown","keyup","keypress"],function(){jQuery.event.special[this]={add:keyHandler}})})(jQuery);

