<commit_before>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name DomElement
 * @namespace
 * @private
 */
var DomElement = new function() {
	// We use a mix of Bootstrap.js legacy and Bonzo.js magic, ported over and
	// furhter simplified to a subset actually required by Paper.js

	var special = /^(checked|value|selected|disabled)$/i,
		translated = { text: 'textContent', html: 'innerHTML' },
		unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 };

	function create(nodes, parent) {
		var res = [];
		for (var i =  0, l = nodes && nodes.length; i < l;) {
			var el = nodes[i++];
			if (typeof el === 'string') {
				el = document.createElement(el);
			} else if (!el || !el.nodeType) {
				continue;
			}
			// Do we have attributes?
			if (Base.isPlainObject(nodes[i]))
				DomElement.set(el, nodes[i++]);
			// Do we have children?
			if (Array.isArray(nodes[i]))
				create(nodes[i++], el);
			// Are we adding to a parent?
			if (parent)
				parent.appendChild(el);
			res.push(el);
		}
		return res;
	}

	// Handles both getting and setting of vendor prefix values
	function handlePrefix(el, name, set, value) {
		var prefixes = ['webkit', 'moz', 'Moz', 'ms', 'o', ''],
			suffix = name[0].toUpperCase() + name.substring(1);
		for (var i = 0; i < 6; i++) {
			var prefix = prefixes[i],
				key = prefix ? prefix + suffix : name;
			if (key in el) {
				if (set) {
					el[key] = value;
				} else {
					return el[key];
				}
				break;
			}
		}
	}

	return /** @lends DomElement */{
		create: function(nodes, parent) {
			var isArray = Array.isArray(nodes),
				res = create(isArray ? nodes : arguments, isArray ? parent : null);
			return res.length == 1 ? res[0] : res;
		},

		find: function(selector, root) {
			return (root || document).querySelector(selector);
		},

		findAll: function(selector, root) {
			return (root || document).querySelectorAll(selector);
		},

		get: function(el, key) {
			return el
				? special.test(key)
					? key === 'value' || typeof el[key] !== 'string'
						? el[key]
						: true
					: key in translated
						? el[translated[key]]
						: el.getAttribute(key)
				: null;
		},

		set: function(el, key, value) {
			if (typeof key !== 'string') {
				for (var name in key)
					if (key.hasOwnProperty(name))
						this.set(el, name, key[name]);
			} else if (!el || value === undefined) {
				return el;
			} else if (special.test(key)) {
				el[key] = value;
			} else if (key in translated) {
				el[translated[key]] = value;
			} else if (key === 'style') {
				this.setStyle(el, value);
			} else if (key === 'events') {
				DomEvent.add(el, value);
			} else {
				el.setAttribute(key, value);
			}
			return el;
		},

		getStyles: function(el) {
			// If el is a document (nodeType == 9), use it directly
			var doc = el && el.nodeType !== 9 ? el.ownerDocument : el,
				view = doc && doc.defaultView;
			return view && view.getComputedStyle(el, '');
		},

		getStyle: function(el, key) {
			return el && el.style[key] || this.getStyles(el)[key] || null;
		},

		setStyle: function(el, key, value) {
			if (typeof key !== 'string') {
				for (var name in key)
					if (key.hasOwnProperty(name))
						this.setStyle(el, name, key[name]);
			} else {
				if (/^-?[\d\.]+$/.test(value) && !(key in unitless))
					value += 'px';
				el.style[key] = value;
			}
			return el;
		},

		hasClass: function(el, cls) {
			return new RegExp('\\s*' + cls + '\\s*').test(el.className);
		},

		addClass: function(el, cls) {
			el.className = (el.className + ' ' + cls).trim();
		},

		removeClass: function(el, cls) {
			el.className = el.className.replace(
				new RegExp('\\s*' + cls + '\\s*'), ' ').trim();
		},

		remove: function(el) {
			if (el.parentNode)
				el.parentNode.removeChild(el);
		},

		removeChildren: function(el) {
			while (el.firstChild)
				el.removeChild(el.firstChild);
		},

		getBounds: function(el, viewport) {
			var doc = el.ownerDocument,
				body = doc.body,
				html = doc.documentElement,
				rect;
			try {
				// On IE, for nodes that are not inside the DOM, this throws an
				// exception. Emulate the behavior of all other browsers, which
				// return a rectangle of 0 dimensions.
				rect = el.getBoundingClientRect();
			} catch (e) {
				rect = { left: 0, top: 0, width: 0, height: 0 };
			}
			var x = rect.left - (html.clientLeft || body.clientLeft || 0),
				y = rect.top - (html.clientTop  || body.clientTop  || 0);
			if (!viewport) {
				var view = doc.defaultView;
				x += view.pageXOffset || html.scrollLeft || body.scrollLeft;
				y += view.pageYOffset || html.scrollTop || body.scrollTop;
			}
			return new Rectangle(x, y, rect.width, rect.height);
		},

		getViewportBounds: function(el) {
			var doc = el.ownerDocument,
				view = doc.defaultView,
				html = doc.documentElement;
			return new Rectangle(0, 0,
				view.innerWidth || html.clientWidth,
				view.innerHeight || html.clientHeight
			);
		},

		getOffset: function(el, viewport) {
			return this.getBounds(el, viewport).getPoint();
		},

		getSize: function(el) {
			return this.getBounds(el, true).getSize();
		},

		/**
		 * Checks if element is invisibile (display: none, ...)
		 */
		isInvisible: function(el) {
			return this.getSize(el).equals(new Size(0, 0));
		},

		/**
		 * Checks if element is visibile in current viewport
		 */
		isInView: function(el) {
			// See if the viewport bounds intersect with the windows rectangle
			// which always starts at 0, 0
			return !this.isInvisible(el) && this.getViewportBounds(el).intersects(
					this.getBounds(el, true));
		},

		/**
		 * Gets the given property from the element, trying out all browser
		 * prefix variants.
		 */
		getPrefixed: function(el, name) {
			return handlePrefix(el, name);
		},

		setPrefixed: function(el, name, value) {
			if (typeof name === 'object') {
				for (var key in name)
					handlePrefix(el, key, true, name[key]);
			} else {
				handlePrefix(el, name, true, value);
			}
		}
	};
};
<commit_msg>Try un-prefixed name before various prefixes, to prevent console warnings.<commit_after>/*
 * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
 * http://paperjs.org/
 *
 * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
 * http://scratchdisk.com/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name DomElement
 * @namespace
 * @private
 */
var DomElement = new function() {
	// We use a mix of Bootstrap.js legacy and Bonzo.js magic, ported over and
	// furhter simplified to a subset actually required by Paper.js

	var special = /^(checked|value|selected|disabled)$/i,
		translated = { text: 'textContent', html: 'innerHTML' },
		unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 };

	function create(nodes, parent) {
		var res = [];
		for (var i =  0, l = nodes && nodes.length; i < l;) {
			var el = nodes[i++];
			if (typeof el === 'string') {
				el = document.createElement(el);
			} else if (!el || !el.nodeType) {
				continue;
			}
			// Do we have attributes?
			if (Base.isPlainObject(nodes[i]))
				DomElement.set(el, nodes[i++]);
			// Do we have children?
			if (Array.isArray(nodes[i]))
				create(nodes[i++], el);
			// Are we adding to a parent?
			if (parent)
				parent.appendChild(el);
			res.push(el);
		}
		return res;
	}

	// Handles both getting and setting of vendor prefix values
	function handlePrefix(el, name, set, value) {
		var prefixes = ['', 'webkit', 'moz', 'Moz', 'ms', 'o'],
			suffix = name[0].toUpperCase() + name.substring(1);
		for (var i = 0; i < 6; i++) {
			var prefix = prefixes[i],
				key = prefix ? prefix + suffix : name;
			if (key in el) {
				if (set) {
					el[key] = value;
				} else {
					return el[key];
				}
				break;
			}
		}
	}

	return /** @lends DomElement */{
		create: function(nodes, parent) {
			var isArray = Array.isArray(nodes),
				res = create(isArray ? nodes : arguments, isArray ? parent : null);
			return res.length == 1 ? res[0] : res;
		},

		find: function(selector, root) {
			return (root || document).querySelector(selector);
		},

		findAll: function(selector, root) {
			return (root || document).querySelectorAll(selector);
		},

		get: function(el, key) {
			return el
				? special.test(key)
					? key === 'value' || typeof el[key] !== 'string'
						? el[key]
						: true
					: key in translated
						? el[translated[key]]
						: el.getAttribute(key)
				: null;
		},

		set: function(el, key, value) {
			if (typeof key !== 'string') {
				for (var name in key)
					if (key.hasOwnProperty(name))
						this.set(el, name, key[name]);
			} else if (!el || value === undefined) {
				return el;
			} else if (special.test(key)) {
				el[key] = value;
			} else if (key in translated) {
				el[translated[key]] = value;
			} else if (key === 'style') {
				this.setStyle(el, value);
			} else if (key === 'events') {
				DomEvent.add(el, value);
			} else {
				el.setAttribute(key, value);
			}
			return el;
		},

		getStyles: function(el) {
			// If el is a document (nodeType == 9), use it directly
			var doc = el && el.nodeType !== 9 ? el.ownerDocument : el,
				view = doc && doc.defaultView;
			return view && view.getComputedStyle(el, '');
		},

		getStyle: function(el, key) {
			return el && el.style[key] || this.getStyles(el)[key] || null;
		},

		setStyle: function(el, key, value) {
			if (typeof key !== 'string') {
				for (var name in key)
					if (key.hasOwnProperty(name))
						this.setStyle(el, name, key[name]);
			} else {
				if (/^-?[\d\.]+$/.test(value) && !(key in unitless))
					value += 'px';
				el.style[key] = value;
			}
			return el;
		},

		hasClass: function(el, cls) {
			return new RegExp('\\s*' + cls + '\\s*').test(el.className);
		},

		addClass: function(el, cls) {
			el.className = (el.className + ' ' + cls).trim();
		},

		removeClass: function(el, cls) {
			el.className = el.className.replace(
				new RegExp('\\s*' + cls + '\\s*'), ' ').trim();
		},

		remove: function(el) {
			if (el.parentNode)
				el.parentNode.removeChild(el);
		},

		removeChildren: function(el) {
			while (el.firstChild)
				el.removeChild(el.firstChild);
		},

		getBounds: function(el, viewport) {
			var doc = el.ownerDocument,
				body = doc.body,
				html = doc.documentElement,
				rect;
			try {
				// On IE, for nodes that are not inside the DOM, this throws an
				// exception. Emulate the behavior of all other browsers, which
				// return a rectangle of 0 dimensions.
				rect = el.getBoundingClientRect();
			} catch (e) {
				rect = { left: 0, top: 0, width: 0, height: 0 };
			}
			var x = rect.left - (html.clientLeft || body.clientLeft || 0),
				y = rect.top - (html.clientTop  || body.clientTop  || 0);
			if (!viewport) {
				var view = doc.defaultView;
				x += view.pageXOffset || html.scrollLeft || body.scrollLeft;
				y += view.pageYOffset || html.scrollTop || body.scrollTop;
			}
			return new Rectangle(x, y, rect.width, rect.height);
		},

		getViewportBounds: function(el) {
			var doc = el.ownerDocument,
				view = doc.defaultView,
				html = doc.documentElement;
			return new Rectangle(0, 0,
				view.innerWidth || html.clientWidth,
				view.innerHeight || html.clientHeight
			);
		},

		getOffset: function(el, viewport) {
			return this.getBounds(el, viewport).getPoint();
		},

		getSize: function(el) {
			return this.getBounds(el, true).getSize();
		},

		/**
		 * Checks if element is invisibile (display: none, ...)
		 */
		isInvisible: function(el) {
			return this.getSize(el).equals(new Size(0, 0));
		},

		/**
		 * Checks if element is visibile in current viewport
		 */
		isInView: function(el) {
			// See if the viewport bounds intersect with the windows rectangle
			// which always starts at 0, 0
			return !this.isInvisible(el) && this.getViewportBounds(el).intersects(
					this.getBounds(el, true));
		},

		/**
		 * Gets the given property from the element, trying out all browser
		 * prefix variants.
		 */
		getPrefixed: function(el, name) {
			return handlePrefix(el, name);
		},

		setPrefixed: function(el, name, value) {
			if (typeof name === 'object') {
				for (var key in name)
					handlePrefix(el, key, true, name[key]);
			} else {
				handlePrefix(el, name, true, value);
			}
		}
	};
};
<|endoftext|>


