<commit_before>/*
 * Fuel UX Tree
 * https://github.com/ExactTarget/fuelux
 *
 * Copyright (c) 2014 ExactTarget
 * Licensed under the MIT license.
 */

// -- BEGIN UMD WRAPPER PREFACE --

// For more information on UMD visit:
// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js

(function (factory) {
	if (typeof define === 'function' && define.amd) {
		// if AMD loader is available, register as an anonymous module.
		define(['jquery'], factory);
	} else {
		// OR use browser globals if AMD is not present
		factory(jQuery);
	}
}(function ($) {
	// -- END UMD WRAPPER PREFACE --

	// -- BEGIN MODULE CODE HERE --

	var old = $.fn.tree;

	// TREE CONSTRUCTOR AND PROTOTYPE

	var Tree = function (element, options) {
		this.$element = $(element);
		this.options = $.extend({}, $.fn.tree.defaults, options);

		this.$element.on('click.fu.tree', '.tree-item', $.proxy( function(ev) { this.selectItem(ev.currentTarget); } ,this));
		this.$element.on('click.fu.tree', '.tree-branch-name', $.proxy( function(ev) { this.openFolder(ev.currentTarget); }, this));

		if( this.options.folderSelect ){
			this.$element.off('click.fu.tree', '.tree-branch-name');
			this.$element.on('click.fu.tree', '.icon-caret', $.proxy( function(ev) { this.openFolder($(ev.currentTarget).parent()); }, this));
			this.$element.on('click.fu.tree', '.tree-branch-name', $.proxy( function(ev) { this.selectFolder($(ev.currentTarget)); }, this));
		}

		this.render();
	};

	Tree.prototype = {
		constructor: Tree,

		destroy: function() {
			// any external bindings [none]
			// empty elements to return to original markup
			this.$element.find("li:not([data-template])").remove();

			this.$element.remove();
			// returns string of markup
			return this.$element[0].outerHTML;
		},

		render: function () {
			this.populate(this.$element);
		},

		populate: function ($el) {
			var self = this;
			var $parent = $el.parent();
			var loader = $parent.find('.tree-loader:eq(0)');

			loader.removeClass('hide');
			this.options.dataSource($el.data(), function (items) {
				loader.addClass('hide');

				$.each( items.data, function(index, value) {
					var $entity;

					if(value.type === 'folder') {
						$entity = self.$element.find('[data-template=treebranch]:eq(0)').clone().removeClass('hide').removeAttr('data-template');
						$entity.data(value);
						$entity.find('.tree-branch-name > .tree-label').html(value.name);
					} else if (value.type === 'item') {
						$entity = self.$element.find('[data-template=treeitem]:eq(0)').clone().removeClass('hide').removeAttr('data-template');
						$entity.find('.tree-item-name > .tree-label').html(value.name);
						$entity.data(value);
					}

					// Decorate $entity with data making the element
					// easily accessable with libraries like jQuery.
					//
					// Values are contained within the object returned
					// for folders and items as dataAttributes:
					//
					// {
					//     name: "An Item",
					//     type: 'item',
					//     dataAttributes = {
					//         'classes': 'required-item red-text',
					//         'data-parent': parentId,
					//         'guid': guid,
					//         'id': guid
					//     }
					// };

					// add attributes to tree-branch or tree-item
					var dataAttributes = value.dataAttributes || [];
					$.each(dataAttributes, function(key, value) {
						switch (key) {
						case 'class':
						case 'classes':
						case 'className':
							$entity.addClass(value);
							break;

						// allow custom icons
						case 'data-icon':
							$entity.find('.icon-item').removeClass().addClass('icon-item ' + value);
							$entity.attr(key, value);
							break;

						// ARIA support
						case 'id':
							$entity.attr(key, value);
							$entity.attr('aria-labelledby', value + '-label');
							$entity.find('.tree-branch-name > .tree-label').attr('id', value + '-label');
							break;

						// id, style, data-*
						default:
							$entity.attr(key, value);
							break;
						}
					});

					// add child nodes
					if($el.hasClass('tree-branch-header')) {
						$parent.find('.tree-branch-children:eq(0)').append($entity);
					} else {
						$el.append($entity);
					}
				});

				// return newly populated folder
				self.$element.trigger('loaded.fu.tree', $parent);
			});
		},

		selectItem: function (el) {
			var $el = $(el);
			var $all = this.$element.find('.tree-selected');
			var data = [];
			var $icon = $el.find('.icon-item');

			if (this.options.multiSelect) {
				$.each($all, function(index, value) {
					var $val = $(value);
					if($val[0] !== $el[0]) {
						data.push( $(value).data() );
					}
				});
			} else if ($all[0] !== $el[0]) {
				$all.removeClass('tree-selected')
					.find('.glyphicon').removeClass('glyphicon-ok').addClass('tree-dot');
				data.push($el.data());
			}

			var eventType = 'selected';
			if($el.hasClass('tree-selected')) {
				eventType = 'unselected';
				$el.removeClass('tree-selected');
				if($icon.hasClass('glyphicon-ok') || $icon.hasClass('fueluxicon-bullet') ) {
					$icon.removeClass('glyphicon-ok').addClass('fueluxicon-bullet');
				}
			} else {
				$el.addClass ('tree-selected');
				// add tree dot back in
				if($icon.hasClass('glyphicon-ok') || $icon.hasClass('fueluxicon-bullet') ) {
					$icon.removeClass('fueluxicon-bullet').addClass('glyphicon-ok');
				}
				if (this.options.multiSelect) {
					data.push( $el.data() );
				}
			}

			if(data.length) {
				this.$element.trigger('selected', {selected: data});
			}

			// Return new list of selected items, the item
			// clicked, and the type of event:
			$el.trigger('updated.fu.tree', {
				selected: data,
				item: $el,
				eventType: eventType
			});
		},

		openFolder: function (el) {
			var $el = $(el); // tree-branch-name
			var $branch;
			var $treeFolderContent;
			var $treeFolderContentFirstChild;

			// if item select only
			if (!this.options.folderSelect) {
				$el = $(el).parent(); // tree-branch, if tree-branch-name clicked
			}

			$branch = $el.closest('.tree-branch'); // tree branch
			$treeFolderContent = $branch.find('.tree-branch-children');
			$treeFolderContentFirstChild = $treeFolderContent.eq(0);

			// manipulate branch/folder
			var eventType, classToTarget, classToAdd;
			if ($el.find('.glyphicon-folder-close').length) {
				eventType = 'opened';
				classToTarget = '.glyphicon-folder-close';
				classToAdd = 'glyphicon-folder-open';

				$branch.addClass('tree-open');
				$branch.attr('aria-expanded', 'true');

				$treeFolderContentFirstChild.removeClass('hide');
				if (!$treeFolderContent.children().length) {
					this.populate($treeFolderContent);
				}

			} else if($el.find('.glyphicon-folder-open')) {
				eventType = 'closed';
				classToTarget = '.glyphicon-folder-open';
				classToAdd = 'glyphicon-folder-close';

				$branch.removeClass('tree-open');
				$branch.attr('aria-expanded', 'false');
				$treeFolderContentFirstChild.addClass('hide');

				// remove if no cache
				if (!this.options.cacheItems) {
					$treeFolderContentFirstChild.empty();
				}

			}

			$branch.find('> .tree-branch-header .icon-folder').eq(0)
				.removeClass('glyphicon-folder-close glyphicon-folder-open')
				.addClass(classToAdd);

			this.$element.trigger(eventType, $branch.data());
		},

		selectFolder: function (clickedElement) {
			var $clickedElement = $(clickedElement);
			var $clickedBranch = $clickedElement.closest('.tree-branch');
			var $selectedBranch = this.$element.find('.tree-branch.tree-selected');
			var selectedData = [];
			var eventType = 'selected';

			// select clicked item
			if($clickedBranch.hasClass('tree-selected')) {
				eventType = 'unselected';
				$clickedBranch.removeClass('tree-selected');
			} else {
				$clickedBranch.addClass('tree-selected');
			}

			if (this.options.multiSelect) {

			// get currently selected
				$selectedBranch = this.$element.find('.tree-branch.tree-selected');

				$.each($selectedBranch, function(index, value) {
					var $value = $(value);
					if($value[0] !== $clickedElement[0]) {
						selectedData.push( $(value).data() );
					}
				});

			} else if ($selectedBranch[0] !== $clickedElement[0]) {
				$selectedBranch.removeClass('tree-selected');

				selectedData.push($clickedBranch.data());
			}

			if(selectedData.length) {
				this.$element.trigger('selected.fu.tree', {selected: selectedData});
			}

			// Return new list of selected items, the item
			// clicked, and the type of event:
			$clickedElement.trigger('updated.fu.tree', {
				selected: selectedData,
				item: $clickedElement,
				eventType: eventType
			});
		},

		selectedItems: function () {
			var $sel = this.$element.find('.tree-selected');
			var data = [];

			$.each($sel, function (index, value) {
				data.push($(value).data());
			});
			return data;
		},

		// collapses open folders
		collapse: function () {
			var cacheItems = this.options.cacheItems;

			// find open folders
			this.$element.find('.icon-folder-open').each(function () {
				// update icon class
				var $this = $(this)
					.removeClass('icon-folder-close icon-folder-open')
					.addClass('icon-folder-close');

				// "close" or empty folder contents
				var $parent = $this.parent().parent();
				var $folder = $parent.children('.tree-branch-children');

				$folder.addClass('hide');
				if (!cacheItems) {
					$folder.empty();
				}
			});
		}
	};

	// TREE PLUGIN DEFINITION

	$.fn.tree = function (option) {
		var args = Array.prototype.slice.call( arguments, 1 );
		var methodReturn;

		var $set = this.each(function () {
			var $this   = $( this );
			var data    = $this.data( 'fu.tree' );
			var options = typeof option === 'object' && option;

			if( !data ) $this.data('fu.tree', (data = new Tree( this, options ) ) );
			if( typeof option === 'string' ) methodReturn = data[ option ].apply( data, args );
		});

		return ( methodReturn === undefined ) ? $set : methodReturn;
	};

	$.fn.tree.defaults = {
		dataSource: function(options, callback){},
		multiSelect: false,
		cacheItems: true,
		folderSelect: true
	};

	$.fn.tree.Constructor = Tree;

	$.fn.tree.noConflict = function () {
		$.fn.tree = old;
		return this;
	};

	// NO DATA-API DUE TO NEED OF DATA-SOURCE

// -- BEGIN UMD WRAPPER AFTERWORD --
}));
	// -- END UMD WRAPPER AFTERWORD --
<commit_msg>Tree folder select options not returning<commit_after>/*
 * Fuel UX Tree
 * https://github.com/ExactTarget/fuelux
 *
 * Copyright (c) 2014 ExactTarget
 * Licensed under the MIT license.
 */

// -- BEGIN UMD WRAPPER PREFACE --

// For more information on UMD visit:
// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js