<commit_before>JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({
  register: function() {
    this._super();
    if(!this.input) return;
    this.input.setAttribute('name',this.formname);
  },
  unregister: function() {
    this._super();
    if(!this.input) return;
    this.input.removeAttribute('name');
  },
  setValue: function(value,initial,from_template) {
    var self = this;

    if(this.template && !from_template) {
      return;
    }

    if(value === null) value = "";
    else if(typeof value === "object") value = JSON.stringify(value);
    else if(typeof value !== "string") value = ""+value;

    if(value === this.serialized) return;

    // Sanitize value before setting it
    var sanitized = this.sanitize(value);

    if(this.input.value === sanitized) {
      return;
    }

    this.input.value = sanitized;

    // If using SCEditor, update the WYSIWYG
    if(this.sceditor_instance) {
      this.sceditor_instance.val(sanitized);
    }
    else if(this.epiceditor) {
      this.epiceditor.importFile(null,sanitized);
    }
    else if(this.ace_editor) {
      this.ace_editor.setValue(sanitized);
    }

    var changed = from_template || this.getValue() !== value;

    this.refreshValue();

    if(initial) this.is_dirty = false;
    else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;

    if(this.adjust_height) this.adjust_height(this.input);

    // Bubble this setValue to parents if the value changed
    this.onChange(changed);
  },
  getNumColumns: function() {
    var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5);
    var num;

    if(this.input_type === 'textarea') num = 6;
    else if(['text','email'].indexOf(this.input_type) >= 0) num = 4;
    else num = 2;

    return Math.min(12,Math.max(min,num));
  },
  build: function() {
    var self = this, i;
    if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
    if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);

    this.format = this.schema.format;
    if(!this.format && this.schema.media && this.schema.media.type) {
      this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,'');
    }
    if(!this.format && this.options.default_format) {
      this.format = this.options.default_format;
    }
    if(this.options.format) {
      this.format = this.options.format;
    }

    // Specific format
    if(this.format) {
      // Text Area
      if(this.format === 'textarea') {
        this.input_type = 'textarea';
        this.input = this.theme.getTextareaInput();
      }
      // Range Input
      else if(this.format === 'range') {
        this.input_type = 'range';
        var min = this.schema.minimum || 0;
        var max = this.schema.maximum || Math.max(100,min+1);
        var step = 1;
        if(this.schema.multipleOf) {
          if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf;
          if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf;
          step = this.schema.multipleOf;
        }

        this.input = this.theme.getRangeInput(min,max,step);
      }
      // Source Code
      else if([
          'actionscript',
          'batchfile',
          'bbcode',
          'c',
          'c++',
          'cpp',
          'coffee',
          'csharp',
          'css',
          'dart',
          'django',
          'ejs',
          'erlang',
          'golang',
          'handlebars',
          'haskell',
          'haxe',
          'html',
          'ini',
          'jade',
          'java',
          'javascript',
          'json',
          'less',
          'lisp',
          'lua',
          'makefile',
          'markdown',
          'matlab',
          'mysql',
          'objectivec',
          'pascal',
          'perl',
          'pgsql',
          'php',
          'python',
          'r',
          'ruby',
          'sass',
          'scala',
          'scss',
          'smarty',
          'sql',
          'stylus',
          'svg',
          'twig',
          'vbscript',
          'xml',
          'yaml'
        ].indexOf(this.format) >= 0
      ) {
        this.input_type = this.format;
        this.source_code = true;

        this.input = this.theme.getTextareaInput();
      }
      // HTML5 Input type
      else {
        this.input_type = this.format;
        this.input = this.theme.getFormInputField(this.input_type);
      }
    }
    // Normal text input
    else {
      this.input_type = 'text';
      this.input = this.theme.getFormInputField(this.input_type);
    }

    // minLength, maxLength, and pattern
    if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength);
    if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern);
    else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}');

    if(this.options.compact) {
      this.container.className += ' compact';
    }
    else {
      if(this.options.input_width) this.input.style.width = this.options.input_width;
    }

    if(this.schema.readOnly || this.schema.readonly || this.schema.template) {
      this.always_disabled = true;
      this.input.disabled = true;
    }

    this.input
      .addEventListener('change',function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Don't allow changing if this field is a template
        if(self.schema.template) {
          this.value = self.value;
          return;
        }

        var val = this.value;

        // sanitize value
        var sanitized = self.sanitize(val);
        if(val !== sanitized) {
          this.value = sanitized;
        }

        self.is_dirty = true;

        self.refreshValue();
        self.onChange(true);
      });

    if(this.options.input_height) this.input.style.height = this.options.input_height;
    if(this.options.expand_height) {
      this.adjust_height = function(el) {
        if(!el) return;
        var i, ch=el.offsetHeight;
        // Input too short
        if(el.offsetHeight < el.scrollHeight) {
          i=0;
          while(el.offsetHeight < el.scrollHeight+3) {
            if(i>100) break;
            i++;
            ch++;
            el.style.height = ch+'px';
          }
        }
        else {
          i=0;
          while(el.offsetHeight >= el.scrollHeight+3) {
            if(i>100) break;
            i++;
            ch--;
            el.style.height = ch+'px';
          }
          el.style.height = (ch+1)+'px';
        }
      };

      this.input.addEventListener('keyup',function(e) {
        self.adjust_height(this);
      });
      this.input.addEventListener('change',function(e) {
        self.adjust_height(this);
      });
      this.adjust_height();
    }

    if(this.format) this.input.setAttribute('data-schemaformat',this.format);

    this.control = this.theme.getFormControl(this.label, this.input, this.description);
    this.container.appendChild(this.control);

    // Any special formatting that needs to happen after the input is added to the dom
    window.requestAnimationFrame(function() {
      // Skip in case the input is only a temporary editor,
      // otherwise, in the case of an ace_editor creation,
      // it will generate an error trying to append it to the missing parentNode
      if(self.input.parentNode) self.afterInputReady();
      if(self.adjust_height) self.adjust_height(self.input);
    });

    // Compile and store the template
    if(this.schema.template) {
      this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine);
      this.refreshValue();
    }
    else {
      this.refreshValue();
    }
  },
  enable: function() {
    if(!this.always_disabled) {
      this.input.disabled = false;
      // TODO: WYSIWYG and Markdown editors
    }
    this._super();
  },
  disable: function() {
    this.input.disabled = true;
    // TODO: WYSIWYG and Markdown editors
    this._super();
  },
  afterInputReady: function() {
    var self = this, options;

    // Code editor
    if(this.source_code) {
      // WYSIWYG html and bbcode editor
      if(this.options.wysiwyg &&
        ['html','bbcode'].indexOf(this.input_type) >= 0 &&
        window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor
      ) {
        options = $extend({},{
          plugins: self.input_type==='html'? 'xhtml' : 'bbcode',
          emoticonsEnabled: false,
          width: '100%',
          height: 300
        },JSONEditor.plugins.sceditor,self.options.sceditor_options||{});

        window.jQuery(self.input).sceditor(options);

        self.sceditor_instance = window.jQuery(self.input).sceditor('instance');

        self.sceditor_instance.blur(function() {
          // Get editor's value
          var val = window.jQuery("<div>"+self.sceditor_instance.val()+"</div>");
          // Remove sceditor spans/divs
          window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove();
          // Set the value and update
          self.input.value = val.html();
          self.value = self.input.value;
          self.is_dirty = true;
          self.onChange(true);
        });
      }
      // EpicEditor for markdown (if it's loaded)
      else if (this.input_type === 'markdown' && window.EpicEditor) {
        this.epiceditor_container = document.createElement('div');
        this.input.parentNode.insertBefore(this.epiceditor_container,this.input);
        this.input.style.display = 'none';

        options = $extend({},JSONEditor.plugins.epiceditor,{
          container: this.epiceditor_container,
          clientSideStorage: false
        });

        this.epiceditor = new window.EpicEditor(options).load();

        this.epiceditor.importFile(null,this.getValue());

        this.epiceditor.on('update',function() {
          var val = self.epiceditor.exportFile();
          self.input.value = val;
          self.value = val;
          self.is_dirty = true;
          self.onChange(true);
        });
      }
      // ACE editor for everything else
      else if(window.ace) {
        var mode = this.input_type;
        // aliases for c/cpp
        if(mode === 'cpp' || mode === 'c++' || mode === 'c') {
          mode = 'c_cpp';
        }

        this.ace_container = document.createElement('div');
        this.ace_container.style.width = '100%';
        this.ace_container.style.position = 'relative';
        this.ace_container.style.height = '400px';
        this.input.parentNode.insertBefore(this.ace_container,this.input);
        this.input.style.display = 'none';
        this.ace_editor = window.ace.edit(this.ace_container);

        this.ace_editor.setValue(this.getValue());

        // The theme
        if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme);
        // The mode
        mode = window.ace.require("ace/mode/"+mode);
        if(mode) this.ace_editor.getSession().setMode(new mode.Mode());

        // Listen for changes
        this.ace_editor.on('change',function() {
          var val = self.ace_editor.getValue();
          self.input.value = val;
          self.refreshValue();
          self.is_dirty = true;
          self.onChange(true);
        });
      }
    }

    self.theme.afterInputReady(self.input);
  },
  refreshValue: function() {
    this.value = this.input.value;
    if(typeof this.value !== "string") this.value = '';
    this.serialized = this.value;
  },
  destroy: function() {
    // If using SCEditor, destroy the editor instance
    if(this.sceditor_instance) {
      this.sceditor_instance.destroy();
    }
    else if(this.epiceditor) {
      this.epiceditor.unload();
    }
    else if(this.ace_editor) {
      this.ace_editor.destroy();
    }

    this.template = null;
    if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
    if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
    if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);

    this._super();
  },
  /**
   * This is overridden in derivative editors
   */
  sanitize: function(value) {
    return value;
  },
  /**
   * Re-calculates the value if needed
   */
  onWatchedFieldChange: function() {
    var self = this, vars, j;

    // If this editor needs to be rendered by a macro template
    if(this.template) {
      vars = this.getWatchedFieldValues();
      this.setValue(this.template(vars),false,true);
    }

    this._super();
  },
  showValidationErrors: function(errors) {
    var self = this;

    if(this.jsoneditor.options.show_errors === "always") {}
    else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return;

    this.previous_error_setting = this.jsoneditor.options.show_errors;

    var messages = [];
    $each(errors,function(i,error) {
      if(error.path === self.path) {
        messages.push(error.message);
      }
    });

    if(messages.length) {
      this.theme.addInputError(this.input, messages.join('. ')+'.');
    }
    else {
      this.theme.removeInputError(this.input);
    }
  }
});
<commit_msg>Avoid displaying string "undefined" when value is `undefined`<commit_after>JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({
  register: function() {
    this._super();
    if(!this.input) return;
    this.input.setAttribute('name',this.formname);
  },
  unregister: function() {
    this._super();
    if(!this.input) return;
    this.input.removeAttribute('name');
  },
  setValue: function(value,initial,from_template) {
    var self = this;

    if(this.template && !from_template) {
      return;
    }

    if(value === null || typeof value === 'undefined') value = "";
    else if(typeof value === "object") value = JSON.stringify(value);
    else if(typeof value !== "string") value = ""+value;

    if(value === this.serialized) return;

    // Sanitize value before setting it
    var sanitized = this.sanitize(value);

    if(this.input.value === sanitized) {
      return;
    }

    this.input.value = sanitized;

    // If using SCEditor, update the WYSIWYG
    if(this.sceditor_instance) {
      this.sceditor_instance.val(sanitized);
    }
    else if(this.epiceditor) {
      this.epiceditor.importFile(null,sanitized);
    }
    else if(this.ace_editor) {
      this.ace_editor.setValue(sanitized);
    }

    var changed = from_template || this.getValue() !== value;

    this.refreshValue();

    if(initial) this.is_dirty = false;
    else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;

    if(this.adjust_height) this.adjust_height(this.input);

    // Bubble this setValue to parents if the value changed
    this.onChange(changed);
  },
  getNumColumns: function() {
    var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5);
    var num;

    if(this.input_type === 'textarea') num = 6;
    else if(['text','email'].indexOf(this.input_type) >= 0) num = 4;
    else num = 2;

    return Math.min(12,Math.max(min,num));
  },
  build: function() {
    var self = this, i;
    if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
    if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);

    this.format = this.schema.format;
    if(!this.format && this.schema.media && this.schema.media.type) {
      this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,'');
    }
    if(!this.format && this.options.default_format) {
      this.format = this.options.default_format;
    }
    if(this.options.format) {
      this.format = this.options.format;
    }

    // Specific format
    if(this.format) {
      // Text Area
      if(this.format === 'textarea') {
        this.input_type = 'textarea';
        this.input = this.theme.getTextareaInput();
      }
      // Range Input
      else if(this.format === 'range') {
        this.input_type = 'range';
        var min = this.schema.minimum || 0;
        var max = this.schema.maximum || Math.max(100,min+1);
        var step = 1;
        if(this.schema.multipleOf) {
          if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf;
          if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf;
          step = this.schema.multipleOf;
        }

        this.input = this.theme.getRangeInput(min,max,step);
      }
      // Source Code
      else if([
          'actionscript',
          'batchfile',
          'bbcode',
          'c',
          'c++',
          'cpp',
          'coffee',
          'csharp',
          'css',
          'dart',
          'django',
          'ejs',
          'erlang',
          'golang',
          'handlebars',
          'haskell',
          'haxe',
          'html',
          'ini',
          'jade',
          'java',
          'javascript',
          'json',
          'less',
          'lisp',
          'lua',
          'makefile',
          'markdown',
          'matlab',
          'mysql',
          'objectivec',
          'pascal',
          'perl',
          'pgsql',
          'php',
          'python',
          'r',
          'ruby',
          'sass',
          'scala',
          'scss',
          'smarty',
          'sql',
          'stylus',
          'svg',
          'twig',
          'vbscript',
          'xml',
          'yaml'
        ].indexOf(this.format) >= 0
      ) {
        this.input_type = this.format;
        this.source_code = true;

        this.input = this.theme.getTextareaInput();
      }
      // HTML5 Input type
      else {
        this.input_type = this.format;
        this.input = this.theme.getFormInputField(this.input_type);
      }
    }
    // Normal text input
    else {
      this.input_type = 'text';
      this.input = this.theme.getFormInputField(this.input_type);
    }

    // minLength, maxLength, and pattern
    if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength);
    if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern);
    else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}');

    if(this.options.compact) {
      this.container.className += ' compact';
    }
    else {
      if(this.options.input_width) this.input.style.width = this.options.input_width;
    }

    if(this.schema.readOnly || this.schema.readonly || this.schema.template) {
      this.always_disabled = true;
      this.input.disabled = true;
    }

    this.input
      .addEventListener('change',function(e) {
        e.preventDefault();
        e.stopPropagation();

        // Don't allow changing if this field is a template
        if(self.schema.template) {
          this.value = self.value;
          return;
        }

        var val = this.value;

        // sanitize value
        var sanitized = self.sanitize(val);
        if(val !== sanitized) {
          this.value = sanitized;
        }

        self.is_dirty = true;

        self.refreshValue();
        self.onChange(true);
      });

    if(this.options.input_height) this.input.style.height = this.options.input_height;
    if(this.options.expand_height) {
      this.adjust_height = function(el) {
        if(!el) return;
        var i, ch=el.offsetHeight;
        // Input too short
        if(el.offsetHeight < el.scrollHeight) {
          i=0;
          while(el.offsetHeight < el.scrollHeight+3) {
            if(i>100) break;
            i++;
            ch++;
            el.style.height = ch+'px';
          }
        }
        else {
          i=0;
          while(el.offsetHeight >= el.scrollHeight+3) {
            if(i>100) break;
            i++;
            ch--;
            el.style.height = ch+'px';
          }
          el.style.height = (ch+1)+'px';
        }
      };

      this.input.addEventListener('keyup',function(e) {
        self.adjust_height(this);
      });
      this.input.addEventListener('change',function(e) {
        self.adjust_height(this);
      });
      this.adjust_height();
    }

    if(this.format) this.input.setAttribute('data-schemaformat',this.format);

    this.control = this.theme.getFormControl(this.label, this.input, this.description);
    this.container.appendChild(this.control);

    // Any special formatting that needs to happen after the input is added to the dom
    window.requestAnimationFrame(function() {
      // Skip in case the input is only a temporary editor,
      // otherwise, in the case of an ace_editor creation,
      // it will generate an error trying to append it to the missing parentNode
      if(self.input.parentNode) self.afterInputReady();
      if(self.adjust_height) self.adjust_height(self.input);
    });

    // Compile and store the template
    if(this.schema.template) {
      this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine);
      this.refreshValue();
    }
    else {
      this.refreshValue();
    }
  },
  enable: function() {
    if(!this.always_disabled) {
      this.input.disabled = false;
      // TODO: WYSIWYG and Markdown editors
    }
    this._super();
  },
  disable: function() {
    this.input.disabled = true;
    // TODO: WYSIWYG and Markdown editors
    this._super();
  },
  afterInputReady: function() {
    var self = this, options;

    // Code editor
    if(this.source_code) {
      // WYSIWYG html and bbcode editor
      if(this.options.wysiwyg &&
        ['html','bbcode'].indexOf(this.input_type) >= 0 &&
        window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor
      ) {
        options = $extend({},{
          plugins: self.input_type==='html'? 'xhtml' : 'bbcode',
          emoticonsEnabled: false,
          width: '100%',
          height: 300
        },JSONEditor.plugins.sceditor,self.options.sceditor_options||{});

        window.jQuery(self.input).sceditor(options);

        self.sceditor_instance = window.jQuery(self.input).sceditor('instance');

        self.sceditor_instance.blur(function() {
          // Get editor's value
          var val = window.jQuery("<div>"+self.sceditor_instance.val()+"</div>");
          // Remove sceditor spans/divs
          window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove();
          // Set the value and update
          self.input.value = val.html();
          self.value = self.input.value;
          self.is_dirty = true;
          self.onChange(true);
        });
      }
      // EpicEditor for markdown (if it's loaded)
      else if (this.input_type === 'markdown' && window.EpicEditor) {
        this.epiceditor_container = document.createElement('div');
        this.input.parentNode.insertBefore(this.epiceditor_container,this.input);
        this.input.style.display = 'none';

        options = $extend({},JSONEditor.plugins.epiceditor,{
          container: this.epiceditor_container,
          clientSideStorage: false
        });

        this.epiceditor = new window.EpicEditor(options).load();

        this.epiceditor.importFile(null,this.getValue());

        this.epiceditor.on('update',function() {
          var val = self.epiceditor.exportFile();
          self.input.value = val;
          self.value = val;
          self.is_dirty = true;
          self.onChange(true);
        });
      }
      // ACE editor for everything else
      else if(window.ace) {
        var mode = this.input_type;
        // aliases for c/cpp
        if(mode === 'cpp' || mode === 'c++' || mode === 'c') {
          mode = 'c_cpp';
        }

        this.ace_container = document.createElement('div');
        this.ace_container.style.width = '100%';
        this.ace_container.style.position = 'relative';
        this.ace_container.style.height = '400px';
        this.input.parentNode.insertBefore(this.ace_container,this.input);
        this.input.style.display = 'none';
        this.ace_editor = window.ace.edit(this.ace_container);

        this.ace_editor.setValue(this.getValue());

        // The theme
        if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme);
        // The mode
        mode = window.ace.require("ace/mode/"+mode);
        if(mode) this.ace_editor.getSession().setMode(new mode.Mode());

        // Listen for changes
        this.ace_editor.on('change',function() {
          var val = self.ace_editor.getValue();
          self.input.value = val;
          self.refreshValue();
          self.is_dirty = true;
          self.onChange(true);
        });
      }
    }

    self.theme.afterInputReady(self.input);
  },
  refreshValue: function() {
    this.value = this.input.value;
    if(typeof this.value !== "string") this.value = '';
    this.serialized = this.value;
  },
  destroy: function() {
    // If using SCEditor, destroy the editor instance
    if(this.sceditor_instance) {
      this.sceditor_instance.destroy();
    }
    else if(this.epiceditor) {
      this.epiceditor.unload();
    }
    else if(this.ace_editor) {
      this.ace_editor.destroy();
    }

    this.template = null;
    if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
    if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
    if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);

    this._super();
  },
  /**
   * This is overridden in derivative editors
   */
  sanitize: function(value) {
    return value;
  },
  /**
   * Re-calculates the value if needed
   */
  onWatchedFieldChange: function() {
    var self = this, vars, j;

    // If this editor needs to be rendered by a macro template
    if(this.template) {
      vars = this.getWatchedFieldValues();
      this.setValue(this.template(vars),false,true);
    }

    this._super();
  },
  showValidationErrors: function(errors) {
    var self = this;

    if(this.jsoneditor.options.show_errors === "always") {}
    else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return;

    this.previous_error_setting = this.jsoneditor.options.show_errors;

    var messages = [];
    $each(errors,function(i,error) {
      if(error.path === self.path) {
        messages.push(error.message);
      }
    });

    if(messages.length) {
      this.theme.addInputError(this.input, messages.join('. ')+'.');
    }
    else {
      this.theme.removeInputError(this.input);
    }
  }
});
<|endoftext|>


<commit_before>var path   = require('path')
var util   = require("util");
var Bot    = require("./lib/irc");

var moment = require('moment-timezone')
var boo    = require('boo')
var spice  = require('spice')
var Factoid = require('./lib/factoidserv')
var Twitter = require('twitter')

var profile = require('./nanoprofile')
var shared  = require('./shared')

function ensure_not_active(bot, cx) {
  if (!bot.current_ww.active)
    return cx.channel.send_reply(cx.intent || cx.sender, "Nenhuma WordWar está rolando no momento. Você pode enviar \"!ww [duração] [hh:mm]\" para pedir uma!")
  else
    return true
}

function string_to_time(hhmm) {
  if (hhmm.charAt(0) === ':')
    var start = moment().tz('America/Sao_Paulo').minutes(+hhmm.slice(1))
  else
    var start = moment.tz(moment.tz('America/Sao_Paulo').format('YYYY-MM-DD') + ' ' + hhmm, 'YYYY-MM-DD H:m', 'America/Sao_Paulo')
  if(!start.isValid())
    return false

  if(start.isBefore(new Date))
    start.add(1, 'hours')

  if(start.isBefore(new Date))
    start.add(1, 'days')

  return start
}

function logTwitterErrors(data) {
  if (!data.id_str)
    console.log('Error sending tweet: ' + data + '\n', data)
}