(function (factory) {
	if (typeof define === 'function' && define.amd) {
		// if AMD loader is available, register as an anonymous module.
		define(['jquery'], factory);
	} else {
		// OR use browser globals if AMD is not present
		factory(jQuery);
	}
}(function ($) {
	// -- END UMD WRAPPER PREFACE --

	// -- BEGIN MODULE CODE HERE --

	var old = $.fn.tree;

	// TREE CONSTRUCTOR AND PROTOTYPE

	var Tree = function (element, options) {
		this.$element = $(element);
		this.options = $.extend({}, $.fn.tree.defaults, options);

		this.$element.on('click.fu.tree', '.tree-item', $.proxy( function(ev) { this.selectItem(ev.currentTarget); } ,this));
		this.$element.on('click.fu.tree', '.tree-branch-name', $.proxy( function(ev) { this.openFolder(ev.currentTarget); }, this));

		if( this.options.folderSelect ){
			this.$element.off('click.fu.tree', '.tree-branch-name');
			this.$element.on('click.fu.tree', '.icon-caret', $.proxy( function(ev) { this.openFolder($(ev.currentTarget).parent()); }, this));
			this.$element.on('click.fu.tree', '.tree-branch-name', $.proxy( function(ev) { this.selectFolder($(ev.currentTarget)); }, this));
		}

		this.render();
	};

	Tree.prototype = {
		constructor: Tree,

		destroy: function() {
			// any external bindings [none]
			// empty elements to return to original markup
			this.$element.find("li:not([data-template])").remove();

			this.$element.remove();
			// returns string of markup
			return this.$element[0].outerHTML;
		},

		render: function () {
			this.populate(this.$element);
		},

		populate: function ($el) {
			var self = this;
			var $parent = $el.parent();
			var loader = $parent.find('.tree-loader:eq(0)');

			loader.removeClass('hide');
			this.options.dataSource( this.options.folderSelect ? $parent.data() : $el.data(), function (items) {
				loader.addClass('hide');

				$.each( items.data, function(index, value) {
					var $entity;

					if(value.type === 'folder') {
						$entity = self.$element.find('[data-template=treebranch]:eq(0)').clone().removeClass('hide').removeAttr('data-template');
						$entity.data(value);
						$entity.find('.tree-branch-name > .tree-label').html(value.name);
					} else if (value.type === 'item') {
						$entity = self.$element.find('[data-template=treeitem]:eq(0)').clone().removeClass('hide').removeAttr('data-template');
						$entity.find('.tree-item-name > .tree-label').html(value.name);
						$entity.data(value);
					}

					// Decorate $entity with data making the element
					// easily accessable with libraries like jQuery.
					//
					// Values are contained within the object returned
					// for folders and items as dataAttributes:
					//
					// {
					//     name: "An Item",
					//     type: 'item',
					//     dataAttributes = {
					//         'classes': 'required-item red-text',
					//         'data-parent': parentId,
					//         'guid': guid,
					//         'id': guid
					//     }
					// };

					// add attributes to tree-branch or tree-item
					var dataAttributes = value.dataAttributes || [];
					$.each(dataAttributes, function(key, value) {
						switch (key) {
						case 'class':
						case 'classes':
						case 'className':
							$entity.addClass(value);
							break;

						// allow custom icons
						case 'data-icon':
							$entity.find('.icon-item').removeClass().addClass('icon-item ' + value);
							$entity.attr(key, value);
							break;

						// ARIA support
						case 'id':
							$entity.attr(key, value);
							$entity.attr('aria-labelledby', value + '-label');
							$entity.find('.tree-branch-name > .tree-label').attr('id', value + '-label');
							break;

						// id, style, data-*
						default:
							$entity.attr(key, value);
							break;
						}
					});

					// add child nodes
					if($el.hasClass('tree-branch-header')) {
						$parent.find('.tree-branch-children:eq(0)').append($entity);
					} else {
						$el.append($entity);
					}
				});

				// return newly populated folder
				self.$element.trigger('loaded.fu.tree', $parent);
			});
		},

		selectItem: function (el) {
			var $el = $(el);
			var $all = this.$element.find('.tree-selected');
			var data = [];
			var $icon = $el.find('.icon-item');

			if (this.options.multiSelect) {
				$.each($all, function(index, value) {
					var $val = $(value);
					if($val[0] !== $el[0]) {
						data.push( $(value).data() );
					}
				});
			} else if ($all[0] !== $el[0]) {
				$all.removeClass('tree-selected')
					.find('.glyphicon').removeClass('glyphicon-ok').addClass('tree-dot');
				data.push($el.data());
			}

			var eventType = 'selected';
			if($el.hasClass('tree-selected')) {
				eventType = 'unselected';
				$el.removeClass('tree-selected');
				if($icon.hasClass('glyphicon-ok') || $icon.hasClass('fueluxicon-bullet') ) {
					$icon.removeClass('glyphicon-ok').addClass('fueluxicon-bullet');
				}
			} else {
				$el.addClass ('tree-selected');
				// add tree dot back in
				if($icon.hasClass('glyphicon-ok') || $icon.hasClass('fueluxicon-bullet') ) {
					$icon.removeClass('fueluxicon-bullet').addClass('glyphicon-ok');
				}
				if (this.options.multiSelect) {
					data.push( $el.data() );
				}
			}

			if(data.length) {
				this.$element.trigger('selected', {selected: data});
			}

			// Return new list of selected items, the item
			// clicked, and the type of event:
			$el.trigger('updated.fu.tree', {
				selected: data,
				item: $el,
				eventType: eventType
			});
		},

		openFolder: function (el) {
			var $el = $(el); // tree-branch-name
			var $branch;
			var $treeFolderContent;
			var $treeFolderContentFirstChild;

			// if item select only
			if (!this.options.folderSelect) {
				$el = $(el).parent(); // tree-branch, if tree-branch-name clicked
			}

			$branch = $el.closest('.tree-branch'); // tree branch
			$treeFolderContent = $branch.find('.tree-branch-children');
			$treeFolderContentFirstChild = $treeFolderContent.eq(0);

			// manipulate branch/folder
			var eventType, classToTarget, classToAdd;
			if ($el.find('.glyphicon-folder-close').length) {
				eventType = 'opened';
				classToTarget = '.glyphicon-folder-close';
				classToAdd = 'glyphicon-folder-open';

				$branch.addClass('tree-open');
				$branch.attr('aria-expanded', 'true');

				$treeFolderContentFirstChild.removeClass('hide');
				if (!$treeFolderContent.children().length) {
					this.populate($treeFolderContent);
				}

			} else if($el.find('.glyphicon-folder-open')) {
				eventType = 'closed';
				classToTarget = '.glyphicon-folder-open';
				classToAdd = 'glyphicon-folder-close';

				$branch.removeClass('tree-open');
				$branch.attr('aria-expanded', 'false');
				$treeFolderContentFirstChild.addClass('hide');

				// remove if no cache
				if (!this.options.cacheItems) {
					$treeFolderContentFirstChild.empty();
				}

			}

			$branch.find('> .tree-branch-header .icon-folder').eq(0)
				.removeClass('glyphicon-folder-close glyphicon-folder-open')
				.addClass(classToAdd);

			this.$element.trigger(eventType, $branch.data());
		},

		selectFolder: function (clickedElement) {
			var $clickedElement = $(clickedElement);
			var $clickedBranch = $clickedElement.closest('.tree-branch');
			var $selectedBranch = this.$element.find('.tree-branch.tree-selected');
			var selectedData = [];
			var eventType = 'selected';

			// select clicked item
			if($clickedBranch.hasClass('tree-selected')) {
				eventType = 'unselected';
				$clickedBranch.removeClass('tree-selected');
			} else {
				$clickedBranch.addClass('tree-selected');
			}

			if (this.options.multiSelect) {

			// get currently selected
				$selectedBranch = this.$element.find('.tree-branch.tree-selected');

				$.each($selectedBranch, function(index, value) {
					var $value = $(value);
					if($value[0] !== $clickedElement[0]) {
						selectedData.push( $(value).data() );
					}
				});

			} else if ($selectedBranch[0] !== $clickedElement[0]) {
				$selectedBranch.removeClass('tree-selected');

				selectedData.push($clickedBranch.data());
			}

			if(selectedData.length) {
				this.$element.trigger('selected.fu.tree', {selected: selectedData});
			}

			// Return new list of selected items, the item
			// clicked, and the type of event:
			$clickedElement.trigger('updated.fu.tree', {
				selected: selectedData,
				item: $clickedElement,
				eventType: eventType
			});
		},

		selectedItems: function () {
			var $sel = this.$element.find('.tree-selected');
			var data = [];

			$.each($sel, function (index, value) {
				data.push($(value).data());
			});
			return data;
		},

		// collapses open folders
		collapse: function () {
			var cacheItems = this.options.cacheItems;

			// find open folders
			this.$element.find('.icon-folder-open').each(function () {
				// update icon class
				var $this = $(this)
					.removeClass('icon-folder-close icon-folder-open')
					.addClass('icon-folder-close');

				// "close" or empty folder contents
				var $parent = $this.parent().parent();
				var $folder = $parent.children('.tree-branch-children');

				$folder.addClass('hide');
				if (!cacheItems) {
					$folder.empty();
				}
			});
		}
	};

	// TREE PLUGIN DEFINITION

	$.fn.tree = function (option) {
		var args = Array.prototype.slice.call( arguments, 1 );
		var methodReturn;

		var $set = this.each(function () {
			var $this   = $( this );
			var data    = $this.data( 'fu.tree' );
			var options = typeof option === 'object' && option;

			if( !data ) $this.data('fu.tree', (data = new Tree( this, options ) ) );
			if( typeof option === 'string' ) methodReturn = data[ option ].apply( data, args );
		});

		return ( methodReturn === undefined ) ? $set : methodReturn;
	};

	$.fn.tree.defaults = {
		dataSource: function(options, callback){},
		multiSelect: false,
		cacheItems: true,
		folderSelect: true
	};

	$.fn.tree.Constructor = Tree;

	$.fn.tree.noConflict = function () {
		$.fn.tree = old;
		return this;
	};

	// NO DATA-API DUE TO NEED OF DATA-SOURCE

// -- BEGIN UMD WRAPPER AFTERWORD --
}));
	// -- END UMD WRAPPER AFTERWORD --
<|endoftext|>