var twitterMessages = {
  finished: [
    "{:end}. Acabou galera. Hora de atacar o bolo e um pouco de café para esperar o próximo sprint!",
    "{:end}. Parou! Espero que sua novel tenha acumulado mais palavras, mesmo que a contagem de personagens tenha diminuído :P",
    "{:end}. Tempo! Muito bom galera, let's rock that word count :)",
    "{:end}. Eeeeee parou. It's dangerous to continue alone, grab some chocolate wrimo."
  ],

  asking: [
    "A próxima é de {:minutes} minutos, galera. Começando {:starting}.",
    "Wrimos, se preparem para uma sprint de {:minutes} minutos {:starting}.",
    "E aí, quem tá à fim de uma sprint de {:minutes} minutes começando {:starting}?"
  ],

  starting: [
    "Ataquem os teclados, wrimos! {:start}–{:end}. Vocês têm {:minutes} minutos, wrimos.",
    "Vai rolar uma sprint de {:minutes} minutos, de {:start} à {:end}. Preparar. Apontar. ESCREVER!",
    "{:start}–{:end}. É de {:minutes} minutes esse sprint, galera. Dêem o seu melhor!"
  ]
}

function choose(xs) {
  return xs[Math.floor(Math.random() * xs.length)] }

var WordWar = boo.Base.derive({
  init:
  function _init() {
    this.open   = false
    this.active = false
    this.timers = []
  }

, activate:
  function _activate(sender, minutes, start_at) {
    this.participants = [sender]
    this.starter      = sender
    this.open         = true
    this.active       = true
    this.time         = minutes
    this.start_at     = start_at
  }

, stop:
  function _stop() {
    this.open   = false
    this.active = false
    this.timers.forEach(clearTimeout)
    this.timers = []
  }

, join:
  function _add(name) {
    this.participants.push(name)
  }

, part:
  function _part(name) {
    this.participants = this.participants.filter(function(a){ return a !== name })
  }

, is_participating:
  function _is_participating(name) {
    return this.participants.some(function(a){ return a === name })
  }

, notify:
  function _notify() {
    var start_at      = this.start_at
    var start_display = start_at? this.start_at.format('HH:mm') : ''
    return spice('{:sender} está pedindo uma WordWar de {:minutes} minutos{:start}!'
                +' Envie "!join" para participar. Envie "!start" para começar a contagem{:now}.'
                , { start   : start_at? ' começando em ' + start_display : ' '
                  , now     : start_at? ' agora' : ''
                  , sender  : this.starter
                  , minutes : this.time })
  }

, notify_start:
  function _notify_start() {
    this.open       = false
    this.start_time = moment(new Date).tz("America/Sao_Paulo")
    this.end_time   = moment(new Date).add('minutes', this.time).tz("America/Sao_Paulo")
    return spice('WordWar de {:start} à {:end} começando. Vai nessa, {:participants}!'
                , { participants: this.participants.join(', ')
                  , start:        this.start_time.format('HH:mm')
                  , end:          this.end_time.format('HH:mm')
                  })
  }

, notify_end:
  function _notify_end() {
    return spice('WordWar acabou, {:participants}.'
                , { participants: this.participants.join(', ') })
  }

, notify_status:
  function _notify_status() {
    return spice('WordWar começou em {:start} e vai terminar em {:end}'
                +' ({:minutes} minutos restantes).'
                +' {:participants} nanowriters estão participando.'
                , { participants: this.participants.length
                  , start:        this.start_time.format('HH:mm')
                  , end:          this.end_time.format('HH:mm')
                  , minutes:      this.end_time.diff(new Date, 'minutes')
                  })
  }
})

util.inherits(NanoBot, Bot)
function NanoBot(profile) {
  Bot.call(this, profile)

  this.factoids = new Factoid(path.join(__dirname, "data/nanobot-factoids.json"))
  this.twitter  = new Twitter(profile[0].twitter)

  this.set_log_level(this.LOG_ALL)
  this.set_trigger("!")
};

NanoBot.prototype.init = function() {
  Bot.prototype.init.call(this)

  this.current_ww = WordWar.make()

  this.register_command("ww", this.ww, {
    allow_intentions: false,
    help: 'Pede uma nova WordWar no canal. Você pode agendar a WordWar para ter uma duração determinada, e começar em um horário específico (horário de São Paulo). O padrão é 20 minutos com início manual (veja "!start"). Comando: !ww [minutos] [hh:mm] - ex: !ww 30 12:34'
  })
  this.register_command("stop", this.stop_ww, {
    allow_intentions: false,
    help: 'Cancela uma WordWar em andamento, ou uma WordWar agendada mas não iniciada. Comando: !stop'
  })
  this.register_command("join", this.join_ww, {
    allow_intentions: false,
    help: 'Participa de uma WordWar em andamento. Pessoas que agendaram a WordWar já são consideradas como participantes. Para sair de uma WordWar, envie "!part". Comando: !join'
  })
  this.register_command("start", this.start_ww, {
    allow_intentions: false,
    help: 'Inicia uma WordWar agendada. Pessoas que estão participando serão notificadas, e o bot notificará todos os participantes quando a WordWar terminar. Comando: !start'
  })
  this.register_command("part", this.part_ww, {
    allow_intentions: false,
    help: 'Deixa de participar de uma WordWar. Comando: !part (aliases: !quit, !leave)'
  })
  this.register_command("quit", "part")
  this.register_command("leave", "part")
  this.register_command("status", this.status_ww, {
    help: 'Te diz se existe uma WordWar em andamento, ou se uma WordWar foi agendada. Você pode adicionar "@ <usuário>" para direcionar sua mensagem para alguém no canal. Comando: !status'
  })

  this.register_command("learn", shared.learn, {
    allow_intentions: false,
    help: 'Ensina novas coisas para o Nanobot. Comando: !learn [coisa] = [o que o nanobot deve saber sobre essa coisa]'
  })

  this.register_command("forget", shared.forget, {
    allow_intentions: false,
    help: 'Pede que o Nanobot esqueça sobre algo que lhe ensinaram anteriormente. Comando: !forget [coisa]'
  })

  this.register_command("find", shared.find, {
    help: 'Pergunta ao Nanobot se ele sabe algo sobre alguma coisa. Comando: !find [coisa]'
  })

  this.register_command("help", this.help)
  this.register_command("commands", this.commands)

  this.on('command_not_found', this.unrecognized)

};

NanoBot.prototype.ww = function(cx, text) {
  var args = text.split(/\s+/)
  var minutes = Number(args[0])
  var start_at = false;
  if (this.current_ww.active)
    return cx.channel.send_reply(cx.sender, "Já existe uma WordWar em andamento. " + this.current_ww.notify_status())
  if (isNaN(minutes) || minutes < 0)
    return cx.channel.send_reply(cx.sender,this.get_command_help("ww"))

  if(args.length >= 2) {
    start_at = string_to_time(args[1])
    if (start_at === false)
      return cx.channel.send_reply(cx.sender, this.get_command_help("ww"))
    else
      this.current_ww.timers.push(setTimeout(function() {
        this.start_ww(cx, '')
      }.bind(this), start_at.diff(new Date)))
  }

  this.current_ww.activate(cx.sender, minutes || 20, start_at)
  cx.channel.send(this.current_ww.notify(start_at))

  this.twitter.updateStatus(
    spice(choose(twitterMessages.asking)
         , { minutes:  minutes || 20
           , starting: start_at? 'em ' + start_at.format('HH:mm') : 'em breve' })
  , logTwitterErrors
  )
};

NanoBot.prototype.start_ww = function(cx, text) {
  if (!ensure_not_active(this, cx)) return
  if (!this.current_ww.open)
    return cx.channel.send_reply(cx.sender, "Já existe uma WordWar em andamento. " + this.current_ww.notify_status())

  this.current_ww.open = false
  this.current_ww.timers.push(setTimeout(function() {
    setImmediate(function() {
      this.twitter.updateStatus(
        spice(choose(twitterMessages.finished)
             , { minutes: this.current_ww.time
               , start:   this.current_ww.start_time.format('HH:mm')
               , end:     this.current_ww.end_time.format('HH:mm')})
      , logTwitterErrors)
    }.bind(this))

    cx.channel.send(this.current_ww.notify_end())
    this.current_ww.stop()
  }.bind(this), this.current_ww.time * 60 * 1000))
  cx.channel.send(this.current_ww.notify_start())

  this.twitter.updateStatus(
    spice(choose(twitterMessages.starting)
         , { minutes: this.current_ww.time
           , start:   this.current_ww.start_time.format('HH:mm')
           , end:     this.current_ww.end_time.format('HH:mm') })
  , logTwitterErrors
  )
}

NanoBot.prototype.stop_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return

  this.current_ww.stop()
  cx.channel.send_reply(cx.sender, "WordWar terminou.")
}

NanoBot.prototype.join_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return
  if (this.current_ww.is_participating(cx.sender))
    return cx.channel.send_reply(cx.sender, "Você já está participando.")

  this.current_ww.join(cx.sender)
  cx.channel.send(cx.sender + " agora está participando da WordWar.")
}

NanoBot.prototype.part_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return
  if (this.current_ww.is_participating(cx.sender)) {
    this.current_ww.part(cx.sender)
    cx.channel.send(cx.sender + " deixou de participar da WordWar")
  }
}

NanoBot.prototype.status_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return
  if (this.current_ww.open) {
    cx.channel.send_reply(cx.intent, 'Tem uma WordWar de ' + this.current_ww.time + ' minutos agendada, mas ainda não iniciada. '
    + (this.current_ww.start_at ? 'Ela vai começar às ' + this.current_ww.start_at.format('HH:mm') + '. ' : '')
    + (this.current_ww.is_participating(cx.intent) ? 'Você já está participando.' : 'Você pode enviar "!join" para participar.'))
  }
  else
    cx.channel.send_reply(cx.intent, this.current_ww.notify_status() + (this.current_ww.is_participating(cx.intent) ? ' Você já está participando.' : ' Você pode enviar "!join" para participar.'))
}

NanoBot.prototype.help = function(cx, text) {
  try {
    if (!text) return this.unrecognized(cx, text)
    cx.channel.send_reply(cx.intent, this.get_command_help(text))
  } catch (e) {
    cx.channel.send_reply(cx.sender, e)
  }
}

NanoBot.prototype.commands = function(cx, text) {
  var trigger  = this.__trigger
  var commands = this.get_commands().map(function(a){ return trigger + a })
  cx.channel.send_reply(cx.intent, "Comandos válidos: " + commands.join(', '))
}

NanoBot.prototype.unrecognized = function(cx, text) {
  if (cx.priv)
    return shared.find.call(this, cx, text)

  try {
    cx.channel.send_reply(cx.intent, this.factoids.find(text, true))
  } catch(e) {
    cx.channel.send_reply(cx.sender, "Ouch! Não sei o que é " + text)
  }
}

;(new NanoBot(profile)).init()
<commit_msg>Fixes #10 and Fixes #9<commit_after>var path   = require('path')
var util   = require("util");
var Bot    = require("./lib/irc");

var moment = require('moment-timezone')
var boo    = require('boo')
var spice  = require('spice')
var Factoid = require('./lib/factoidserv')
var Twitter = require('twitter')

var profile = require('./nanoprofile')
var shared  = require('./shared')

function ensure_not_active(bot, cx) {
  if (!bot.current_ww.active)
    return cx.channel.send_reply(cx.intent || cx.sender, "Nenhuma WordWar está rolando no momento. Você pode enviar \"!ww [duração] [hh:mm]\" para pedir uma!")
  else
    return true
}

function string_to_time(hhmm) {
  if (hhmm.charAt(0) === ':')
    var start = moment().tz('America/Sao_Paulo').minutes(+hhmm.slice(1))
  else
    var start = moment.tz(moment.tz('America/Sao_Paulo').format('YYYY-MM-DD') + ' ' + hhmm + ':00', 'YYYY-MM-DD H:m:ss', 'America/Sao_Paulo')
  if(!start.isValid())
    return false

  if(start.isBefore(new Date))
    start.add(1, 'hours')

  if(start.isBefore(new Date))
    start.subtract(1, 'hours').add(1, 'days')

  return start
}

function logTwitterErrors(data) {
  if (!data.id_str)
    console.log('Error sending tweet: ' + data + '\n', data)
}

var twitterMessages = {
  finished: [
    "{:end}. Acabou galera. Hora de atacar o bolo e um pouco de café para esperar o próximo sprint!",
    "{:end}. Parou! Espero que sua novel tenha acumulado mais palavras, mesmo que a contagem de personagens tenha diminuído :P",
    "{:end}. Tempo! Muito bom galera, let's rock that word count :)",
    "{:end}. Eeeeee parou. It's dangerous to continue alone, grab some chocolate wrimo."
  ],

  asking: [
    "A próxima é de {:minutes} minutos, galera. Começando {:starting}.",
    "Wrimos, se preparem para uma sprint de {:minutes} minutos {:starting}.",
    "E aí, quem tá à fim de uma sprint de {:minutes} minutes começando {:starting}?"
  ],

  starting: [
    "Ataquem os teclados, wrimos! {:start}–{:end}. Vocês têm {:minutes} minutos, wrimos.",
    "Vai rolar uma sprint de {:minutes} minutos, de {:start} à {:end}. Preparar. Apontar. ESCREVER!",
    "{:start}–{:end}. É de {:minutes} minutes esse sprint, galera. Dêem o seu melhor!"
  ]
}

function choose(xs) {
  return xs[Math.floor(Math.random() * xs.length)] }

var WordWar = boo.Base.derive({
  init:
  function _init() {
    this.open   = false
    this.active = false
    this.timers = []
  }

, activate:
  function _activate(sender, minutes, start_at) {
    this.participants = [sender]
    this.starter      = sender
    this.open         = true
    this.active       = true
    this.time         = minutes
    this.start_at     = start_at
  }

, stop:
  function _stop() {
    this.open   = false
    this.active = false
    this.timers.forEach(clearTimeout)
    this.timers = []
  }

, join:
  function _add(name) {
    this.participants.push(name)
  }

, part:
  function _part(name) {
    this.participants = this.participants.filter(function(a){ return a !== name })
  }

, is_participating:
  function _is_participating(name) {
    return this.participants.some(function(a){ return a === name })
  }

, notify:
  function _notify() {
    var start_at      = this.start_at
    var start_display = start_at? this.start_at.format('HH:mm') : ''
    return spice('{:sender} está pedindo uma WordWar de {:minutes} minutos{:start}!'
                +' Envie "!join" para participar. Envie "!start" para começar a contagem{:now}.'
                , { start   : start_at? ' começando em ' + start_display : ' '
                  , now     : start_at? ' agora' : ''
                  , sender  : this.starter
                  , minutes : this.time })
  }

, notify_start:
  function _notify_start() {
    this.open       = false
    this.start_time = moment(new Date).tz("America/Sao_Paulo")
    this.end_time   = moment(new Date).add('minutes', this.time).tz("America/Sao_Paulo")
    return spice('WordWar de {:start} à {:end} começando. Vai nessa, {:participants}!'
                , { participants: this.participants.join(', ')
                  , start:        this.start_time.format('HH:mm')
                  , end:          this.end_time.format('HH:mm')
                  })
  }

, notify_end:
  function _notify_end() {
    return spice('WordWar acabou, {:participants}.'
                , { participants: this.participants.join(', ') })
  }

, notify_status:
  function _notify_status() {
    return spice('WordWar começou em {:start} e vai terminar em {:end}'
                +' ({:minutes} minutos restantes).'
                +' {:participants} nanowriters estão participando.'
                , { participants: this.participants.length
                  , start:        this.start_time.format('HH:mm')
                  , end:          this.end_time.format('HH:mm')
                  , minutes:      this.end_time.diff(new Date, 'minutes')
                  })
  }
})

util.inherits(NanoBot, Bot)
function NanoBot(profile) {
  Bot.call(this, profile)

  this.factoids = new Factoid(path.join(__dirname, "data/nanobot-factoids.json"))
  this.twitter  = new Twitter(profile[0].twitter)

  this.set_log_level(this.LOG_ALL)
  this.set_trigger("!")
};

NanoBot.prototype.init = function() {
  Bot.prototype.init.call(this)

  this.current_ww = WordWar.make()

  this.register_command("ww", this.ww, {
    allow_intentions: false,
    help: 'Pede uma nova WordWar no canal. Você pode agendar a WordWar para ter uma duração determinada, e começar em um horário específico (horário de São Paulo). O padrão é 20 minutos com início manual (veja "!start"). Comando: !ww [minutos] [hh:mm] - ex: !ww 30 12:34'
  })
  this.register_command("stop", this.stop_ww, {
    allow_intentions: false,
    help: 'Cancela uma WordWar em andamento, ou uma WordWar agendada mas não iniciada. Comando: !stop'
  })
  this.register_command("join", this.join_ww, {
    allow_intentions: false,
    help: 'Participa de uma WordWar em andamento. Pessoas que agendaram a WordWar já são consideradas como participantes. Para sair de uma WordWar, envie "!part". Comando: !join'
  })
  this.register_command("start", this.start_ww, {
    allow_intentions: false,
    help: 'Inicia uma WordWar agendada. Pessoas que estão participando serão notificadas, e o bot notificará todos os participantes quando a WordWar terminar. Comando: !start'
  })
  this.register_command("part", this.part_ww, {
    allow_intentions: false,
    help: 'Deixa de participar de uma WordWar. Comando: !part (aliases: !quit, !leave)'
  })
  this.register_command("quit", "part")
  this.register_command("leave", "part")
  this.register_command("status", this.status_ww, {
    help: 'Te diz se existe uma WordWar em andamento, ou se uma WordWar foi agendada. Você pode adicionar "@ <usuário>" para direcionar sua mensagem para alguém no canal. Comando: !status'
  })

  this.register_command("learn", shared.learn, {
    allow_intentions: false,
    help: 'Ensina novas coisas para o Nanobot. Comando: !learn [coisa] = [o que o nanobot deve saber sobre essa coisa]'
  })

  this.register_command("forget", shared.forget, {
    allow_intentions: false,
    help: 'Pede que o Nanobot esqueça sobre algo que lhe ensinaram anteriormente. Comando: !forget [coisa]'
  })

  this.register_command("find", shared.find, {
    help: 'Pergunta ao Nanobot se ele sabe algo sobre alguma coisa. Comando: !find [coisa]'
  })

  this.register_command("help", this.help)
  this.register_command("commands", this.commands)

  this.on('command_not_found', this.unrecognized)

};

NanoBot.prototype.ww = function(cx, text) {
  var args = text.split(/\s+/)
  var minutes = Number(args[0])
  var start_at = false;
  if (this.current_ww.active)
    return cx.channel.send_reply(cx.sender, "Já existe uma WordWar em andamento. " + this.current_ww.notify_status())
  if (isNaN(minutes) || minutes < 0)
    return cx.channel.send_reply(cx.sender,this.get_command_help("ww"))

  if(args.length >= 2) {
    start_at = string_to_time(args[1])
    if (start_at === false)
      return cx.channel.send_reply(cx.sender, this.get_command_help("ww"))
    else {
      this.current_ww.timers.push(setTimeout(function() {
        this.start_ww(cx, '')
      }.bind(this), start_at.diff(new Date)))
    }
  }

  this.current_ww.activate(cx.sender, minutes || 20, start_at)
  cx.channel.send(this.current_ww.notify(start_at))

  this.twitter.updateStatus(
    spice(choose(twitterMessages.asking)
         , { minutes:  minutes || 20
           , starting: start_at? 'em ' + start_at.format('HH:mm') : 'em breve' })
  , logTwitterErrors
  )
};

NanoBot.prototype.start_ww = function(cx, text) {
  if (!ensure_not_active(this, cx)) return
  if (!this.current_ww.open)
    return cx.channel.send_reply(cx.sender, "Já existe uma WordWar em andamento. " + this.current_ww.notify_status())

  this.current_ww.open = false
  this.current_ww.timers.push(setTimeout(function() {
    setImmediate(function() {
      this.twitter.updateStatus(
        spice(choose(twitterMessages.finished)
             , { minutes: this.current_ww.time
               , start:   this.current_ww.start_time.format('HH:mm')
               , end:     this.current_ww.end_time.format('HH:mm')})
      , logTwitterErrors)
    }.bind(this))

    cx.channel.send(this.current_ww.notify_end())
    this.current_ww.stop()
  }.bind(this), this.current_ww.time * 60 * 1000))
  cx.channel.send(this.current_ww.notify_start())

  this.twitter.updateStatus(
    spice(choose(twitterMessages.starting)
         , { minutes: this.current_ww.time
           , start:   this.current_ww.start_time.format('HH:mm')
           , end:     this.current_ww.end_time.format('HH:mm') })
  , logTwitterErrors
  )
}

NanoBot.prototype.stop_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return

  this.current_ww.stop()
  cx.channel.send_reply(cx.sender, "WordWar terminou.")
}

NanoBot.prototype.join_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return
  if (this.current_ww.is_participating(cx.sender))
    return cx.channel.send_reply(cx.sender, "Você já está participando.")

  this.current_ww.join(cx.sender)
  cx.channel.send(cx.sender + " agora está participando da WordWar.")
}

NanoBot.prototype.part_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return
  if (this.current_ww.is_participating(cx.sender)) {
    this.current_ww.part(cx.sender)
    cx.channel.send(cx.sender + " deixou de participar da WordWar")
  }
}