<commit_before>/*
 * StatusNet - a distributed open-source microblogging tool
 * Copyright (C) 2008, StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(document).ready(function(){
	var counterBlackout = false;

	// count character on keyup
	function counter(event){
         if (maxLength <= 0) {
              return;
         }
		var currentLength = $("#notice_data-text").val().length;
		var remaining = maxLength - currentLength;
		var counter = $("#notice_text-count");

		if (remaining.toString() != counter.text()) {
		    if (!counterBlackout || remaining == 0) {
                        if (counter.text() != String(remaining)) {
                            counter.text(remaining);
		        }

                        if (remaining < 0) {
                            $("#form_notice").addClass("warning");
                        } else {
                            $("#form_notice").removeClass("warning");
                        }
                        // Skip updates for the next 500ms.
                        // On slower hardware, updating on every keypress is unpleasant.
                        if (!counterBlackout) {
                            counterBlackout = true;
                            window.setTimeout(clearCounterBlackout, 500);
                        }
                    }
                }
	}

	function clearCounterBlackout() {
		// Allow keyup events to poke the counter again
		counterBlackout = false;
		// Check if the string changed since we last looked
		counter(null);
	}

	function submitonreturn(event) {
		if (event.keyCode == 13 || event.keyCode == 10) {
			// iPhone sends \n not \r for 'return'
			$("#form_notice").submit();
			event.preventDefault();
			event.stopPropagation();
			$("#notice_data-text").blur();
			$("body").focus();
			return false;
		}
		return true;
	}

     // define maxLength if it wasn't defined already

    if (typeof(maxLength) == "undefined") {
         maxLength = 140;
    }

	if ($("#notice_data-text").length) {
         if (maxLength > 0) {
              $("#notice_data-text").bind("keyup", counter);
              // run once in case there's something in there
              counter();
         }

		$("#notice_data-text").bind("keydown", submitonreturn);

        if($('body')[0].id != 'conversation') {
            $("#notice_data-text").focus();
        }
	}

	// XXX: refactor this code

	var favoptions = { dataType: 'xml',
					   beforeSubmit: function(data, target, options) {
					   							$(target).addClass('processing');
												return true;
											  },
					   success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
												var dis = new_form.id;
												var fav = dis.replace('disfavor', 'favor');
												$('form#'+fav).replaceWith(new_form);
												$('form#'+dis).ajaxForm(disoptions).each(addAjaxHidden);
											  }
					 };

	var disoptions = { dataType: 'xml',
					   beforeSubmit: function(data, target, options) {
					   							$(target).addClass('processing');
												return true;
											  },
					   success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
												var fav = new_form.id;
												var dis = fav.replace('favor', 'disfavor');
												$('form#'+dis).replaceWith(new_form);
												$('form#'+fav).ajaxForm(favoptions).each(addAjaxHidden);
											  }
					 };

	var joinoptions = { dataType: 'xml',
					   success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
												var leave = new_form.id;
												var join = leave.replace('leave', 'join');
												$('form#'+join).replaceWith(new_form);
												$('form#'+leave).ajaxForm(leaveoptions).each(addAjaxHidden);
											  }
					 };

	var leaveoptions = { dataType: 'xml',
					   success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
												var join = new_form.id;
												var leave = join.replace('join', 'leave');
												$('form#'+leave).replaceWith(new_form);
												$('form#'+join).ajaxForm(joinoptions).each(addAjaxHidden);
											  }
					 };

	function addAjaxHidden() {
		var ajax = document.createElement('input');
		ajax.setAttribute('type', 'hidden');
		ajax.setAttribute('name', 'ajax');
		ajax.setAttribute('value', 1);
		this.appendChild(ajax);
	}

	$("form.form_favor").ajaxForm(favoptions);
	$("form.form_disfavor").ajaxForm(disoptions);
	$("form.form_group_join").ajaxForm(joinoptions);
	$("form.form_group_leave").ajaxForm(leaveoptions);
	$("form.form_favor").each(addAjaxHidden);
	$("form.form_disfavor").each(addAjaxHidden);
	$("form.form_group_join").each(addAjaxHidden);
	$("form.form_group_leave").each(addAjaxHidden);

	$("#form_user_nudge").ajaxForm ({ dataType: 'xml',
		beforeSubmit: function(xml) { $("#form_user_nudge input[type=submit]").attr("disabled", "disabled");
									  $("#form_user_nudge input[type=submit]").addClass("disabled");
									},
		success: function(xml) { $("#form_user_nudge").replaceWith(document._importNode($("#nudge_response", xml).get(0),true));
							     $("#form_user_nudge input[type=submit]").removeAttr("disabled");
							     $("#form_user_nudge input[type=submit]").removeClass("disabled");
							   }
	 });
	$("#form_user_nudge").each(addAjaxHidden);

	var Subscribe = { dataType: 'xml',
					  beforeSubmit: function(formData, jqForm, options) { $(".form_user_subscribe input[type=submit]").attr("disabled", "disabled");
																	      $(".form_user_subscribe input[type=submit]").addClass("disabled");
																	    },
					  success: function(xml) { var form_unsubscribe = document._importNode($('form', xml).get(0), true);
										  	   var form_unsubscribe_id = form_unsubscribe.id;
											   var form_subscribe_id = form_unsubscribe_id.replace('unsubscribe', 'subscribe');
											   $("form#"+form_subscribe_id).replaceWith(form_unsubscribe);
											   $("form#"+form_unsubscribe_id).ajaxForm(UnSubscribe).each(addAjaxHidden);
											   $("dd.subscribers").text(parseInt($("dd.subscribers").text())+1);
											   $(".form_user_subscribe input[type=submit]").removeAttr("disabled");
											   $(".form_user_subscribe input[type=submit]").removeClass("disabled");
										     }
					};

	var UnSubscribe = { dataType: 'xml',
						beforeSubmit: function(formData, jqForm, options) { $(".form_user_unsubscribe input[type=submit]").attr("disabled", "disabled");
																		    $(".form_user_unsubscribe input[type=submit]").addClass("disabled");
																		  },
					    success: function(xml) { var form_subscribe = document._importNode($('form', xml).get(0), true);
										  		 var form_subscribe_id = form_subscribe.id;
												 var form_unsubscribe_id = form_subscribe_id.replace('subscribe', 'unsubscribe');
												 $("form#"+form_unsubscribe_id).replaceWith(form_subscribe);
												 $("form#"+form_subscribe_id).ajaxForm(Subscribe).each(addAjaxHidden);
												 $("#profile_send_a_new_message").remove();
												 $("#profile_nudge").remove();
											     $("dd.subscribers").text(parseInt($("dd.subscribers").text())-1);
												 $(".form_user_unsubscribe input[type=submit]").removeAttr("disabled");
												 $(".form_user_unsubscribe input[type=submit]").removeClass("disabled");
											   }
					  };

	$(".form_user_subscribe").ajaxForm(Subscribe);
	$(".form_user_unsubscribe").ajaxForm(UnSubscribe);
	$(".form_user_subscribe").each(addAjaxHidden);
	$(".form_user_unsubscribe").each(addAjaxHidden);

	var PostNotice = { dataType: 'xml',
					   beforeSubmit: function(formData, jqForm, options) { if ($("#notice_data-text").get(0).value.length == 0) {
																				$("#form_notice").addClass("warning");
																				return false;
																		   }
																		   $("#form_notice").addClass("processing");
																		   $("#notice_action-submit").attr("disabled", "disabled");
																		   $("#notice_action-submit").addClass("disabled");
																		   return true;
												 						 },
					   timeout: '60000',
					   error: function (xhr, textStatus, errorThrown) {	$("#form_notice").removeClass("processing");
																		$("#notice_action-submit").removeAttr("disabled");
																		$("#notice_action-submit").removeClass("disabled");
																		if (textStatus == "timeout") {
																			alert ("Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists");
																		}
																		else {
																			if ($(".error", xhr.responseXML).length > 0) {
																				$('#form_notice').append(document._importNode($(".error", xhr.responseXML).get(0), true));
																			}
																			else {
																				var HTTP20x30x = [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307];
																				if(jQuery.inArray(parseInt(xhr.status), HTTP20x30x) < 0) {
																					alert("Sorry! We had trouble sending your notice ("+xhr.status+" "+xhr.statusText+"). Please report the problem to the site administrator if this happens again.");
																				}
																				else {
																					$("#notice_data-text").val("");
                                                                                if (maxLength > 0) {
                                                                                counter();
                                                                                }
																				}
																			}
																		}
																	  },
					   success: function(xml) {	if ($("#error", xml).length > 0) {
													var result = document._importNode($("p", xml).get(0), true);
													result = result.textContent || result.innerHTML;
													alert(result);
												}
												else {
												    if($('body')[0].id == 'bookmarklet') {
												        self.close();
												    }
												    if ($("#command_result", xml).length > 0) {
													    var result = document._importNode($("p", xml).get(0), true);
													    result = result.textContent || result.innerHTML;
													    alert(result);
                                                    }
                                                    else {
                                                         li = $("li", xml).get(0);
                                                         if ($("#"+li.id).length == 0) {
                                                            var notice_irt_value = $('#notice_in-reply-to').val();
                                                            var notice_irt = '#notices_primary #notice-'+notice_irt_value;
                                                            if($('body')[0].id == 'conversation') {
                                                                if(notice_irt_value.length > 0 && $(notice_irt+' .notices').length < 1) {
                                                                    $(notice_irt).append('

');
                                                                }
                                                                $($(notice_irt+' .notices')[0]).append(document._importNode(li, true));
                                                            }
                                                            else {
                                                                $("#notices_primary .notices").prepend(document._importNode(li, true));
                                                            }
                                                            $('#'+li.id).css({display:'none'});
                                                            $('#'+li.id).fadeIn(2500);
                                                            NoticeReply();
                                                            NoticeAttachments();
                                                         }
													}
													$("#notice_data-text").val("");
    												$("#notice_data-attach").val("");
    												$("#notice_in-reply-to").val("");
                                                    $('#notice_data-attach_selected').remove();
                                                     if (maxLength > 0) {
                                                          counter();
                                                     }
												}
												$("#form_notice").removeClass("processing");
												$("#notice_action-submit").removeAttr("disabled");
												$("#notice_action-submit").removeClass("disabled");
											 }
					   };
	$("#form_notice").ajaxForm(PostNotice);
	$("#form_notice").each(addAjaxHidden);
    NoticeReply();
    NoticeAttachments();
    NoticeDataAttach();
});

function NoticeReply() {
    if ($('#notice_data-text').length > 0 && $('#content .notice_reply').length > 0) {
        $('#content .notice').each(function() {
            var notice = $(this)[0];
            $($('.notice_reply', notice)[0]).click(function() {
                var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
                NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text());
                return false;
            });
        });
    }
}

function NoticeReplySet(nick,id) {
	rgx_username = /^[0-9a-zA-Z\-_.]*$/;
	if (nick.match(rgx_username)) {
		var text = $("#notice_data-text");
		if (text.length) {
			replyto = "@" + nick + " ";
			text.val(replyto + text.val().replace(RegExp(replyto, 'i'), ''));
			$("#form_notice input#notice_in-reply-to").val(id);
			if (text.get(0).setSelectionRange) {
				var len = text.val().length;
				text.get(0).setSelectionRange(len,len);
				text.get(0).focus();
			}
			return false;
		}
	}
	return true;
}

function NoticeAttachments() {
    $.fn.jOverlay.options = {
        method : 'GET',
        data : '',
        url : '',
        color : '#000',
        opacity : '0.6',
        zIndex : 99,
        center : false,
        imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
        bgClickToClose : true,
        success : function() {
            $('#jOverlayContent').append('<button>×</button>');
            $('#jOverlayContent button').click($.closeOverlay);
        },
        timeout : 0,
        autoHide : true,
        css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'}
    };

    $('#content .notice a.attachment').click(function() {
        $().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
        return false;
    });

    var t;
    $("body:not(#shownotice) #content .notice a.thumbnail").hover(
        function() {
            var anchor = $(this);
            $("a.thumbnail").children('img').hide();
            anchor.closest(".entry-title").addClass('ov');

            if (anchor.children('img').length == 0) {
                t = setTimeout(function() {
                    $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
                        anchor.append(data);
                    });
                }, 500);
            }
            else {
                anchor.children('img').show();
            }
        },
        function() {
            clearTimeout(t);
            $("a.thumbnail").children('img').hide();
            $(this).closest(".entry-title").removeClass('ov');
        }
    );
}

function NoticeDataAttach() {
    NDA = $('#notice_data-attach');
    NDA.change(function() {
        S = '
'+$(this).val()+' <button>×</button>
';
        NDAS = $('#notice_data-attach_selected');
        (NDAS.length > 0) ? NDAS.replaceWith(S) : $('#form_notice').append(S);
        $('#notice_data-attach_selected button').click(function(){
            $('#notice_data-attach_selected').remove();
            NDA.val('');
        });
    });
}
<commit_msg>Refactored XHR forms: .form_user_subscribe .form_user_unsubscribe .form_favor .form_disfavor .form_group_join .form_group_leave .form_user_nudge<commit_after>/*
 * StatusNet - a distributed open-source microblogging tool
 * Copyright (C) 2008, StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(document).ready(function(){
	var counterBlackout = false;

	// count character on keyup
	function counter(event){
         if (maxLength <= 0) {
              return;
         }
		var currentLength = $("#notice_data-text").val().length;
		var remaining = maxLength - currentLength;
		var counter = $("#notice_text-count");

		if (remaining.toString() != counter.text()) {
		    if (!counterBlackout || remaining == 0) {
                        if (counter.text() != String(remaining)) {
                            counter.text(remaining);
		        }

                        if (remaining < 0) {
                            $("#form_notice").addClass("warning");
                        } else {
                            $("#form_notice").removeClass("warning");
                        }
                        // Skip updates for the next 500ms.
                        // On slower hardware, updating on every keypress is unpleasant.
                        if (!counterBlackout) {
                            counterBlackout = true;
                            window.setTimeout(clearCounterBlackout, 500);
                        }
                    }
                }
	}

	function clearCounterBlackout() {
		// Allow keyup events to poke the counter again
		counterBlackout = false;
		// Check if the string changed since we last looked
		counter(null);
	}

	function submitonreturn(event) {
		if (event.keyCode == 13 || event.keyCode == 10) {
			// iPhone sends \n not \r for 'return'
			$("#form_notice").submit();
			event.preventDefault();
			event.stopPropagation();
			$("#notice_data-text").blur();
			$("body").focus();
			return false;
		}
		return true;
	}

     // define maxLength if it wasn't defined already

    if (typeof(maxLength) == "undefined") {
         maxLength = 140;
    }

	if ($("#notice_data-text").length) {
         if (maxLength > 0) {
              $("#notice_data-text").bind("keyup", counter);
              // run once in case there's something in there
              counter();
         }

		$("#notice_data-text").bind("keydown", submitonreturn);

        if($('body')[0].id != 'conversation') {
            $("#notice_data-text").focus();
        }
	}

	function addAjaxHidden() {
		var ajax = document.createElement('input');
		ajax.setAttribute('type', 'hidden');
		ajax.setAttribute('name', 'ajax');
		ajax.setAttribute('value', 1);
		this.appendChild(ajax);
	}

    $('.form_user_subscribe').each(function() { SN.U.FormXHR($(this)); });
    $('.form_user_unsubscribe').each(function() { SN.U.FormXHR($(this)); });
    $('.form_favor').each(function() { SN.U.FormXHR($(this)); });
    $('.form_disfavor').each(function() { SN.U.FormXHR($(this)); });
    $('.form_group_join').each(function() { SN.U.FormXHR($(this)); });
    $('.form_group_leave').each(function() { SN.U.FormXHR($(this)); });
    $('.form_user_nudge').each(function() { SN.U.FormXHR($(this)); });

	var PostNotice = { dataType: 'xml',
					   beforeSubmit: function(formData, jqForm, options) { if ($("#notice_data-text").get(0).value.length == 0) {
																				$("#form_notice").addClass("warning");
																				return false;
																		   }
																		   $("#form_notice").addClass("processing");
																		   $("#notice_action-submit").attr("disabled", "disabled");
																		   $("#notice_action-submit").addClass("disabled");
																		   return true;
												 						 },
					   timeout: '60000',
					   error: function (xhr, textStatus, errorThrown) {	$("#form_notice").removeClass("processing");
																		$("#notice_action-submit").removeAttr("disabled");
																		$("#notice_action-submit").removeClass("disabled");
																		if (textStatus == "timeout") {
																			alert ("Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists");
																		}
																		else {
																			if ($(".error", xhr.responseXML).length > 0) {
																				$('#form_notice').append(document._importNode($(".error", xhr.responseXML).get(0), true));
																			}
																			else {
																				var HTTP20x30x = [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307];
																				if(jQuery.inArray(parseInt(xhr.status), HTTP20x30x) < 0) {
																					alert("Sorry! We had trouble sending your notice ("+xhr.status+" "+xhr.statusText+"). Please report the problem to the site administrator if this happens again.");
																				}
																				else {
																					$("#notice_data-text").val("");
                                                                                if (maxLength > 0) {
                                                                                counter();
                                                                                }
																				}
																			}
																		}
																	  },
					   success: function(xml) {	if ($("#error", xml).length > 0) {
													var result = document._importNode($("p", xml).get(0), true);
													result = result.textContent || result.innerHTML;
													alert(result);
												}
												else {
												    if($('body')[0].id == 'bookmarklet') {
												        self.close();
												    }
												    if ($("#command_result", xml).length > 0) {
													    var result = document._importNode($("p", xml).get(0), true);
													    result = result.textContent || result.innerHTML;
													    alert(result);
                                                    }
                                                    else {
                                                         li = $("li", xml).get(0);
                                                         if ($("#"+li.id).length == 0) {
                                                            var notice_irt_value = $('#notice_in-reply-to').val();
                                                            var notice_irt = '#notices_primary #notice-'+notice_irt_value;
                                                            if($('body')[0].id == 'conversation') {
                                                                if(notice_irt_value.length > 0 && $(notice_irt+' .notices').length < 1) {
                                                                    $(notice_irt).append('

');
                                                                }
                                                                $($(notice_irt+' .notices')[0]).append(document._importNode(li, true));
                                                            }
                                                            else {
                                                                $("#notices_primary .notices").prepend(document._importNode(li, true));
                                                            }
                                                            $('#'+li.id).css({display:'none'});
                                                            $('#'+li.id).fadeIn(2500);
                                                            NoticeReply();
                                                            NoticeAttachments();
                                                         }
													}
													$("#notice_data-text").val("");
    												$("#notice_data-attach").val("");
    												$("#notice_in-reply-to").val("");
                                                    $('#notice_data-attach_selected').remove();
                                                     if (maxLength > 0) {
                                                          counter();
                                                     }
												}
												$("#form_notice").removeClass("processing");
												$("#notice_action-submit").removeAttr("disabled");
												$("#notice_action-submit").removeClass("disabled");
											 }
					   };
	$("#form_notice").ajaxForm(PostNotice);
	$("#form_notice").each(addAjaxHidden);
    NoticeReply();
    NoticeAttachments();
    NoticeDataAttach();
});

function NoticeReply() {
    if ($('#notice_data-text').length > 0 && $('#content .notice_reply').length > 0) {
        $('#content .notice').each(function() {
            var notice = $(this)[0];
            $($('.notice_reply', notice)[0]).click(function() {
                var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname.uid');
                NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text());
                return false;
            });
        });
    }
}

function NoticeReplySet(nick,id) {
	rgx_username = /^[0-9a-zA-Z\-_.]*$/;
	if (nick.match(rgx_username)) {
		var text = $("#notice_data-text");
		if (text.length) {
			replyto = "@" + nick + " ";
			text.val(replyto + text.val().replace(RegExp(replyto, 'i'), ''));
			$("#form_notice input#notice_in-reply-to").val(id);
			if (text.get(0).setSelectionRange) {
				var len = text.val().length;
				text.get(0).setSelectionRange(len,len);
				text.get(0).focus();
			}
			return false;
		}
	}
	return true;
}

function NoticeAttachments() {
    $.fn.jOverlay.options = {
        method : 'GET',
        data : '',
        url : '',
        color : '#000',
        opacity : '0.6',
        zIndex : 99,
        center : false,
        imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
        bgClickToClose : true,
        success : function() {
            $('#jOverlayContent').append('<button>×</button>');
            $('#jOverlayContent button').click($.closeOverlay);
        },
        timeout : 0,
        autoHide : true,
        css : {'max-width':'542px', 'top':'5%', 'left':'32.5%'}
    };

    $('#content .notice a.attachment').click(function() {
        $().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
        return false;
    });

    var t;
    $("body:not(#shownotice) #content .notice a.thumbnail").hover(
        function() {
            var anchor = $(this);
            $("a.thumbnail").children('img').hide();
            anchor.closest(".entry-title").addClass('ov');

            if (anchor.children('img').length == 0) {
                t = setTimeout(function() {
                    $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
                        anchor.append(data);
                    });
                }, 500);
            }
            else {
                anchor.children('img').show();
            }
        },
        function() {
            clearTimeout(t);
            $("a.thumbnail").children('img').hide();
            $(this).closest(".entry-title").removeClass('ov');
        }
    );
}

function NoticeDataAttach() {
    NDA = $('#notice_data-attach');
    NDA.change(function() {
        S = '
'+$(this).val()+' <button>×</button>
';
        NDAS = $('#notice_data-attach_selected');
        (NDAS.length > 0) ? NDAS.replaceWith(S) : $('#form_notice').append(S);
        $('#notice_data-attach_selected button').click(function(){
            $('#notice_data-attach_selected').remove();
            NDA.val('');
        });
    });
}

var SN = { // StatusNet
    C: { // Config
        S: { // Selector
            Disabled: 'disabled',
            Warning: 'warning',
            Error: 'error',
            Processing: 'processing'
        }
    },

    U: { // Utils
        FormXHR: function(f) {
            f.bind('submit', function(e) {
                form_id = $(this)[0].id;
                $.ajax({
                    type: 'POST',
                    url: $(this)[0].action,
                    data: $(this).serialize() + '&ajax=1',
                    beforeSend: function(xhr) {
                        $('#'+form_id).addClass(SN.C.S.Processing);
                        $('#'+form_id+' .submit').addClass(SN.C.S.Disabled);
                        $('#'+form_id+' .submit').attr(SN.C.S.Disabled, SN.C.S.Disabled);
                    },
                    error: function (xhr, textStatus, errorThrown) {
                        alert(errorThrown || textStatus);
                    },
                    success: function(data, textStatus) {
                        if ($('form', data)[0].length > 0) {
                            form_new = $('form', data)[0];
                            $('#'+form_id).replaceWith(document._importNode(form_new, true));
                            $('#'+form_new.id).each(function() { SN.U.FormXHR($(this)); });
                        }
                        else {
                            $('#'+form_id).replaceWith(document._importNode($('p', data)[0], true));
                        }
                    }
                });
                return false;
            });
        }
    }
}
<|endoftext|>


<commit_before>class JSRange {
  // Note about setting "step" with "from"/"to": it is allowed to use "from"/"to" not matching step
  // Step option is best used with powers of 10 (including negative powers) or regular numbers like 2
  // -------
  // Options for JSRange are retrieved for supplied input min
  constructor (inputMin, inputMax, options) {
    this.inputMin           = document.querySelector(inputMin)
    this.inputMax           = document.querySelector(inputMax)
    this._updateObject      = this._update
    this._eventsObject      = this._events
    this._mousemoveThrottle = false
    this._windowResizeThrottle = false

    this.options      = {}
    this.options.min  = options.min   || this.inputMin.getAttribute('min')
    this.options.max  = options.max   || this.inputMin.getAttribute('max')
    this.options.step = options.step  || this.inputMin.getAttribute('step')
    this.options.stepDecimals = this._calculateDecimals(this.options.step)

    this.selected = {
      from: this.options.min,
      to: this.options.max
    }

    this._createBody()
    this._bindEvents()
    this.update()
  }

  // Propably we want to update everything
  update () {
    this._update.all()
  }

  // Sets values:
  // from, to
  set (hash = {}) {
    if (hash.from) {
      this.selected.from = hash.from
    }

    if (hash.to) {
      this.selected.to = hash.to
    }

    this.update()
  }

  _createBody () {
    this.inputMin.style.display = 'none'
    this.inputMax.style.display = 'none'

    this.body = {}

    this.body.parent = document.createElement('div')
    this.body.parent.classList.add('jsr')

    this.body.rail = document.createElement('div')
    this.body.rail.classList.add('jsr_rail')

    this.body.sliders = {}
    this.body.sliders.from = document.createElement('div')
    this.body.sliders.from.classList.add('jsr_slider', 'jsr_slider--from')
    this.body.sliders.from.dataset.jsrType = 'from'
    this.body.sliders.to = document.createElement('div')
    this.body.sliders.to.classList.add('jsr_slider', 'jsr_slider--to')
    this.body.sliders.to.dataset.jsrType = 'to'

    this.body.info = {}
    this.body.info.min = document.createElement('span')
    this.body.info.min.classList.add('jsr_info', 'jsr_info--min')
    this.body.info.min.dataset.jsrType = 'min'
    this.body.info.max = document.createElement('span')
    this.body.info.max.classList.add('jsr_info', 'jsr_info--max')
    this.body.info.max.dataset.jsrType = 'max'

    this.body.info.from = document.createElement('span')
    this.body.info.from.classList.add('jsr_info', 'jsr_info--from')
    this.body.info.from.dataset.jsrType = 'from'
    this.body.info.to = document.createElement('span')
    this.body.info.to.classList.add('jsr_info', 'jsr_info--to')
    this.body.info.to.dataset.jsrType = 'to'

    this.body.info.single = document.createElement('span')
    this.body.info.single.classList.add('jsr_info', 'jsr_info--single')
    this.body.info.singleFrom = document.createElement('span')
    this.body.info.singleFrom.classList.add('jsr_info_singleFrom')
    this.body.info.singleFrom.dataset.jsrType = 'from'
    this.body.info.singleTo = document.createElement('span')
    this.body.info.singleTo.classList.add('jsr_info_singleTo')
    this.body.info.singleTo.dataset.jsrType = 'to'

    this.body.info.single.appendChild(this.body.info.singleFrom)
    this.body.info.single.appendChild(this.body.info.singleTo)

    let elements = [
      this.body.rail,
      this.body.sliders.from,
      this.body.sliders.to,
      this.body.info.min,
      this.body.info.max,
      this.body.info.from,
      this.body.info.to,
      this.body.info.single
    ]

    elements.forEach((element) => {
      this.body.parent.appendChild(element)
    })

    // https://stackoverflow.com/questions/4793604/how-to-do-insert-after-in-javascript-without-using-a-library
    this.inputMax.parentNode.insertBefore(this.body.parent, this.inputMax.nextSibling);
  }

  _solveMove (type, value, clientX) {
    // Let's say somebody want to move a label...
    if (type == 'from') {
      // but it can't move behind 'to'!
      this.selected.from = (value > this.selected.to) ? (this.selected.to) : value
    } else if (type == 'to') {
      // also it can't move before 'from'!
      this.selected.to = (value < this.selected.from) ? (this.selected.from) : value
    // we should be safe now
    // ...
    } else if (type == 'single') {
      // But if, and ONLY IF, hypothetical, somebody would want to move SINGLE label
      // (single means selected values are equal)
      // we need to determine whether he or she is moving it to the LEFT or to the RIGHT,
      // then set the corresponding value and move object to proper label.
      // But who would want to do such a thing?
      let direction = clientX - window.jsrClickX // negative = left, positive = right
      if (direction < 0) {
        this.selected.from == value
        window.jsrMoveObject = this.body.info.from
      } else if (direction > 0) {
        this.selected.to == value
        window.jsrMoveObject = this.body.info.to
      }
      // No, seriously, who?
    }

    // Definitely it can't:
    if (this.selected.from <= this.options.min) {
      // move behind min,
      this.selected.from = this.options.min
    } else if (this.selected.to >= this.options.max) {
      // and behind max
      this.selected.to = this.options.max
    }
  }

  _bindEvents () {
    let mouseDownElements = [
    // Following handle moving basic from/to sliders (dots [sliders] and labels [info])
      this.body.sliders.from,
      this.body.sliders.to,
      this.body.info.from,
      this.body.info.to,
      // Following handle moving single slider
      this.body.info.singleFrom,
      this.body.info.singleTo,
      // Following handle moving slider to min or max
      this.body.info.min,
      this.body.info.max,
      // Following just prevents selecting text
      this.body.info.single
    ]

    mouseDownElements.forEach((element) => {
      element.addEventListener('mousedown', this._events.sliderMouseDown)
    })

    window.addEventListener('resize', this._events.windowResize)

    document.addEventListener('mousemove', this._events.sliderMouseMove)
    document.addEventListener('mouseup', this._events.sliderMouseUp)

    this.body.rail.addEventListener('click', this._events.railClick)
  }

  _calculateDecimals (float) {
    let string = float.toString()
    return string.split('.')[1].length
  }

  _getValueOfPosition (mouseX) {
    let railLeft = this.body.rail.getBoundingClientRect().left
    let diff = mouseX - railLeft
    let value = parseFloat(diff / this.body.rail.offsetWidth * this.options.max)
    value = Math.round(value / this.options.step) * this.options.step
    // let's make finish rounding: move decimal point to the left (*), then to the right (/)
    let stepDecimalsPow = Math.pow(10, this.options.stepDecimals)
    value = Math.round(value * stepDecimalsPow) / stepDecimalsPow
    return value
  }

  // returns left of element relatively to rail
  _getLeftOf (element) {
    let railLeft = parseFloat(this.body.rail.getBoundingClientRect().left)
    let elLeft = parseFloat(element.getBoundingClientRect().left)
    let elRelativeLeft = elLeft - railLeft
    let railWidth = parseFloat(this.body.rail.offsetWidth)
    return elRelativeLeft / railWidth
  }

  // returns right of element (from style + width)
  _getRightOf (element) {
    return this._getLeftOf(element) + this._getWidthOf(element)
  }

  // returns center of element relatively to rail
  _getCenterOf (element) {
    let left = this._getLeftOf(element)
    let widthRatio = this._getWidthOf(element)
    return (left + widthRatio / 2)
  }

  // returns width of element relatively to rail
  _getWidthOf (element) {
    let width = parseFloat(element.offsetWidth)
    let widthRatio = width / parseFloat(this.body.rail.offsetWidth)
    return widthRatio
  }

  // _update returns all events available to use
  // it tries to use the buffering-variable (_eventsObject) to not recreate this big object every time
  get _events () {
    var _this = this;
    return _this._eventsObject || {
      windowResize: function (event) {
        if (!_this._windowResizeThrottle) {
          // throttling function
          _this._windowResizeThrottle = true
          setTimeout(() => { _this._windowResizeThrottle = false }, 50)
          // ./ throttling

          _this.update()
        }
      },

      sliderMouseDown: function (event) {
        event.preventDefault() // prevents selecting text

        let type = event.target.dataset.jsrType
        // Handle only move of handled item - type determines if it is handled
        if (type) {
          if (type == 'min') {
            _this.selected.from = _this.options.min
          } else if (type == 'max') {
            _this.selected.to = _this.options.max
          } else {
            window.jsrMoveObject = event.target
            window.jsrClickX = event.clientX
            return
          }
          // Update after setting values
          _this.update()
        }
      },
      sliderMouseUp: function (event) {
        window.jsrMoveObject = null
      },
      sliderMouseMove: function (event) {
        if (window.jsrMoveObject && !_this._mousemoveThrottle) {
          // throttling function
          _this._mousemoveThrottle = true
          setTimeout(() => { _this._mousemoveThrottle = false }, 20)
          // ./ throttling

          let type = window.jsrMoveObject.dataset.jsrType
          let newSelected = _this._getValueOfPosition(event.clientX)
          _this._solveMove(type, newSelected, event.clientX)
          _this.update()
        }
      },
      railClick: function (event) {
        // determine closer to which slider it was closer
        let clickedValue = _this._getValueOfPosition(event.clientX)
        let selectedAverage = (_this.selected.from + _this.selected.to) / 2

        // determine side of click
        if (clickedValue < selectedAverage) {
          // clicked on the left side of average, move 'from'
          _this.selected.from = clickedValue
        } else {
          // clicked on the right side of average, move 'to'
          _this.selected.to = clickedValue
        }

        _this.update()
      }
    }
  }

  // _update returns all methods available to use to update things
  // it tries to use the buffering-variable (_updateObject) to not recreate this big object every time
  get _update () {
    var _this = this;
    return _this._updateObject || {
      // Here goes every function which should be updated via .all()
      // 'info' should be after 'sliders' because it depends on their value
      _toUpdate: ['rail', 'sliders', 'info', 'values'],
      all: function () {
        this._toUpdate.forEach((item) => {
          this[item]() // Call certain update function
        })
      },

      rail: function () {
        // Calc rail color position
        let start     = _this.selected.from / _this.options.max
        let end       = _this.selected.to / _this.options.max
        // Calc real start (because of weird percentage work)
        let realStart = start / (1 - (end - start))

        // Sets colored part of rail
        // Expects percentages without % sign
        _this.body.rail.style.backgroundPosition  = `${realStart * 100}% 0`
        _this.body.rail.style.backgroundSize      = `${(end - start) * 100}% 100%`
      },

      sliders: function () {
        let startWidthRatio = _this._getWidthOf(_this.body.sliders.from)
        let endWidthRatio   = _this._getWidthOf(_this.body.sliders.to)
        // widthRatio is used to place middle point of slider in the right point
        let start           = _this.selected.from / _this.options.max - startWidthRatio / 2
        let end             = _this.selected.to / _this.options.max - endWidthRatio / 2

        _this.body.sliders.from.style.left  = `${start * 100}%`
        _this.body.sliders.to.style.left    = `${end * 100}%`
      },

      info: function () {
        _this.body.info.min.innerHTML         = _this.options.min
        _this.body.info.max.innerHTML         = _this.options.max
        _this.body.info.from.innerHTML        = _this.selected.from
        _this.body.info.to.innerHTML          = _this.selected.to
        _this.body.info.singleFrom.innerHTML  = _this.selected.from
        _this.body.info.singleTo.innerHTML    = _this.selected.to

        if (_this.selected.from == _this.selected.to) {
          _this.body.info.singleTo.style.display = 'none'
          _this.body.info.singleFrom.dataset.jsrType = 'single'
        } else {
          _this.body.info.singleTo.style.display = 'inline'
          _this.body.info.singleFrom.dataset.jsrType = 'from'
        }

        // position infos
        let fromInfo = _this._getCenterOf(_this.body.sliders.from) - _this._getWidthOf(_this.body.info.from) / 2
        let toInfo = _this._getCenterOf(_this.body.sliders.to) - _this._getWidthOf(_this.body.info.to) / 2
        let slidersMiddle = (_this._getCenterOf(_this.body.sliders.from) + _this._getCenterOf(_this.body.sliders.to)) / 2
        let singleInfo = slidersMiddle - _this._getWidthOf(_this.body.info.single) / 2

        _this.body.info.from.style.left = `${fromInfo * 100}%`
        _this.body.info.to.style.left = `${toInfo * 100}%`
        _this.body.info.single.style.left = `${singleInfo * 100}%`

        // determine infos overlap, and hide them
        // 'from' and 'to' overlaps
        if (_this._getRightOf(_this.body.info.from) > _this._getLeftOf(_this.body.info.to)) {
          _this.body.info.from.style.visibility = 'hidden'
          _this.body.info.to.style.visibility = 'hidden'
          _this.body.info.single.style.visibility = 'visible'
        } else {
          _this.body.info.from.style.visibility = 'visible'
          _this.body.info.to.style.visibility = 'visible'
          _this.body.info.single.style.visibility = 'hidden'
        }

        // 'min' and 'from' overlaps
        if (_this._getRightOf(_this.body.info.min) > _this._getLeftOf(_this.body.info.from)) {
          _this.body.info.min.style.visibility = 'hidden'
        } else {
          _this.body.info.min.style.visibility = 'visible'
        }

        // 'max' and 'to' overlaps
        if (_this._getRightOf(_this.body.info.to) > _this._getLeftOf(_this.body.info.max)) {
          _this.body.info.max.style.visibility = 'hidden'
        } else {
          _this.body.info.max.style.visibility = 'visible'
        }
      },

      values: function () {
        _this.inputMin.setAttribute('value', _this.selected.from)
        _this.inputMax.setAttribute('value', _this.selected.to)
      }
    }
  }
}<commit_msg>Remove jsrClickX after mouseup<commit_after>class JSRange {
  // Note about setting "step" with "from"/"to": it is allowed to use "from"/"to" not matching step
  // Step option is best used with powers of 10 (including negative powers) or regular numbers like 2
  // -------
  // Options for JSRange are retrieved for supplied input min
  constructor (inputMin, inputMax, options) {
    this.inputMin               = document.querySelector(inputMin)
    this.inputMax               = document.querySelector(inputMax)
    this._updateObject          = this._update
    this._eventsObject          = this._events
    this._mousemoveThrottle     = false
    this._windowResizeThrottle  = false

    this.options      = {}
    this.options.min  = options.min   || this.inputMin.getAttribute('min')
    this.options.max  = options.max   || this.inputMin.getAttribute('max')
    this.options.step = options.step  || this.inputMin.getAttribute('step')
    this.options.stepDecimals = this._calculateDecimals(this.options.step)

    this.selected = {
      from: this.options.min,
      to: this.options.max
    }

    this._createBody()
    this._bindEvents()
    this.update()
  }

  // Propably we want to update everything
  update () {
    this._update.all()
  }

  // Sets values:
  // from, to
  set (hash = {}) {
    if (hash.from) {
      this.selected.from = hash.from
    }

    if (hash.to) {
      this.selected.to = hash.to
    }

    this.update()
  }

  _createBody () {
    this.inputMin.style.display = 'none'
    this.inputMax.style.display = 'none'

    this.body = {}

    this.body.parent = document.createElement('div')
    this.body.parent.classList.add('jsr')

    this.body.rail = document.createElement('div')
    this.body.rail.classList.add('jsr_rail')

    this.body.sliders = {}
    this.body.sliders.from = document.createElement('div')
    this.body.sliders.from.classList.add('jsr_slider', 'jsr_slider--from')
    this.body.sliders.from.dataset.jsrType = 'from'
    this.body.sliders.to = document.createElement('div')
    this.body.sliders.to.classList.add('jsr_slider', 'jsr_slider--to')
    this.body.sliders.to.dataset.jsrType = 'to'

    this.body.info = {}
    this.body.info.min = document.createElement('span')
    this.body.info.min.classList.add('jsr_info', 'jsr_info--min')
    this.body.info.min.dataset.jsrType = 'min'
    this.body.info.max = document.createElement('span')
    this.body.info.max.classList.add('jsr_info', 'jsr_info--max')
    this.body.info.max.dataset.jsrType = 'max'

    this.body.info.from = document.createElement('span')
    this.body.info.from.classList.add('jsr_info', 'jsr_info--from')
    this.body.info.from.dataset.jsrType = 'from'
    this.body.info.to = document.createElement('span')
    this.body.info.to.classList.add('jsr_info', 'jsr_info--to')
    this.body.info.to.dataset.jsrType = 'to'

    this.body.info.single = document.createElement('span')
    this.body.info.single.classList.add('jsr_info', 'jsr_info--single')
    this.body.info.singleFrom = document.createElement('span')
    this.body.info.singleFrom.classList.add('jsr_info_singleFrom')
    this.body.info.singleFrom.dataset.jsrType = 'from'
    this.body.info.singleTo = document.createElement('span')
    this.body.info.singleTo.classList.add('jsr_info_singleTo')
    this.body.info.singleTo.dataset.jsrType = 'to'

    this.body.info.single.appendChild(this.body.info.singleFrom)
    this.body.info.single.appendChild(this.body.info.singleTo)

    let elements = [
      this.body.rail,
      this.body.sliders.from,
      this.body.sliders.to,
      this.body.info.min,
      this.body.info.max,
      this.body.info.from,
      this.body.info.to,
      this.body.info.single
    ]

    elements.forEach((element) => {
      this.body.parent.appendChild(element)
    })

    // https://stackoverflow.com/questions/4793604/how-to-do-insert-after-in-javascript-without-using-a-library
    this.inputMax.parentNode.insertBefore(this.body.parent, this.inputMax.nextSibling);
  }

  _solveMove (type, value, clientX) {
    // Let's say somebody want to move a label...
    if (type == 'from') {
      // but it can't move behind 'to'!
      this.selected.from = (value > this.selected.to) ? (this.selected.to) : value
    } else if (type == 'to') {
      // also it can't move before 'from'!
      this.selected.to = (value < this.selected.from) ? (this.selected.from) : value
    // we should be safe now
    // ...
    } else if (type == 'single') {
      // But if, and ONLY IF, hypothetical, somebody would want to move SINGLE label
      // (single means selected values are equal)
      // we need to determine whether he or she is moving it to the LEFT or to the RIGHT,
      // then set the corresponding value and move object to proper label.
      // But who would want to do such a thing?
      let direction = clientX - window.jsrClickX // negative = left, positive = right
      if (direction < 0) {
        this.selected.from == value
        window.jsrMoveObject = this.body.info.from
      } else if (direction > 0) {
        this.selected.to == value
        window.jsrMoveObject = this.body.info.to
      }
      // No, seriously, who?
    }

    // Definitely it can't:
    if (this.selected.from <= this.options.min) {
      // move behind min,
      this.selected.from = this.options.min
    } else if (this.selected.to >= this.options.max) {
      // and behind max
      this.selected.to = this.options.max
    }
  }

  _bindEvents () {
    let mouseDownElements = [
    // Following handle moving basic from/to sliders (dots [sliders] and labels [info])
      this.body.sliders.from,
      this.body.sliders.to,
      this.body.info.from,
      this.body.info.to,
      // Following handle moving single slider
      this.body.info.singleFrom,
      this.body.info.singleTo,
      // Following handle moving slider to min or max
      this.body.info.min,
      this.body.info.max,
      // Following just prevents selecting text
      this.body.info.single
    ]

    mouseDownElements.forEach((element) => {
      element.addEventListener('mousedown', this._events.sliderMouseDown)
    })

    window.addEventListener('resize', this._events.windowResize)

    document.addEventListener('mousemove', this._events.sliderMouseMove)
    document.addEventListener('mouseup', this._events.sliderMouseUp)

    this.body.rail.addEventListener('click', this._events.railClick)
  }

  _calculateDecimals (float) {
    let string = float.toString()
    return string.split('.')[1].length
  }

  _getValueOfPosition (mouseX) {
    let railLeft = this.body.rail.getBoundingClientRect().left
    let diff = mouseX - railLeft
    let value = parseFloat(diff / this.body.rail.offsetWidth * this.options.max)
    value = Math.round(value / this.options.step) * this.options.step
    // let's make finish rounding: move decimal point to the left (*), then to the right (/)
    let stepDecimalsPow = Math.pow(10, this.options.stepDecimals)
    value = Math.round(value * stepDecimalsPow) / stepDecimalsPow
    return value
  }

  // returns left of element relatively to rail
  _getLeftOf (element) {
    let railLeft = parseFloat(this.body.rail.getBoundingClientRect().left)
    let elLeft = parseFloat(element.getBoundingClientRect().left)
    let elRelativeLeft = elLeft - railLeft
    let railWidth = parseFloat(this.body.rail.offsetWidth)
    return elRelativeLeft / railWidth
  }

  // returns right of element (from style + width)
  _getRightOf (element) {
    return this._getLeftOf(element) + this._getWidthOf(element)
  }

  // returns center of element relatively to rail
  _getCenterOf (element) {
    let left = this._getLeftOf(element)
    let widthRatio = this._getWidthOf(element)
    return (left + widthRatio / 2)
  }

  // returns width of element relatively to rail
  _getWidthOf (element) {
    let width = parseFloat(element.offsetWidth)
    let widthRatio = width / parseFloat(this.body.rail.offsetWidth)
    return widthRatio
  }

  // _update returns all events available to use
  // it tries to use the buffering-variable (_eventsObject) to not recreate this big object every time
  get _events () {
    var _this = this;
    return _this._eventsObject || {
      windowResize: function (event) {
        if (!_this._windowResizeThrottle) {
          // throttling function
          _this._windowResizeThrottle = true
          setTimeout(() => { _this._windowResizeThrottle = false }, 50)
          // ./ throttling

          _this.update()
        }
      },

      sliderMouseDown: function (event) {
        event.preventDefault() // prevents selecting text

        let type = event.target.dataset.jsrType
        // Handle only move of handled item - type determines if it is handled
        if (type) {
          if (type == 'min') {
            _this.selected.from = _this.options.min
          } else if (type == 'max') {
            _this.selected.to = _this.options.max
          } else {
            window.jsrMoveObject = event.target
            window.jsrClickX = event.clientX
            return
          }
          // Update after setting values
          _this.update()
        }
      },
      sliderMouseUp: function (event) {
        window.jsrMoveObject = null
        window.jsrClickX = null
      },
      sliderMouseMove: function (event) {
        if (window.jsrMoveObject && !_this._mousemoveThrottle) {
          // throttling function
          _this._mousemoveThrottle = true
          setTimeout(() => { _this._mousemoveThrottle = false }, 20)
          // ./ throttling

          let type = window.jsrMoveObject.dataset.jsrType
          let newSelected = _this._getValueOfPosition(event.clientX)
          _this._solveMove(type, newSelected, event.clientX)
          _this.update()
        }
      },
      railClick: function (event) {
        // determine closer to which slider it was closer
        let clickedValue = _this._getValueOfPosition(event.clientX)
        let selectedAverage = (_this.selected.from + _this.selected.to) / 2

        // determine side of click
        if (clickedValue < selectedAverage) {
          // clicked on the left side of average, move 'from'
          _this.selected.from = clickedValue
        } else {
          // clicked on the right side of average, move 'to'
          _this.selected.to = clickedValue
        }

        _this.update()
      }
    }
  }

  // _update returns all methods available to use to update things
  // it tries to use the buffering-variable (_updateObject) to not recreate this big object every time
  get _update () {
    var _this = this;
    return _this._updateObject || {
      // Here goes every function which should be updated via .all()
      // 'info' should be after 'sliders' because it depends on their value
      _toUpdate: ['rail', 'sliders', 'info', 'values'],
      all: function () {
        this._toUpdate.forEach((item) => {
          this[item]() // Call certain update function
        })
      },

      rail: function () {
        // Calc rail color position
        let start     = _this.selected.from / _this.options.max
        let end       = _this.selected.to / _this.options.max
        // Calc real start (because of weird percentage work)
        let realStart = start / (1 - (end - start))

        // Sets colored part of rail
        // Expects percentages without % sign
        _this.body.rail.style.backgroundPosition  = `${realStart * 100}% 0`
        _this.body.rail.style.backgroundSize      = `${(end - start) * 100}% 100%`
      },

      sliders: function () {
        let startWidthRatio = _this._getWidthOf(_this.body.sliders.from)
        let endWidthRatio   = _this._getWidthOf(_this.body.sliders.to)
        // widthRatio is used to place middle point of slider in the right point
        let start           = _this.selected.from / _this.options.max - startWidthRatio / 2
        let end             = _this.selected.to / _this.options.max - endWidthRatio / 2

        _this.body.sliders.from.style.left  = `${start * 100}%`
        _this.body.sliders.to.style.left    = `${end * 100}%`
      },

      info: function () {
        _this.body.info.min.innerHTML         = _this.options.min
        _this.body.info.max.innerHTML         = _this.options.max
        _this.body.info.from.innerHTML        = _this.selected.from
        _this.body.info.to.innerHTML          = _this.selected.to
        _this.body.info.singleFrom.innerHTML  = _this.selected.from
        _this.body.info.singleTo.innerHTML    = _this.selected.to

        if (_this.selected.from == _this.selected.to) {
          _this.body.info.singleTo.style.display = 'none'
          _this.body.info.singleFrom.dataset.jsrType = 'single'
        } else {
          _this.body.info.singleTo.style.display = 'inline'
          _this.body.info.singleFrom.dataset.jsrType = 'from'
        }

        // position infos
        let fromInfo = _this._getCenterOf(_this.body.sliders.from) - _this._getWidthOf(_this.body.info.from) / 2
        let toInfo = _this._getCenterOf(_this.body.sliders.to) - _this._getWidthOf(_this.body.info.to) / 2
        let slidersMiddle = (_this._getCenterOf(_this.body.sliders.from) + _this._getCenterOf(_this.body.sliders.to)) / 2
        let singleInfo = slidersMiddle - _this._getWidthOf(_this.body.info.single) / 2

        _this.body.info.from.style.left = `${fromInfo * 100}%`
        _this.body.info.to.style.left = `${toInfo * 100}%`
        _this.body.info.single.style.left = `${singleInfo * 100}%`

        // determine infos overlap, and hide them
        // 'from' and 'to' overlaps
        if (_this._getRightOf(_this.body.info.from) > _this._getLeftOf(_this.body.info.to)) {
          _this.body.info.from.style.visibility = 'hidden'
          _this.body.info.to.style.visibility = 'hidden'
          _this.body.info.single.style.visibility = 'visible'
        } else {
          _this.body.info.from.style.visibility = 'visible'
          _this.body.info.to.style.visibility = 'visible'
          _this.body.info.single.style.visibility = 'hidden'
        }

        // 'min' and 'from' overlaps
        if (_this._getRightOf(_this.body.info.min) > _this._getLeftOf(_this.body.info.from)) {
          _this.body.info.min.style.visibility = 'hidden'
        } else {
          _this.body.info.min.style.visibility = 'visible'
        }

        // 'max' and 'to' overlaps
        if (_this._getRightOf(_this.body.info.to) > _this._getLeftOf(_this.body.info.max)) {
          _this.body.info.max.style.visibility = 'hidden'
        } else {
          _this.body.info.max.style.visibility = 'visible'
        }
      },

      values: function () {
        _this.inputMin.setAttribute('value', _this.selected.from)
        _this.inputMax.setAttribute('value', _this.selected.to)
      }
    }
  }
}<|endoftext|>


<commit_before>OC.L10N.register(
    "audioplayer",
    {
	"Welcome to" : "Willkommen bei",
	"Audio Player" : "Audio-Player",
	"Add new tracks to library" : "Der Bibliothek neue Titel hinzufügen",
	"Scan for new audio files" : "Nach neuen Audiodateien suchen",
	"New audio files available" : "Neue Audiodateien verfügbar",
	"Help" : "Hilfe",
	"Sort playlist" : "Wiedergabeliste sortieren",
	"Rename playlist" : "Wiedergabeliste umbenennen",
	"Delete playlist" : "Wiedergabeliste löschen",
	"Add new tracks to playlist by drag and drop" : "Der Wiedergabeliste neue Titel per Drag und Drop hinzufügen",
	"No playlist selected!" : "Keine Wiedergabeliste ausgewählt!",
	"Sort modus active" : "Sortiermodus aktiv",
	"Playlist successfully deleted!" : "Wiedergabeliste erfolgreich gelöscht!",
	"Start scanning…" : "Scannen starten…",
	"Scanning finished! New Audios found!" : "Scannen beendet! Neue Audiodateien gefunden!",
	"Yes" : "Ja",
	"No" : "Nein",
	"Error" : "Fehler",
	"Are you sure?" : "Bist du sicher?",
	"Disc" : "Disc",
	"Track" : "Stück",
	"File" : "Datei",
	"Title" : "Titel",
	"Subtitle" : "Untertitel",
	"Artist" : "Interpret",
	"Album Artist" : "Albuminterpret",
	"Genre" : "Genre",
	"Year" : "Jahr",
	"Album" : "Album",
	"Albums" : "Alben",
	"Composer" : "Composer",
	"Edit metadata" : "Metadaten bearbeiten",
	"Remove" : "Entfernen",
	"Options" : "Optionen",
	"MIME type" : "MIME-Typ",
	"MIME type not supported by browser" : "MIME-Typ vom Browser nicht unterstützt",
	"Missing permission to edit metadata of track!" : "Fehlende Berechtigung zum Bearbeiten der Metadaten des Stücks!",
	"Existing Albums" : "Vorhandene Alben",
	"New Album" : "Neues Album",
	"Existing Artists" : "Vorhandene Interpreten",
	"New Artist" : " Neuer Interpret",
	"Existing Genres" : "Vorhandene Genres",
	"New Genre" : "Neues Genre",
	"Add as Album Cover" : "Als Album-Cover verwenden",
	"of" : "von",
	"Total" : "Gesamt",
	"Select from cloud" : "Bild aus Cloud wählen",
	"Edit" : "Bearbeiten",
	"Delete" : "Löschen",
	"Upload" : "Hochladen",
	"Drop picture" : "Bild fallen lassen",
	"Select picture" : "Bild auswählen",
	"Picture generating, please wait…" : "Bilderzeugung, bitte warten…",
	"Drag picture here!" : "Bild hierher ziehen",
	"Settings" : "Einstellungen",
	"Select a single folder with audio files" : "Einzelnen Ordner mit Audiodateien auswählen",
	"Invalid path!" : "Ungültiger Pfad!",
	"saved" : "gespeichert",
	"Reset library" : "Bibliothek zurücksetzen",
	"Start resetting library…" : "Bibliothek wird zurückgesetzt…",
	"Resetting finished!" : "Zurücksetzen beendet!",
	"All library entries will be deleted!" : "Alle Bibliothekseinträge werden gelöscht!",
	"Close" : "Schließen",
	"Play" : "Abspielen",
	"Save" : "Speichern",
	"Selected" : "Ausgewählt",
	"Selected Album" : "Ausgewähltes Album",
	"Selected Artist" : "Ausgewählter Interpret",
        "Selected Album Artist": "Ausgewählter Albuminterpret",
	"Selected Composer" : "Ausgewählter Komponist",
	"Selected Folder" : "Ausgewählter Ordner",
	"Selected Genre" : "Ausgewähltes Genre",
	"Selected Playlist" : "Ausgewählte Wiedergabeliste",
	"Selected Year" : "Ausgewähltes Jahr",
	"Length" : "Länge"
},
"");
<commit_msg>New or updated audio files available<commit_after>OC.L10N.register(
    "audioplayer",
    {
	"Welcome to" : "Willkommen bei",
	"Audio Player" : "Audio-Player",
	"Add new tracks to library" : "Der Bibliothek neue Titel hinzufügen",
	"Scan for new audio files" : "Nach neuen Audiodateien suchen",
	"New or updated audio files available" : "Neue oder aktualisierte Audiodateien verfügbar",
	"Help" : "Hilfe",
	"Sort playlist" : "Wiedergabeliste sortieren",
	"Rename playlist" : "Wiedergabeliste umbenennen",
	"Delete playlist" : "Wiedergabeliste löschen",
	"Add new tracks to playlist by drag and drop" : "Der Wiedergabeliste neue Titel per Drag und Drop hinzufügen",
	"No playlist selected!" : "Keine Wiedergabeliste ausgewählt!",
	"Sort modus active" : "Sortiermodus aktiv",
	"Playlist successfully deleted!" : "Wiedergabeliste erfolgreich gelöscht!",
	"Start scanning…" : "Scannen starten…",
	"Scanning finished! New Audios found!" : "Scannen beendet! Neue Audiodateien gefunden!",
	"Yes" : "Ja",
	"No" : "Nein",
	"Error" : "Fehler",
	"Are you sure?" : "Bist du sicher?",
	"Disc" : "Disc",
	"Track" : "Stück",
	"File" : "Datei",
	"Title" : "Titel",
	"Subtitle" : "Untertitel",
	"Artist" : "Interpret",
	"Album Artist" : "Albuminterpret",
	"Genre" : "Genre",
	"Year" : "Jahr",
	"Album" : "Album",
	"Albums" : "Alben",
	"Composer" : "Composer",
	"Edit metadata" : "Metadaten bearbeiten",
	"Remove" : "Entfernen",
	"Options" : "Optionen",
	"MIME type" : "MIME-Typ",
	"MIME type not supported by browser" : "MIME-Typ vom Browser nicht unterstützt",
	"Missing permission to edit metadata of track!" : "Fehlende Berechtigung zum Bearbeiten der Metadaten des Stücks!",
	"Existing Albums" : "Vorhandene Alben",
	"New Album" : "Neues Album",
	"Existing Artists" : "Vorhandene Interpreten",
	"New Artist" : " Neuer Interpret",
	"Existing Genres" : "Vorhandene Genres",
	"New Genre" : "Neues Genre",
	"Add as Album Cover" : "Als Album-Cover verwenden",
	"of" : "von",
	"Total" : "Gesamt",
	"Select from cloud" : "Bild aus Cloud wählen",
	"Edit" : "Bearbeiten",
	"Delete" : "Löschen",
	"Upload" : "Hochladen",
	"Drop picture" : "Bild fallen lassen",
	"Select picture" : "Bild auswählen",
	"Picture generating, please wait…" : "Bilderzeugung, bitte warten…",
	"Drag picture here!" : "Bild hierher ziehen",
	"Settings" : "Einstellungen",
	"Select a single folder with audio files" : "Einzelnen Ordner mit Audiodateien auswählen",
	"Invalid path!" : "Ungültiger Pfad!",
	"saved" : "gespeichert",
	"Reset library" : "Bibliothek zurücksetzen",
	"Start resetting library…" : "Bibliothek wird zurückgesetzt…",
	"Resetting finished!" : "Zurücksetzen beendet!",
	"All library entries will be deleted!" : "Alle Bibliothekseinträge werden gelöscht!",
	"Close" : "Schließen",
	"Play" : "Abspielen",
	"Save" : "Speichern",
	"Selected" : "Ausgewählt",
	"Selected Album" : "Ausgewähltes Album",
	"Selected Artist" : "Ausgewählter Interpret",
        "Selected Album Artist": "Ausgewählter Albuminterpret",
	"Selected Composer" : "Ausgewählter Komponist",
	"Selected Folder" : "Ausgewählter Ordner",
	"Selected Genre" : "Ausgewähltes Genre",
	"Selected Playlist" : "Ausgewählte Wiedergabeliste",
	"Selected Year" : "Ausgewähltes Jahr",
	"Length" : "Länge"
},
"");
<|endoftext|>


<commit_before>var LeapOut = {
  ws: null,
  ctx: null,
  width: null,
  height: null,
  el: null,
  debugEl: null,
  leapMinX: -200,
  leapMaxX: 200,
  leapMinY: 100,
  leapMaxY: 600,
  leapMinZ: -180,
  leapMaxZ: 180,
  started: false,
  reset: false,
  intervalId: null,
  brickRows: 5,
  brickCols: 10,
  brickHeight: 20,
  brickWidth: null,
  brickPad: 1,
  bricks: new Array(),

  paddleStart: {
    'x': null,
    'y': null,
    'width': 200,
    'height': 10
  },

  ballStart: {
    'radius': 10,
    'x': 250,
    'y': 150,
    'dx': 4,
    'dy': 6
  },

  paddle: null,
  ball: null,

  init: function(el, debugEl) {
    this.el = $(el);
    this.debugEl = $(debugEl);

    // Support both the WebSocket and MozWebSocket objects
    if ((typeof(WebSocket) == 'undefined') &&
        (typeof(MozWebSocket) != 'undefined')) {
      WebSocket = MozWebSocket;
    }

    var w = this.width = $(window).width();
    var h = this.height = $(window).height();
    $(el).attr('width', w).css('width', w).attr('height', h).css('height', h);
    $(el).css('position', 'absolute').css('left', '0').css('top', '0');

    this.ctx = $(el)[0].getContext("2d");
    this.ws = new WebSocket("ws://localhost:6437/");

    this.ws.onopen = function(event) {
      LeapOut.debug("WebSocket connection open!");
      LeapOut.start();
    };

    this.ws.onclose = function(event) {
      LeapOut.ws = null;
      LeapOut.debug("WebSocket connection closed");
    };

    this.ws.onerror = function(event) {
      LeapOut.debug("Received error");
    };

    this.ws.onmessage = function(event) {
      if (LeapOut.started) {
        var obj = JSON.parse(event.data);
        var str = JSON.stringify(obj, undefined, 2);

        LeapOut.debug(str);

        if (typeof(obj.hands) != 'undefined' && obj.hands.length > 0) {
          var hand = obj.hands[0];
          var x = hand.palmPosition[0];
          LeapOut.paddle.x = LeapOut.scale(x, LeapOut.leapMinX, LeapOut.leapMaxX, 0, LeapOut.width - LeapOut.paddle.width);
          if (LeapOut.paddle.x > LeapOut.width - LeapOut.paddle.width) { LeapOut.paddle.x = LeapOut.width - LeapOut.paddle.width; }
          if (LeapOut.paddle.x < 0) { LeapOut.paddle.x = 0; }
        }
      }
    };

    $(document.body).click(function() {
      if (LeapOut.reset) {
        LeapOut.start();
      } else {
        LeapOut.toggle();
      }
    });

    LeapOut.started = true;

    return this.el;
  },

  start: function() {
    this.paddle = jQuery.extend(true, {}, this.paddleStart);
    this.ball = jQuery.extend(true, {}, this.ballStart);
    this.buildBricks();

    this.paddle.x = (this.width - this.paddle.width) / 2;
    this.paddle.y = this.height - this.paddle.height;

    var self = this;
    this.intervalId = setInterval(function() {
      if (self.started) {
        self.draw();
      }
    });

    this.reset = false;
    this.started = true;
    return this.intervalId;
  },

  buildBricks: function() {
    this.brickWidth = (this.width / this.brickCols) - 1;

    this.bricks = new Array(this.brickRows);
    for (i=0; i < this.brickRows; i++) {
      this.bricks[i] = new Array(this.brickCols);
      for (j=0; j < this.brickCols; j++) {
        this.bricks[i][j] = 1;
      }
    }
  },

  draw: function() {
    this.clear();
    this.drawBricks();
    this.drawPaddle();
    this.drawBall();

    // hit a brick?
    var rowHeight = this.brickHeight + this.brickPad;
    var colWidth = this.brickWidth + this.brickPad;
    var row = Math.floor(this.ball.y / rowHeight);
    var col = Math.floor(this.ball.x / colWidth);

    // reverse the ball and mark the brick as broken
    if ((this.ball.y < this.brickRows * rowHeight) && (row >= 0) && (col >= 0) && (this.bricks[row][col] == 1)) {
      this.ball.dy = -this.ball.dy;
      this.bricks[row][col] = 0;
    }

    // check edges, bounce...
    if (this.ball.x + this.ball.dx > this.width || this.ball.x + this.ball.dx < 0) {
      this.ball.dx = -this.ball.dx;
    }

    if (this.ball.y + this.ball.dy < 0) {
      this.ball.dy = -this.ball.dy;

    } else if (this.ball.y + this.ball.dy > this.height) {

      // paddle hit
      if (this.ball.x > this.paddle.x && this.ball.x < this.paddle.x + this.paddle.width) {
        this.ball.dy = -this.ball.dy;

      // game over
      } else {
        clearInterval(this.intervalId);
        LeapOut.debug('Game Over! Click to restart.');
        this.started = false;
        this.reset = true;
      }
    }

    this.ball.x += this.ball.dx;
    this.ball.y += this.ball.dy;
  },

  clear: function() {
    this.ctx.clearRect(0, 0, this.width, this.height);
  },

  drawBricks: function() {
    var ctx = this.ctx;
    ctx.beginPath();

    for (i=0; i < this.brickRows; i++) {
      for (j=0; j < this.brickCols; j++) {
        if (this.bricks[i][j] == 1) {
          ctx.rect((j * (this.brickWidth + this.brickPad)) + this.brickPad,
                   (i * (this.brickHeight + this.brickPad)) + this.brickPad,
                   this.brickWidth, this.brickHeight);
          ctx.closePath();
          ctx.fill();
        }
      }
    }
  },

  drawPaddle: function() {
    var ctx = this.ctx;
    ctx.beginPath();
    ctx.rect(this.paddle.x, this.paddle.y, this.paddle.width, this.paddle.height);
    ctx.closePath();
    ctx.fill();
  },

  drawBall: function() {
    var ctx = this.ctx;
    ctx.beginPath();
    ctx.arc(this.ball.x, this.ball.y, this.ball.radius, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fill();
  },

  scale: function(value, oldMin, oldMax, newMin, newMax) {
    return (((newMax - newMin) * (value - oldMin)) / (oldMax - oldMin)) + newMin;
  },

  toggle: function() {
    if (LeapOut.started) {
      LeapOut.started = false;
    } else {
      LeapOut.started = true;
    }
  },

  debug: function(message) {
    if (LeapOut.debugEl) {
      LeapOut.debugEl.text(message);
    }
  }
};
<commit_msg>increase paddle and ball size<commit_after>var LeapOut = {
  ws: null,
  ctx: null,
  width: null,
  height: null,
  el: null,
  debugEl: null,
  leapMinX: -200,
  leapMaxX: 200,
  leapMinY: 100,
  leapMaxY: 600,
  leapMinZ: -180,
  leapMaxZ: 180,
  started: false,
  reset: false,
  intervalId: null,
  brickRows: 5,
  brickCols: 10,
  brickHeight: 20,
  brickWidth: null,
  brickPad: 1,
  bricks: new Array(),

  paddleStart: {
    'x': null,
    'y': null,
    'width': 200,
    'height': 20
  },

  ballStart: {
    'radius': 15,
    'x': 250,
    'y': 150,
    'dx': 4,
    'dy': 6
  },

  paddle: null,
  ball: null,

  init: function(el, debugEl) {
    this.el = $(el);
    this.debugEl = $(debugEl);

    // Support both the WebSocket and MozWebSocket objects
    if ((typeof(WebSocket) == 'undefined') &&
        (typeof(MozWebSocket) != 'undefined')) {
      WebSocket = MozWebSocket;
    }

    var w = this.width = $(window).width();
    var h = this.height = $(window).height();
    $(el).attr('width', w).css('width', w).attr('height', h).css('height', h);
    $(el).css('position', 'absolute').css('left', '0').css('top', '0');

    this.ctx = $(el)[0].getContext("2d");
    this.ws = new WebSocket("ws://localhost:6437/");

    this.ws.onopen = function(event) {
      LeapOut.debug("WebSocket connection open!");
      LeapOut.start();
    };

    this.ws.onclose = function(event) {
      LeapOut.ws = null;
      LeapOut.debug("WebSocket connection closed");
    };

    this.ws.onerror = function(event) {
      LeapOut.debug("Received error");
    };

    this.ws.onmessage = function(event) {
      if (LeapOut.started) {
        var obj = JSON.parse(event.data);
        var str = JSON.stringify(obj, undefined, 2);

        LeapOut.debug(str);

        if (typeof(obj.hands) != 'undefined' && obj.hands.length > 0) {
          var hand = obj.hands[0];
          var x = hand.palmPosition[0];
          LeapOut.paddle.x = LeapOut.scale(x, LeapOut.leapMinX, LeapOut.leapMaxX, 0, LeapOut.width - LeapOut.paddle.width);
          if (LeapOut.paddle.x > LeapOut.width - LeapOut.paddle.width) { LeapOut.paddle.x = LeapOut.width - LeapOut.paddle.width; }
          if (LeapOut.paddle.x < 0) { LeapOut.paddle.x = 0; }
        }
      }
    };

    $(document.body).click(function() {
      if (LeapOut.reset) {
        LeapOut.start();
      } else {
        LeapOut.toggle();
      }
    });

    LeapOut.started = true;

    return this.el;
  },

  start: function() {
    this.paddle = jQuery.extend(true, {}, this.paddleStart);
    this.ball = jQuery.extend(true, {}, this.ballStart);
    this.buildBricks();

    this.paddle.x = (this.width - this.paddle.width) / 2;
    this.paddle.y = this.height - this.paddle.height;

    var self = this;
    this.intervalId = setInterval(function() {
      if (self.started) {
        self.draw();
      }
    });

    this.reset = false;
    this.started = true;
    return this.intervalId;
  },

  buildBricks: function() {
    this.brickWidth = (this.width / this.brickCols) - 1;

    this.bricks = new Array(this.brickRows);
    for (i=0; i < this.brickRows; i++) {
      this.bricks[i] = new Array(this.brickCols);
      for (j=0; j < this.brickCols; j++) {
        this.bricks[i][j] = 1;
      }
    }
  },

  draw: function() {
    this.clear();
    this.drawBricks();
    this.drawPaddle();
    this.drawBall();

    // hit a brick?
    var rowHeight = this.brickHeight + this.brickPad;
    var colWidth = this.brickWidth + this.brickPad;
    var row = Math.floor(this.ball.y / rowHeight);
    var col = Math.floor(this.ball.x / colWidth);

    // reverse the ball and mark the brick as broken
    if ((this.ball.y < this.brickRows * rowHeight) && (row >= 0) && (col >= 0) && (this.bricks[row][col] == 1)) {
      this.ball.dy = -this.ball.dy;
      this.bricks[row][col] = 0;
    }

    // check edges, bounce...
    if (this.ball.x + this.ball.dx > this.width || this.ball.x + this.ball.dx < 0) {
      this.ball.dx = -this.ball.dx;
    }

    if (this.ball.y + this.ball.dy < 0) {
      this.ball.dy = -this.ball.dy;

    } else if (this.ball.y + this.ball.dy > this.height) {

      // paddle hit
      if (this.ball.x > this.paddle.x && this.ball.x < this.paddle.x + this.paddle.width) {
        this.ball.dy = -this.ball.dy;

      // game over
      } else {
        clearInterval(this.intervalId);
        LeapOut.debug('Game Over! Click to restart.');
        this.started = false;
        this.reset = true;
      }
    }

    this.ball.x += this.ball.dx;
    this.ball.y += this.ball.dy;
  },

  clear: function() {
    this.ctx.clearRect(0, 0, this.width, this.height);
  },

  drawBricks: function() {
    var ctx = this.ctx;
    ctx.beginPath();

    for (i=0; i < this.brickRows; i++) {
      for (j=0; j < this.brickCols; j++) {
        if (this.bricks[i][j] == 1) {
          ctx.rect((j * (this.brickWidth + this.brickPad)) + this.brickPad,
                   (i * (this.brickHeight + this.brickPad)) + this.brickPad,
                   this.brickWidth, this.brickHeight);
          ctx.closePath();
          ctx.fill();
        }
      }
    }
  },

  drawPaddle: function() {
    var ctx = this.ctx;
    ctx.beginPath();
    ctx.rect(this.paddle.x, this.paddle.y, this.paddle.width, this.paddle.height);
    ctx.closePath();
    ctx.fill();
  },

  drawBall: function() {
    var ctx = this.ctx;
    ctx.beginPath();
    ctx.arc(this.ball.x, this.ball.y, this.ball.radius, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fill();
  },

  scale: function(value, oldMin, oldMax, newMin, newMax) {
    return (((newMax - newMin) * (value - oldMin)) / (oldMax - oldMin)) + newMin;
  },

  toggle: function() {
    if (LeapOut.started) {
      LeapOut.started = false;
    } else {
      LeapOut.started = true;
    }
  },

  debug: function(message) {
    if (LeapOut.debugEl) {
      LeapOut.debugEl.text(message);
    }
  }
};
<|endoftext|>