NanoBot.prototype.status_ww = function(cx, text) {
  if (!ensure_not_active(this, cx))  return
  if (this.current_ww.open) {
    cx.channel.send_reply(cx.intent, 'Tem uma WordWar de ' + this.current_ww.time + ' minutos agendada, mas ainda não iniciada. '
    + (this.current_ww.start_at ? 'Ela vai começar às ' + this.current_ww.start_at.format('HH:mm') + '. ' : '')
    + (this.current_ww.is_participating(cx.intent) ? 'Você já está participando.' : 'Você pode enviar "!join" para participar.'))
  }
  else
    cx.channel.send_reply(cx.intent, this.current_ww.notify_status() + (this.current_ww.is_participating(cx.intent) ? ' Você já está participando.' : ' Você pode enviar "!join" para participar.'))
}

NanoBot.prototype.help = function(cx, text) {
  try {
    if (!text) return this.unrecognized(cx, text)
    cx.channel.send_reply(cx.intent, this.get_command_help(text))
  } catch (e) {
    cx.channel.send_reply(cx.sender, e)
  }
}

NanoBot.prototype.commands = function(cx, text) {
  var trigger  = this.__trigger
  var commands = this.get_commands().map(function(a){ return trigger + a })
  cx.channel.send_reply(cx.intent, "Comandos válidos: " + commands.join(', '))
}

NanoBot.prototype.unrecognized = function(cx, text) {
  if (cx.priv)
    return shared.find.call(this, cx, text)

  try {
    cx.channel.send_reply(cx.intent, this.factoids.find(text, true))
  } catch(e) {
    cx.channel.send_reply(cx.sender, "Ouch! Não sei o que é " + text)
  }
}

;(new NanoBot(profile)).init()
<|endoftext|>


<commit_before>/* eslint import/no-extraneous-dependencies: 0 */

'use strict'

const path = require('path')
const { name } = require('../../package.json')
const { app, Menu, Tray, globalShortcut, clipboard, BrowserWindow } = require('electron')
const settings = require('electron-settings')
const Positioner = require('electron-positioner')
const log = require('electron-log')
const stripIndent = require('strip-indent')
const isPlatform = require('./isPlatform')
const codeHighlight = require('./codeHighlight')
const { DEFAULT_SETTINGS } = require('./defaults')

const width = 800
const height = 600

const isDev = require('electron-is-dev')
require('electron-debug')({
  showDevTools: 'undocked'
})

// Prevent garbage collection
// Otherwise the tray icon would randomly hide after some time
let tray = null
// Create a reference to be able to destroy it
let preferencesWindow = null

// Hide dock icon before the app starts
if (isPlatform('macOS')) {
  app.dock.hide()
}

function registerShortcut(newShortcut, oldShortcut, callback) {
  // Unregister old shortcut
  if (oldShortcut) {
    globalShortcut.unregister(oldShortcut)
  }

  //  Register new shortcut
  const isRegistered = globalShortcut.register(newShortcut, callback)

  if (!isRegistered) {
    log.error('Shortcut registration failed')
  }
}

app.on('ready', () => {
  preferencesWindow = new BrowserWindow({
    width,
    height,
    frame: true,
    maximizable: false,
    minimizable: false,
    fullscreenable: false,
    titleBarStyle: 'hidden-inset',
    show: true
  })
  const positioner = new Positioner(preferencesWindow)

  const startUrl = isDev ? 'http://localhost:3000' : `file://${__dirname}/build/index.html`

  preferencesWindow.loadURL(startUrl)
  preferencesWindow.webContents.on('dom-ready', () => {
    preferencesWindow.webContents.executeJavaScript('window.require')
  })

  // Prevent closing the application when window is closed
  preferencesWindow.on('close', event => {
    event.preventDefault()
    // Still close the window, though
    preferencesWindow.hide()
  })

  const mainMenu = Menu.buildFromTemplate([
    {
      role: 'about'
    },
    {
      accelerator: 'Cmd+,',
      label: 'Preferences...',
      type: 'normal',
      click: () => {
        positioner.move('center')
        preferencesWindow.show()
      }
    },
    {
      type: 'separator'
    },
    {
      label: `Quit ${name}`,
      role: 'quit',
      type: 'normal'
    }
  ])
  tray = new Tray(path.join(__dirname, '..', '..', 'public', 'iconTemplate@2x.png'))
  tray.setContextMenu(mainMenu)

  // Register a shortcut listener.
  const onShortcutPressed = () => {
    const codeSnippet = stripIndent(clipboard.readText())
    preferencesWindow.webContents.send('global-shortcut-pressed', { codeSnippet })
    codeHighlight(codeSnippet, settings)
  }
  const shortcut = settings.get('shortcut', DEFAULT_SETTINGS.shortcut)
  registerShortcut(shortcut, null, onShortcutPressed)
  settings.watch('shortcut', (newVal, oldVal) =>
    registerShortcut(newVal, oldVal, onShortcutPressed)
  )
})

app.on('will-quit', () => {
  // Destroy window reference
  preferencesWindow.destroy()
  // De-reference object
  preferencesWindow = null
  // Unregister all shortcuts.
  globalShortcut.unregisterAll()
})
<commit_msg>Do not show preferences window on start<commit_after>/* eslint import/no-extraneous-dependencies: 0 */

'use strict'

const path = require('path')
const { name } = require('../../package.json')
const { app, Menu, Tray, globalShortcut, clipboard, BrowserWindow } = require('electron')
const settings = require('electron-settings')
const Positioner = require('electron-positioner')
const log = require('electron-log')
const stripIndent = require('strip-indent')
const isPlatform = require('./isPlatform')
const codeHighlight = require('./codeHighlight')
const { DEFAULT_SETTINGS } = require('./defaults')

const width = 800
const height = 600

const isDev = require('electron-is-dev')
require('electron-debug')({
  showDevTools: 'undocked'
})

// Prevent garbage collection
// Otherwise the tray icon would randomly hide after some time
let tray = null
// Create a reference to be able to destroy it
let preferencesWindow = null

// Hide dock icon before the app starts
if (isPlatform('macOS')) {
  app.dock.hide()
}

function registerShortcut(newShortcut, oldShortcut, callback) {
  // Unregister old shortcut
  if (oldShortcut) {
    globalShortcut.unregister(oldShortcut)
  }

  //  Register new shortcut
  const isRegistered = globalShortcut.register(newShortcut, callback)

  if (!isRegistered) {
    log.error('Shortcut registration failed')
  }
}

app.on('ready', () => {
  preferencesWindow = new BrowserWindow({
    width,
    height,
    frame: true,
    maximizable: false,
    minimizable: false,
    fullscreenable: false,
    titleBarStyle: 'hidden-inset',
    show: false
  })
  const positioner = new Positioner(preferencesWindow)

  const startUrl = isDev ? 'http://localhost:3000' : `file://${__dirname}/build/index.html`

  preferencesWindow.loadURL(startUrl)
  preferencesWindow.webContents.on('dom-ready', () => {
    preferencesWindow.webContents.executeJavaScript('window.require')
  })

  // Prevent closing the application when window is closed
  preferencesWindow.on('close', event => {
    event.preventDefault()
    // Still close the window, though
    preferencesWindow.hide()
  })

  const mainMenu = Menu.buildFromTemplate([
    {
      role: 'about'
    },
    {
      accelerator: 'Cmd+,',
      label: 'Preferences...',
      type: 'normal',
      click: () => {
        positioner.move('center')
        preferencesWindow.show()
      }
    },
    {
      type: 'separator'
    },
    {
      label: `Quit ${name}`,
      role: 'quit',
      type: 'normal'
    }
  ])
  tray = new Tray(path.join(__dirname, '..', '..', 'public', 'iconTemplate@2x.png'))
  tray.setContextMenu(mainMenu)

  // Register a shortcut listener.
  const onShortcutPressed = () => {
    const codeSnippet = stripIndent(clipboard.readText())
    preferencesWindow.webContents.send('global-shortcut-pressed', { codeSnippet })
    codeHighlight(codeSnippet, settings)
  }
  const shortcut = settings.get('shortcut', DEFAULT_SETTINGS.shortcut)
  registerShortcut(shortcut, null, onShortcutPressed)
  settings.watch('shortcut', (newVal, oldVal) =>
    registerShortcut(newVal, oldVal, onShortcutPressed)
  )
})

app.on('will-quit', () => {
  // Destroy window reference
  preferencesWindow.destroy()
  // De-reference object
  preferencesWindow = null
  // Unregister all shortcuts.
  globalShortcut.unregisterAll()
})
<|endoftext|>


<commit_before>import { Entity } from './entity.js'
import { invoke } from './utilities.js'

/**
 * @ignore
 * Returns index of smallest element
 */
const minIndexReducer = (minIndex, value, index, values) =>
  value < values[minIndex] ? index : minIndex

/** @ignore */
export class EntityStorage {
  constructor(world) {
    /** @ignore */
    this.world = world
    /** @ignore */
    this.componentClasses = {}
    /** @ignore */
    this.nextEntityId = 1

    /**
     * Maps entity IDs to entities
     * @ignore
     */
    this.entities = new Map()

    /**
     * Maps component keys to entities
     * @ignore
     */
    this.index = new Map()
  }

  clear() {
    // Call onRemove on all components of all entities
    for (const [, entity] of this.entities) {
      for (let componentName in entity.data) {
        // Get component, and call onRemove if it exists as a function
        let component = entity.data[componentName]
        invoke(component, 'onRemove')
      }
    }

    // Clear entities
    this.entities.clear()
    this.index.clear()
  }

  registerComponent(name, componentClass) {
    // Only allow functions and classes to be components
    if (typeof componentClass !== 'function') {
      throw new Error('Component is not a valid function or class.')
    }
    this.componentClasses[name] = componentClass
  }

  // Creates a new entity attached to the world
  createEntity() {
    const entityId = this.nextEntityId++
    const entity = new Entity(this.world, entityId)
    this.entities.set(entityId, entity)
    return entity
  }

  // Forwards query args from world
  each(...args) {
    return this.queryIndex(this.queryArgs(...args))
  }

  // Returns an existing or new index
  accessIndex(component) {
    return (
      this.index.get(component) ||
      this.index.set(component, new Map()).get(component)
    )
  }

  // Add component with an entity to the index
  addToIndex(entity, compName) {
    this.accessIndex(compName).set(entity.id, entity)
  }

  // Remove component from the index for an entity
  removeFromIndex(entity, compName) {
    this.accessIndex(compName).delete(entity.id)
  }

  queryArgs(...args) {
    // Gather component names and a callback (if any) from args
    const result = {
      componentNames: [],
      callback: null,
    }
    for (const arg of args) {
      if (typeof arg === 'string') {
        result.componentNames.push(arg)
      } else if (typeof arg === 'function') {
        result.callback = arg
      } else if (Array.isArray(arg)) {
        // Add 1-level deep arrays of strings as separate component names
        for (const name of arg) {
          result.componentNames.push(name)
        }
      } else {
        throw new Error(
          `Unknown argument ${arg} with type ${typeof arg} passed to world.each().`
        )
      }
    }
    return result
  }

  // Uses an existing index or builds a new index, to get entities with the specified components
  // If callback is defined, it will be called for each entity with component data, and returns undefined
  // If callback is not defined, an array of entities will be returned
  queryIndex({ componentNames, callback }) {
    // Return all entities (array if no callback)
    if (componentNames.length === 0) {
      const iter = this.entities.values()
      if (!callback) {
        return [...iter]
      }
      for (const entity of iter) {
        if (callback(entity.data, entity) === false) {
          break
        }
      }
      return
    }

    // Get the index name with the least number of entities
    const minCompIndex = componentNames
      .map(name => this.accessIndex(name).size)
      .reduce(minIndexReducer, 0)
    const minComp = componentNames[minCompIndex]
    const iter = this.index.get(minComp).values()

    // Return array of matched entities
    if (!callback) {
      const results = []
      for (const entity of iter) {
        const { data } = entity
        if (componentNames.every(name => name in data)) {
          results.push(entity)
        }
      }
      return results
    }

    // Invoke callback for each matched entity
    for (const entity of iter) {
      const { data } = entity
      if (
        componentNames.every(name => name in data) &&
        callback(data, entity) === false
      ) {
        return
      }
    }
  }
}
<commit_msg>Sort component names for queries by index size This improves performance with short-circuiting in the entity loops<commit_after>import { Entity } from './entity.js'
import { invoke } from './utilities.js'

/** @ignore */
export class EntityStorage {
  constructor(world) {
    /** @ignore */
    this.world = world
    /** @ignore */
    this.componentClasses = {}
    /** @ignore */
    this.nextEntityId = 1

    /**
     * Maps entity IDs to entities
     * @ignore
     */
    this.entities = new Map()

    /**
     * Maps component keys to entities
     * @ignore
     */
    this.index = new Map()
  }

  clear() {
    // Call onRemove on all components of all entities
    for (const [, entity] of this.entities) {
      for (let componentName in entity.data) {
        // Get component, and call onRemove if it exists as a function
        let component = entity.data[componentName]
        invoke(component, 'onRemove')
      }
    }

    // Clear entities
    this.entities.clear()
    this.index.clear()
  }

  registerComponent(name, componentClass) {
    // Only allow functions and classes to be components
    if (typeof componentClass !== 'function') {
      throw new Error('Component is not a valid function or class.')
    }
    this.componentClasses[name] = componentClass
  }

  // Creates a new entity attached to the world
  createEntity() {
    const entityId = this.nextEntityId++
    const entity = new Entity(this.world, entityId)
    this.entities.set(entityId, entity)
    return entity
  }

  // Forwards query args from world
  each(...args) {
    return this.queryIndex(this.queryArgs(...args))
  }

  // Returns an existing or new index
  accessIndex(component) {
    return (
      this.index.get(component) ||
      this.index.set(component, new Map()).get(component)
    )
  }

  // Add component with an entity to the index
  addToIndex(entity, compName) {
    this.accessIndex(compName).set(entity.id, entity)
  }

  // Remove component from the index for an entity
  removeFromIndex(entity, compName) {
    this.accessIndex(compName).delete(entity.id)
  }

  queryArgs(...args) {
    // Gather component names and a callback (if any) from args
    const result = {
      componentNames: [],
      callback: null,
    }
    for (const arg of args) {
      if (typeof arg === 'string') {
        result.componentNames.push(arg)
      } else if (typeof arg === 'function') {
        result.callback = arg
      } else if (Array.isArray(arg)) {
        // Add 1-level deep arrays of strings as separate component names
        for (const name of arg) {
          result.componentNames.push(name)
        }
      } else {
        throw new Error(
          `Unknown argument ${arg} with type ${typeof arg} passed to world.each().`
        )
      }
    }
    return result
  }

  // Uses an existing index or builds a new index, to get entities with the specified components
  // If callback is defined, it will be called for each entity with component data, and returns undefined
  // If callback is not defined, an array of entities will be returned
  queryIndex({ componentNames, callback }) {
    // Return all entities (array if no callback)
    if (componentNames.length === 0) {
      const iter = this.entities.values()
      if (!callback) {
        return [...iter]
      }
      for (const entity of iter) {
        if (callback(entity.data, entity) === false) {
          break
        }
      }
      return
    }

    // Sort component names by number of components in each index
    // This allows us to get the minimum component index as the first element
    // This also helps short-circuit much faster during the full query loops with .every()
    componentNames.sort(
      (a, b) => this.accessIndex(a).size - this.accessIndex(b).size
    )
    const iter = this.accessIndex(componentNames[0]).values()

    // Return array of matched entities
    if (!callback) {
      const results = []
      for (const entity of iter) {
        const { data } = entity
        if (componentNames.every(name => name in data)) {
          results.push(entity)
        }
      }
      return results
    }

    // Invoke callback for each matched entity
    for (const entity of iter) {
      const { data } = entity
      if (
        componentNames.every(name => name in data) &&
        callback(data, entity) === false
      ) {
        return
      }
    }
  }
}
<|endoftext|>


<commit_before>/*!
 * template-helpers <https://github.com/jonschlinkert/template-helpers>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

require('should');
var helpers = require('..');
var _ = require('lodash');

var imports = {imports: helpers.code};

describe('code', function() {
  var orig = process.cwd();
  before(function () {
    process.chdir(__dirname + '/fixtures');
  });
  after(function () {
    process.chdir(orig);
  });

  describe('embed', function() {
    it('should return create a code example from the given file.', function() {
      _.template('<%= embed("a.js") %>', imports)().should.equal([
        '```js',
        'function foo(a, b, c) {',
        '  return a + b + c;',
        '}',
        '```\n'
      ].join('\n'));
    });

    it('should use the extension specified as a second argument.', function() {
      _.template('<%= embed("a.js", "javascript") %>', imports)().should.equal([
        '```javascript',
        'function foo(a, b, c) {',
        '  return a + b + c;',
        '}',
        '```\n'
      ].join('\n'));
    });

    it.skip('should return create a jsfiddle link.', function() {
      _.template('<%= jsfiddle({id: "0dfk10ks", {tabs: true}}) %>', imports)()
      .should.equal([
        '```js',
        'function foo(a, b, c) {',
        '  return a + b + c;',
        '}',
        '```\n'
      ].join('\n'));
    });
  });
});
<commit_msg>jsfiddle tests<commit_after>/*!
 * template-helpers <https://github.com/jonschlinkert/template-helpers>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

require('should');
var helpers = require('..');
var _ = require('lodash');

var imports = {imports: helpers.code};

describe('code', function() {
  var orig = process.cwd();
  before(function () {
    process.chdir(__dirname + '/fixtures');
  });
  after(function () {
    process.chdir(orig);
  });

  describe('embed', function() {
    it('should return create a code example from the given file.', function() {
      _.template('<%= embed("a.js") %>', imports)().should.equal([
        '```js',
        'function foo(a, b, c) {',
        '  return a + b + c;',
        '}',
        '```\n'
      ].join('\n'));
    });

    it('should use the extension specified as a second argument.', function() {
      _.template('<%= embed("a.js", "javascript") %>', imports)().should.equal([
        '```javascript',
        'function foo(a, b, c) {',
        '  return a + b + c;',
        '}',
        '```\n'
      ].join('\n'));
    });

    it('should return create a jsfiddle link.', function() {
      _.template('<%= jsfiddle({id: "c0th6weq", tabs: true}) %>', imports)()
      .should.equal('<iframe width="100%" height="300" src="http://jsfiddle.net/c0th6weq/embedded/true/presentation/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>');
    });
  });
});
<|endoftext|>


<commit_before>/**
 * Environment Variables affecting this config.
 * --------------------------------------------
 *
 * DARTIUM: The full path to the Dartium binary.
 *
 * NGDART_EXAMPLE_BASEURL: Overrides the default baseUrl to one of your
 *     choosing.  (The default is http://localhost:8080 which is the
 *     correct if you simply run "pub serve" inside the example folder
 *     of the AngularDart repo.)
 */

var env = process.env,
    fs = require('fs'),
    path = require('path');

function getBaseUrl() {
  if (env.NGDART_EXAMPLE_BASEURL) {
    return env.NGDART_EXAMPLE_BASEURL;
  } else if (env.USER == 'chirayu') {
    return 'http://example.ngdart.localhost';
  } else {
    // Default host:port when you run "pub serve" from the example
    // subdirectory of the AngularDart repo.
    return 'http://localhost:8080';
  }
}

function getDartiumBinary() {
  var ensure = function(condition) {
    if (!condition) throw "Unable to locate Dartium.  Please set the DARTIUM environment variable.";
  };

  // DARTIUM is set by scripts/env.sh for Travis.
  if (env.DARTIUM) {
    return env.DARTIUM;
  }
  var platform = require('os').platform();
  var DART_SDK = env.DART_SDK;
  if (DART_SDK) {
    // Locate the chromium directory as a sibling of the DART_SDK
    // directory.  (It's there if you unpacked the full Dart distribution.)
    var chromiumRoot = path.join(DART_SDK, "../chromium");
    ensure(fs.existsSync(chromiumRoot));
    var binary = path.join(chromiumRoot,
        (platform == 'darwin') ? 'Chromium.app/Contents/MacOS/Chromium' : 'chrome');
    ensure(fs.existsSync(binary));
    return binary;
  }
  // Last resort: Try the standard location on Macs for the AngularDart team.
  var binary = '/Applications/dart/chromium/Chromium.app/Contents/MacOS/Chromium';
  ensure(platform == 'darwin' && fs.existsSync(binary));
  return binary;
}

exports.config = {
  seleniumAddress: 'http://127.0.0.1:4444/wd/hub',

  specs: [
    'animation_spec.dart',
    'hello_world_spec.dart',
    'todo_spec.dart'
  ],

  splitTestsBetweenCapabilities: true,

  multiCapabilities: [{
    'browserName': 'chrome',
    'chromeOptions': {
      'binary': getDartiumBinary(),
    },
    count: 4
  }],

  baseUrl: getBaseUrl(),

  jasmineNodeOpts: {
    isVerbose: true, // display spec names.
    showColors: true, // print colors to the terminal.
    includeStackTrace: true, // include stack traces in failures.
    defaultTimeoutInterval: 15000 // wait time in ms before failing a test.
  },
};
<commit_msg>test(conf): use DARTIUM_BIN instead of DARTIUM<commit_after>/**
 * Environment Variables affecting this config.
 * --------------------------------------------
 *
 * DARTIUM_BIN: The full path to the Dartium binary.
 *
 * NGDART_EXAMPLE_BASEURL: Overrides the default baseUrl to one of your
 *     choosing.  (The default is http://localhost:8080 which is the
 *     correct if you simply run "pub serve" inside the example folder
 *     of the AngularDart repo.)
 */

var env = process.env,
    fs = require('fs'),
    path = require('path');

function getBaseUrl() {
  if (env.NGDART_EXAMPLE_BASEURL) {
    return env.NGDART_EXAMPLE_BASEURL;
  } else if (env.USER == 'chirayu') {
    return 'http://example.ngdart.localhost';
  } else {
    // Default host:port when you run "pub serve" from the example
    // subdirectory of the AngularDart repo.
    return 'http://localhost:8080';
  }
}

function getDartiumBinary() {
  var ensure = function(condition) {
    if (!condition) throw "Unable to locate Dartium.  Please set the DARTIUM_BIN environment variable.";
  };

  // DARTIUM_BIN is set by scripts/env.sh for Travis.
  if (env.DARTIUM_BIN) {
    return env.DARTIUM_BIN;
  }
  var platform = require('os').platform();
  var DART_SDK = env.DART_SDK;
  if (DART_SDK) {
    // Locate the chromium directory as a sibling of the DART_SDK
    // directory.  (It's there if you unpacked the full Dart distribution.)
    var chromiumRoot = path.join(DART_SDK, "../chromium");
    ensure(fs.existsSync(chromiumRoot));
    var binary = path.join(chromiumRoot,
        (platform == 'darwin') ? 'Chromium.app/Contents/MacOS/Chromium' : 'chrome');
    ensure(fs.existsSync(binary));
    return binary;
  }
  // Last resort: Try the standard location on Macs for the AngularDart team.
  var binary = '/Applications/dart/chromium/Chromium.app/Contents/MacOS/Chromium';
  ensure(platform == 'darwin' && fs.existsSync(binary));
  return binary;
}

exports.config = {
  seleniumAddress: 'http://127.0.0.1:4444/wd/hub',

  specs: [
    'animation_spec.dart',
    'hello_world_spec.dart',
    'todo_spec.dart'
  ],

  splitTestsBetweenCapabilities: true,

  multiCapabilities: [{
    'browserName': 'chrome',
    'chromeOptions': {
      'binary': getDartiumBinary(),
    },
    count: 4
  }],

  baseUrl: getBaseUrl(),

  jasmineNodeOpts: {
    isVerbose: true, // display spec names.
    showColors: true, // print colors to the terminal.
    includeStackTrace: true, // include stack traces in failures.
    defaultTimeoutInterval: 15000 // wait time in ms before failing a test.
  },
};
<|endoftext|>


<commit_before>var Network = (function (Data, Log) {

    var net = require('net');
    var events = require('events');

    var Connection = function (options) {
        this.name = options.name;
        this.host = options.host;
        this.port = options.port;

        var self = this;
        this.connect = function () {
            self.socket.connect(self.port, self.host);
        };

        this.socket = new net.Socket();
        this.emitter = new events.EventEmitter();

        this.write = function (data) {};    // To be defined
        this.emitter.write = function (data) {
            self.write(data);
        };
    };

    var dataRelay = new Connection({
        name: 'dataRelay',
        host: '127.0.0.1',
        port: 1234,
    });
    var multiEcho = new Connection({
        name: 'multiEcho',
        host: '127.0.0.1',
        port: 4321,
    });

    // Data-relay handlers

    dataRelay.socket.on('connect', function () {
        Log.info('Network (dataRelay) Connected: ' + dataRelay.host + ':' + dataRelay.port);
        this.write("commander\r\n");
    });

    dataRelay.socket.on('data', function (data) {
        data = data.toString();

        // First transmission is header columns
        if (Data.headers.length === 0) {
            Data.headers = data.split(",").map(function (str) {
                return str.trim();
            });
            return;
        }

        var dataSplit = data.split(",");
        var cloneState = {};
        for (var i = 0; i < dataSplit.length; i++) {
            Data.state[Data.headers[i]] = dataSplit[i].trim().toString().replace('(','').replace(')','');
            cloneState[Data.headers[i]] = dataSplit[i].trim().toString().replace('(','').replace(')','');
        }
        Data.history.push(cloneState);

        Log.debug("Network (dataRelay) Parsed:" + JSON.stringify(Data.state));

        dataRelay.emitter.emit('data', Data.state);
    });

    dataRelay.socket.on('error', function (err) {
        console.log('dataRelay.error', err.code);
    });

    dataRelay.socket.on('close', function (had_error) {
        if (had_error) {
            Log.error('Network (dataRelay) Closed: Retrying connection');
        } else {
            Log.info('Network (dataRelay) Closed: Retrying connection');
        }

        Data.headers = [];
        setTimeout(dataRelay.connect, Math.random() * 1000 + 1000);
    });

    dataRelay.write = function (data) {
        dataRelay.socket.write(data);
        Log.info("Network (dataRelay) Sent: " + data);
    };

    // Multi-echo handlers

    multiEcho.socket.on('connect', function () {
        Log.info('Network (multiEcho) Connected: ' + multiEcho.host + ':' + multiEcho.port);
        console.log('Network (multiEcho) Connected: ' + multiEcho.host + ':' + multiEcho.port);
    });

    multiEcho.socket.on('data', function (data) {
        data = data.toString().trim();

        data = data.split('\n');

        for (var i = 0; i < data.length; ++i) {
            var parts = data[i].split(':');
            if (parts[0] != "TA") continue;

            var arr = parts[1].split(',').map(function (str){
                return str.trim();
            });

            var comp = Data.compIDs.indexOf(arr[3]);
            if (comp == -1) {
                comp = Data.compIDs.push(arr[3]) - 1;
            }

            var target = {
                lat: arr[0],
                lon: arr[1],
                type: arr[2],
                comp: comp,
            };
            Data.targets.push(target);

            Log.debug("Network (multiEcho) Parsed: " + JSON.stringify(target));

            multiEcho.emitter.emit('data', target);
        }
    });

    multiEcho.socket.on('error', function (err) {
        console.log('multiEcho.error', err.code);
    });

    multiEcho.socket.on('close', function (had_error) {
        if (had_error) {
            Log.error('Network (multiEcho) Closed: Not reconnecting');
        } else {
            Log.info('Network (multiEcho) Closed: Not reconnecting');
        }

        // setTimeout(multiEcho.connect, Math.random() * 1000 + 1000);
    });

    multiEcho.write = function (data) {
        multiEcho.socket.write(data);
        Log.info("Network Sent (multiEcho): " + data);
    };

    $(function () {
        dataRelay.connect();
        multiEcho.connect();
    });

    // Export all relevant event emitters
    return {
        dataRelay: dataRelay.emitter,
        multiEcho: multiEcho.emitter,
    };

})(Data, Log);<commit_msg>Add Ctrl+L shortcut to connect dataRelay/multiEcho to other IP & port.<commit_after>var Network = (function (Data, Log, Mousetrap) {

    var net = require('net');
    var events = require('events');

    var Connection = function (options) {
        this.name = options.name;
        this.host = options.host;
        this.port = options.port;

        var self = this;
        this.connect = function () {
            self.socket.connect(self.port, self.host);
        };

        this.socket = new net.Socket();
        this.emitter = new events.EventEmitter();

        this.write = function (data) {};    // To be defined
        this.emitter.write = function (data) {
            self.write(data);
        };
        this.emitter.socket = this.socket;
        this.emitter.configure = function (host, port) {
            if (!host || !port) {
                console.error(self.name, "network configure error: empty host", host, "or empty port", port);
                return;
            }
            self.host = host;
            self.port = port;
        };
    };

    var dataRelay = new Connection({
        name: 'dataRelay',
        host: '127.0.0.1',
        port: 1234,
    });
    var multiEcho = new Connection({
        name: 'multiEcho',
        host: '127.0.0.1',
        port: 4321,
    });

    // Data-relay handlers

    dataRelay.socket.on('connect', function () {
        Log.info('Network (dataRelay) Connected: ' + dataRelay.host + ':' + dataRelay.port);
        this.write("commander\r\n");
    });

    dataRelay.socket.on('data', function (data) {
        data = data.toString();

        // First transmission is header columns
        if (Data.headers.length === 0) {
            Log.debug('Network (dataRelay) Received headers: ' + data);
            Data.headers = data.split(",").map(function (str) {
                return str.trim();
            });
            Log.debug('Network (dataRelay) Parsed headers: ' + JSON.stringify(Data.headers));
            return;
        }

        var dataSplit = data.split(",");
        var cloneState = {};
        for (var i = 0; i < dataSplit.length; i++) {
            Data.state[Data.headers[i]] = dataSplit[i].trim().toString().replace('(','').replace(')','');
            cloneState[Data.headers[i]] = dataSplit[i].trim().toString().replace('(','').replace(')','');
        }
        Data.history.push(cloneState);

        Log.debug("Network (dataRelay) Parsed:" + JSON.stringify(Data.state));

        dataRelay.emitter.emit('data', Data.state);
    });

    dataRelay.socket.on('error', function (err) {
        console.log('dataRelay.error', err.code);
    });

    dataRelay.socket.on('close', function (had_error) {
        if (had_error) {
            Log.error('Network (dataRelay) Closed: Not reconnecting');
        } else {
            Log.info('Network (dataRelay) Closed: Not reconnecting');
        }

        Data.headers = [];
        // setTimeout(dataRelay.connect, Math.random() * 1000 + 1000);
    });

    dataRelay.write = function (data) {
        dataRelay.socket.write(data);
        Log.info("Network (dataRelay) Sent: " + data);
    };

    // Multi-echo handlers

    multiEcho.socket.on('connect', function () {
        Log.info('Network (multiEcho) Connected: ' + multiEcho.host + ':' + multiEcho.port);
        console.log('Network (multiEcho) Connected: ' + multiEcho.host + ':' + multiEcho.port);
    });

    multiEcho.socket.on('data', function (data) {
        data = data.toString().trim();

        data = data.split('\n');

        for (var i = 0; i < data.length; ++i) {
            var parts = data[i].split(':');
            if (parts[0] != "TA") continue;

            var arr = parts[1].split(',').map(function (str){
                return str.trim();
            });

            var comp = Data.compIDs.indexOf(arr[3]);
            if (comp == -1) {
                comp = Data.compIDs.push(arr[3]) - 1;
            }

            var target = {
                lat: arr[0],
                lon: arr[1],
                type: arr[2],
                comp: comp,
            };
            Data.targets.push(target);

            Log.debug("Network (multiEcho) Parsed: " + JSON.stringify(target));

            multiEcho.emitter.emit('data', target);
        }
    });

    multiEcho.socket.on('error', function (err) {
        console.log('multiEcho.error', err.code);
    });

    multiEcho.socket.on('close', function (had_error) {
        if (had_error) {
            Log.error('Network (multiEcho) Closed: Not reconnecting');
        } else {
            Log.info('Network (multiEcho) Closed: Not reconnecting');
        }

        // setTimeout(multiEcho.connect, Math.random() * 1000 + 1000);
    });

    multiEcho.write = function (data) {
        multiEcho.socket.write(data);
        Log.info("Network Sent (multiEcho): " + data);
    };

    $(function () {
        dataRelay.connect();
        multiEcho.connect();
    });

    Mousetrap.bind(["mod+l"], function () {
        var connectionId;
        while (connectionId != "d" && connectionId != "m") {
            connectionId = prompt("Connect to a server\n\nWhich connection?\n(d: dataRelay, m: multiEcho)");
            if (connectionId === null) return;
        }
        var connection = {d: dataRelay, m: multiEcho}[connectionId];

        var parseLocation = function (locationStr) {
            if (!locationStr) return null;
            var parts = locationStr.trim().split(':');
            if (parts.length != 2) return null;
            return {host: parts[0], port: parts[1]};
        };
        var location, locationStr;
        while (!(location = parseLocation(locationStr))) {
            locationStr = prompt("Connect to a server (" + connection.name + ")\n\nWhich locationStr?\n(host:port)", connection.host + ":" + connection.port);
            if (locationStr === null) return;
        };

        // Reconnect
        connection.host = location.host;
        connection.port = location.port;
        connection.socket.destroy();
        connection.connect();
    });

    // Export all relevant event emitters
    return {
        dataRelay: dataRelay.emitter,
        multiEcho: multiEcho.emitter,
    };

})(Data, Log, Mousetrap);<|endoftext|>