<commit_before>/*global HTMLDocument, Window, console, define */
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof require === 'function') {
    module.exports = factory();
  } else {
    root.Costanza = factory();
  }
}(this, function() {
  function defaultReporter(info, err) {
    /*jshint eqnull:true */
    console.error('Costanza error:'
          + '\n  type: ' + info.type
          + '\n  section: ' + info.section
          + (info.url ? '\n  url: ' + info.url : '')
          + (info.line != null ? '\n  line: ' + info.line : '')
          + ((info.stack && info.stack.indexOf(info.msg) < 0) ? '\n\n' + info.msg : ''),
          + '\n\n' + (info.stack || info.msg));
  }

  var reportCallback = defaultReporter,
      currentSection = 'global',
      _listeners = [],
      _addEventStr = window.attachEvent ? 'attachEvent' : 'addEventListener',
      _removeEventStr = window.detachEvent ? 'detachEvent' : 'removeEventListener',
      _onError,
      _setTimeout,
      _setInterval;

  function init(_reportCallback, options) {
    options = options || {};
    reportCallback = _reportCallback || defaultReporter;

    if (!options.noGlobal && window.addEventListener && (!window.onerror || !window.onerror._costanza)) {
      _onError = _onError || window.onerror;
      window.onerror = onErrorRoot;

      window.addEventListener('error', onError, true);

      if ('onpagehide' in window) {
        window.addEventListener('pageshow', onPageShow, false);
        window.addEventListener('pagehide', onPageHide, false);
      } else {
        window.addEventListener('beforeunload', onPageHide, false);
      }
    }

    // Allow users to opt out of the native prototype augmentation.
    if (options.safeMode) {
      return;
    }

    // IE <=8 makes it nearly impossible to override setTimeout, so we don't
    // http://www.adequatelygood.com/Replacing-setTimeout-Globally.html
    if (!window.attachEvent) {
      if (window.setTimeout && !setTimeout._costanza) {
        _setTimeout = setTimeout;
        window.setTimeout = wrapSet(_setTimeout);
      }

      if (window.setInterval && !setInterval._costanza) {
        _setInterval = setInterval;
        window.setInterval = wrapSet(_setInterval);
      }
    }

    function wrapSet($super) {
      function ret(_callback, duration) {
        var callback = _callback;
        if (typeof _callback === 'string') {
          callback = function() {
            // Force global exec
            // http://perfectionkills.com/global-eval-what-are-the-options
            (1,window.eval)(_callback);
          };
        }

        var args = Array.prototype.slice.call(arguments);
        args[0] = bind({
          captureErrors: true,     // No need to propagate to global error handler
          callback: callback
        });

        return $super.apply(this, args);
      }
      ret._costanza = true;
      return ret;
    }

    if (window.Element && Element.prototype[_addEventStr] && !Element.prototype[_addEventStr]._costanza) {

      wrapListener(Element.prototype);
      if (window.HTMLDocument || window.Document) {
        // IE10: Document, others are HTMLDocument
        wrapListener((window.HTMLDocument || window.Document).prototype);
      }
      if (window.Window) {
        wrapListener(Window.prototype);
      }
    }

    function addCleanupListener(proto, listenerName, listener) {
      proto[listenerName]._costanza = true;

      _listeners.push(function() {
        proto[listenerName] = listener;
      });
    }

    function wrapListener(proto) {
      var _addListener,
          _removeListener;

      // We must pair the native implementation with the proper proto object as ios7 will throw
      // if they are not.
          //
      _addListener = proto[_addEventStr];
      proto[_addEventStr] = function(type, callback, useCapture) {
        if (!callback._section) {
          var className = '';
          if (this.className) {
            // baseValue for SVGAnimatedString on svg elements
            className = this.className.baseVal || this.className;
            className = '.' + className.replace(' ', '.');
          }

          var elementId = (this.nodeName || 'window').toLowerCase()
              + (this.id ? '#' + this.id : className),
            sectionName = 'event-' + elementId + ':' + type;

          if (callback.handleEvent) {
            callback._section = {
              handleEvent: bind(sectionName, function(event) {
                return callback.handleEvent(event);
              })
            };
          } else {
            callback._section = bind({
              name: sectionName,
              captureErrors: true,   // No need to propagate to global error handler
              callback: callback
            });
          }
        }

        _addListener.call(this, type, callback._section, useCapture);
      };

      addCleanupListener(proto, _addEventStr, _addListener);

      _removeListener = proto[_removeEventStr];
      proto[_removeEventStr] = function(type, callback, useCapture) {
        _removeListener.call(this, type, callback._section || callback, useCapture);
      };

      addCleanupListener(proto, _removeEventStr, _removeListener);
    }
  }

  function cleanup() {
    while (_listeners.length) {
      _listeners.pop()();
    }

    reportCallback = defaultReporter;
    if (_setTimeout && setTimeout._costanza) {
      window.setTimeout = _setTimeout;
    }
    if (_setInterval && setInterval._costanza) {
      window.setInterval = _setInterval;
    }

    if (window.onerror && window.onerror._costanza) {
      window.onerror = _onError;

      if (window.removeEventListener) {
        window.removeEventListener('error', onError, true);
      }
    }
  }

  function bind(/* [name, ][info, ] callback */) {
    var callback = arguments[2] || arguments[1] || arguments[0],
        captureErrors,
        info,
        name = currentSection;

    if (!callback.apply) {
      // Options object was passed
      name = callback.name || name;
      info = callback.info;
      captureErrors = callback.captureErrors;

      callback = callback.callback;
    } else {
      // Array-based arguments
      if (arguments.length > 2) {
        info = arguments[1];
        name = arguments[0];
      }
      if (arguments.length > 1) {
        name = arguments[0];
        if (typeof name !== 'string') {
          info = name;
          name = currentSection;
        }
      }
    }

    if (!callback || !callback.apply) {
      // If we don't have a valid function, log it and pass whatever it is on
      reportError(new Error('Costanza:Unexpected bind: ' + callback));

      return callback;
    } else if (callback._costanza) {
      return callback;
    }

    var ret = function() {
      var priorSite = currentSection;

      try {
        currentSection = name;

        return callback.apply(this, arguments);
      } catch (err) {
        reportError(err);

        // Continute propagation of the error so we aren't left in an intedeterminate
        // state
        if (!captureErrors) {
          // Rewrite the error so we know that its already gone through the error handling
          // stack
          if (!err.stack) {
            // Unexpected data. Log and hope that this is surfaced somewhere that it's usable.
            if (window.console) {
              console.error('Unknown throw:', err);
            }

            var toThrow = new Error('Costanza: ' + err);
            toThrow._costanza = true;
            throw toThrow;
          } else {
            // We don't want to throw a different type or otherwise loose the data that we have
            // attached to this error message. Instead do the "safest" thing and insert our
            // logging message so we can detect this in the global error handler.
            err.message = 'Costanza: ' + err.message;
            err._costanza = true;
            throw err;
          }
        }
      } finally {
        currentSection = priorSite;
      }
    };
    ret._costanza = true;
    return ret;

    function reportError(err) {
      // Do not over report
      if (err._costanza) {
        return;
      }
      err._costanza = true;

      var reportInfo = {
        type: 'javascript',
        section: currentSection,
        msg: err.message,
        stack: (err.stack || err) + ''
      };

      // Inline debug data
      function extend(info) {
        if (info) {
          for (var keyName in info) {
            if (info.hasOwnProperty(keyName)) {
              reportInfo[keyName] = info[keyName];
            }
          }
        }
      }
      extend(info);
      extend(err.info);

      reportCallback(reportInfo, err);
    }
  }

  function onError(errorMsg, url, lineNumber, error) {
    var type = 'javascript';

    // Handle ErrorEvent if we receieve it
    if (errorMsg && errorMsg.message) {
      url = errorMsg.filename;
      lineNumber = errorMsg.lineno;
      error = errorMsg.error;
      errorMsg = errorMsg.message;
    }

    var isCostanzaError = /\bCostanza: /.test(errorMsg);
    if ((errorMsg === 'Script error.' && !url)
        || isCostanzaError) {
      // Ignore untrackable external errors locally
      // Also ignore errors that have already been reported (The _costanza flag does not
      // propagate to window.onerror
      return isCostanzaError;
    }
    if (!url && lineNumber === 0) {
      // If the external script error message MIGHT have some more meaning then provide a more
      // meaningful url than undefined.
      url = 'Unknown External Script';
    }

    // This is a real event handler vs. the onerror special case.
    // Since some browsers decide to treat window.onerror as the error event handler,
    // we have to be prepared for either case
    var el = errorMsg.target || errorMsg.srcElement;
    if (errorMsg && el) {
      // Don't submit duplciate events (and if we weren't already tracking
      // it, it probably wasn't that important)
      // Additionally ignore load errors that might happen after page unload has started.
      if (errorMsg.defaultPrevented || errorMsg._costanzaHandled
          || Costanza.pageUnloading) {
        return;
      }

      errorMsg._costanzaHandled = true;
      url = url || el.src || el.href;
      type = el.nodeName.toLowerCase();
      errorMsg = 'load-failed';
    }

    // Error argument added to the spec. Adding support so we can catch this as it rolls out
    // http://html5.org/tools/web-apps-tracker?from=8085&to=8086
    reportCallback({
      section: currentSection,
      url: url,
      line: lineNumber,
      type: type,

      // Cast error message to string as it sometimes can come through with objects, particularly DOM objects
      // containing circular references.
      msg: errorMsg+'',
      stack: error && error.stack
    }, error);
  }

  function onErrorRoot(errorMsg, url, lineNumber, error) {
    _onError && _onError(errorMsg, url, lineNumber, error);
    return onError(errorMsg, url, lineNumber, error);
  }
  onErrorRoot._costanza = true;

  function onPageShow(event) {
    Costanza.pageUnloading = false;
  }
  function onPageHide(event) {
    Costanza.pageUnloading = true;
  }

  var Costanza = {
    init: init,
    cleanup: cleanup,
    current: function() {
      return currentSection;
    },
    emit: function(info, error) {
      reportCallback(info, error);
    },
    bind: bind,
    run: function(name, info, callback) {
      return bind(name, info, callback)();
    },
    onError: onError
  };
  return Costanza;
}));
<commit_msg>Quiet jshint error<commit_after>/*global HTMLDocument, Window, console, define */
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof require === 'function') {
    module.exports = factory();
  } else {
    root.Costanza = factory();
  }
}(this, function() {
  function defaultReporter(info, err) {
    /*jshint eqnull:true */
    console.error('Costanza error:'
          + '\n  type: ' + info.type
          + '\n  section: ' + info.section
          + (info.url ? '\n  url: ' + info.url : '')
          + (info.line != null ? '\n  line: ' + info.line : '')
          + ((info.stack && info.stack.indexOf(info.msg) < 0) ? '\n\n' + info.msg : ''),
          + '\n\n' + (info.stack || info.msg));
  }

  var reportCallback = defaultReporter,
      currentSection = 'global',
      _listeners = [],
      _addEventStr = window.attachEvent ? 'attachEvent' : 'addEventListener',
      _removeEventStr = window.detachEvent ? 'detachEvent' : 'removeEventListener',
      _onError,
      _setTimeout,
      _setInterval;

  function init(_reportCallback, options) {
    options = options || {};
    reportCallback = _reportCallback || defaultReporter;

    if (!options.noGlobal && window.addEventListener && (!window.onerror || !window.onerror._costanza)) {
      _onError = _onError || window.onerror;
      window.onerror = onErrorRoot;

      window.addEventListener('error', onError, true);

      if ('onpagehide' in window) {
        window.addEventListener('pageshow', onPageShow, false);
        window.addEventListener('pagehide', onPageHide, false);
      } else {
        window.addEventListener('beforeunload', onPageHide, false);
      }
    }

    // Allow users to opt out of the native prototype augmentation.
    if (options.safeMode) {
      return;
    }

    // IE <=8 makes it nearly impossible to override setTimeout, so we don't
    // http://www.adequatelygood.com/Replacing-setTimeout-Globally.html
    if (!window.attachEvent) {
      if (window.setTimeout && !setTimeout._costanza) {
        _setTimeout = setTimeout;
        window.setTimeout = wrapSet(_setTimeout);
      }

      if (window.setInterval && !setInterval._costanza) {
        _setInterval = setInterval;
        window.setInterval = wrapSet(_setInterval);
      }
    }

    function wrapSet($super) {
      function ret(_callback, duration) {
        var callback = _callback;
        if (typeof _callback === 'string') {
          callback = function() {
            /*jshint -W061:true */
            // Force global exec
            // http://perfectionkills.com/global-eval-what-are-the-options
            (1,window.eval)(_callback);
          };
        }

        var args = Array.prototype.slice.call(arguments);
        args[0] = bind({
          captureErrors: true,     // No need to propagate to global error handler
          callback: callback
        });

        return $super.apply(this, args);
      }
      ret._costanza = true;
      return ret;
    }

    if (window.Element && Element.prototype[_addEventStr] && !Element.prototype[_addEventStr]._costanza) {

      wrapListener(Element.prototype);
      if (window.HTMLDocument || window.Document) {
        // IE10: Document, others are HTMLDocument
        wrapListener((window.HTMLDocument || window.Document).prototype);
      }
      if (window.Window) {
        wrapListener(Window.prototype);
      }
    }

    function addCleanupListener(proto, listenerName, listener) {
      proto[listenerName]._costanza = true;

      _listeners.push(function() {
        proto[listenerName] = listener;
      });
    }

    function wrapListener(proto) {
      var _addListener,
          _removeListener;

      // We must pair the native implementation with the proper proto object as ios7 will throw
      // if they are not.
          //
      _addListener = proto[_addEventStr];
      proto[_addEventStr] = function(type, callback, useCapture) {
        if (!callback._section) {
          var className = '';
          if (this.className) {
            // baseValue for SVGAnimatedString on svg elements
            className = this.className.baseVal || this.className;
            className = '.' + className.replace(' ', '.');
          }

          var elementId = (this.nodeName || 'window').toLowerCase()
              + (this.id ? '#' + this.id : className),
            sectionName = 'event-' + elementId + ':' + type;

          if (callback.handleEvent) {
            callback._section = {
              handleEvent: bind(sectionName, function(event) {
                return callback.handleEvent(event);
              })
            };
          } else {
            callback._section = bind({
              name: sectionName,
              captureErrors: true,   // No need to propagate to global error handler
              callback: callback
            });
          }
        }

        _addListener.call(this, type, callback._section, useCapture);
      };

      addCleanupListener(proto, _addEventStr, _addListener);

      _removeListener = proto[_removeEventStr];
      proto[_removeEventStr] = function(type, callback, useCapture) {
        _removeListener.call(this, type, callback._section || callback, useCapture);
      };

      addCleanupListener(proto, _removeEventStr, _removeListener);
    }
  }

  function cleanup() {
    while (_listeners.length) {
      _listeners.pop()();
    }

    reportCallback = defaultReporter;
    if (_setTimeout && setTimeout._costanza) {
      window.setTimeout = _setTimeout;
    }
    if (_setInterval && setInterval._costanza) {
      window.setInterval = _setInterval;
    }

    if (window.onerror && window.onerror._costanza) {
      window.onerror = _onError;

      if (window.removeEventListener) {
        window.removeEventListener('error', onError, true);
      }
    }
  }

  function bind(/* [name, ][info, ] callback */) {
    var callback = arguments[2] || arguments[1] || arguments[0],
        captureErrors,
        info,
        name = currentSection;

    if (!callback.apply) {
      // Options object was passed
      name = callback.name || name;
      info = callback.info;
      captureErrors = callback.captureErrors;

      callback = callback.callback;
    } else {
      // Array-based arguments
      if (arguments.length > 2) {
        info = arguments[1];
        name = arguments[0];
      }
      if (arguments.length > 1) {
        name = arguments[0];
        if (typeof name !== 'string') {
          info = name;
          name = currentSection;
        }
      }
    }

    if (!callback || !callback.apply) {
      // If we don't have a valid function, log it and pass whatever it is on
      reportError(new Error('Costanza:Unexpected bind: ' + callback));

      return callback;
    } else if (callback._costanza) {
      return callback;
    }

    var ret = function() {
      var priorSite = currentSection;

      try {
        currentSection = name;

        return callback.apply(this, arguments);
      } catch (err) {
        reportError(err);

        // Continute propagation of the error so we aren't left in an intedeterminate
        // state
        if (!captureErrors) {
          // Rewrite the error so we know that its already gone through the error handling
          // stack
          if (!err.stack) {
            // Unexpected data. Log and hope that this is surfaced somewhere that it's usable.
            if (window.console) {
              console.error('Unknown throw:', err);
            }

            var toThrow = new Error('Costanza: ' + err);
            toThrow._costanza = true;
            throw toThrow;
          } else {
            // We don't want to throw a different type or otherwise loose the data that we have
            // attached to this error message. Instead do the "safest" thing and insert our
            // logging message so we can detect this in the global error handler.
            err.message = 'Costanza: ' + err.message;
            err._costanza = true;
            throw err;
          }
        }
      } finally {
        currentSection = priorSite;
      }
    };
    ret._costanza = true;
    return ret;

    function reportError(err) {
      // Do not over report
      if (err._costanza) {
        return;
      }
      err._costanza = true;

      var reportInfo = {
        type: 'javascript',
        section: currentSection,
        msg: err.message,
        stack: (err.stack || err) + ''
      };

      // Inline debug data
      function extend(info) {
        if (info) {
          for (var keyName in info) {
            if (info.hasOwnProperty(keyName)) {
              reportInfo[keyName] = info[keyName];
            }
          }
        }
      }
      extend(info);
      extend(err.info);

      reportCallback(reportInfo, err);
    }
  }

  function onError(errorMsg, url, lineNumber, error) {
    var type = 'javascript';

    // Handle ErrorEvent if we receieve it
    if (errorMsg && errorMsg.message) {
      url = errorMsg.filename;
      lineNumber = errorMsg.lineno;
      error = errorMsg.error;
      errorMsg = errorMsg.message;
    }

    var isCostanzaError = /\bCostanza: /.test(errorMsg);
    if ((errorMsg === 'Script error.' && !url)
        || isCostanzaError) {
      // Ignore untrackable external errors locally
      // Also ignore errors that have already been reported (The _costanza flag does not
      // propagate to window.onerror
      return isCostanzaError;
    }
    if (!url && lineNumber === 0) {
      // If the external script error message MIGHT have some more meaning then provide a more
      // meaningful url than undefined.
      url = 'Unknown External Script';
    }

    // This is a real event handler vs. the onerror special case.
    // Since some browsers decide to treat window.onerror as the error event handler,
    // we have to be prepared for either case
    var el = errorMsg.target || errorMsg.srcElement;
    if (errorMsg && el) {
      // Don't submit duplciate events (and if we weren't already tracking
      // it, it probably wasn't that important)
      // Additionally ignore load errors that might happen after page unload has started.
      if (errorMsg.defaultPrevented || errorMsg._costanzaHandled
          || Costanza.pageUnloading) {
        return;
      }

      errorMsg._costanzaHandled = true;
      url = url || el.src || el.href;
      type = el.nodeName.toLowerCase();
      errorMsg = 'load-failed';
    }

    // Error argument added to the spec. Adding support so we can catch this as it rolls out
    // http://html5.org/tools/web-apps-tracker?from=8085&to=8086
    reportCallback({
      section: currentSection,
      url: url,
      line: lineNumber,
      type: type,

      // Cast error message to string as it sometimes can come through with objects, particularly DOM objects
      // containing circular references.
      msg: errorMsg+'',
      stack: error && error.stack
    }, error);
  }

  function onErrorRoot(errorMsg, url, lineNumber, error) {
    _onError && _onError(errorMsg, url, lineNumber, error);
    return onError(errorMsg, url, lineNumber, error);
  }
  onErrorRoot._costanza = true;

  function onPageShow(event) {
    Costanza.pageUnloading = false;
  }
  function onPageHide(event) {
    Costanza.pageUnloading = true;
  }

  var Costanza = {
    init: init,
    cleanup: cleanup,
    current: function() {
      return currentSection;
    },
    emit: function(info, error) {
      reportCallback(info, error);
    },
    bind: bind,
    run: function(name, info, callback) {
      return bind(name, info, callback)();
    },
    onError: onError
  };
  return Costanza;
}));
<|endoftext|>


<commit_before>/*
    // dimaninc popups class

    * 2012/01/07
        * reorganized into a class
*/

var diPopups = function() {
	var self = this,
		states = {},
		events = {};

	var globalOptions = {
        positioning: true,
        positioningX: true,
        positioningY: true,
        mobilePositioning: true,
        livePosition: true
    };

	this.e = {
		$bg: $('#gray-bg')
	};
	this.$e_ar = {};
	this.id_prefix = '';
	this.id_suffix = '-dipopup';

	this.defaultCallbacks = {
		realShow: function (obj, id) {
			obj.getPopupElement(id).fadeIn();
			obj.update_position(id);
		},
		realHide: function (obj, id) {
			obj.getPopupElement(id).fadeOut();
		}
	};

	this.callbacks = {};

	function constructor() {
		$(window)
			.off('resize.dipopup-live orientationchange.dipopup-live')
			.on('resize.dipopup-live orientationchange.dipopup-live', function() {
				$.each(self.$e_ar, function(id, $popup) {
					if ($popup.data('live-position')) {
						self.update_position(id);
					}
				});
			});
	}

	this.setGlobalOptions = function(o) {
		globalOptions = $.extend(globalOptions, o || {});

		return this;
	};

	this.disableAutoPosition = function() {
		return this.setGlobalOptions({
            positioning: false,
            positioningX: false,
            positioningY: false,
            mobilePositioning: false,
            livePosition: false
		});
	};

	this.setEvent = function(id, eventName, callback) {
		if (typeof events[id] == 'undefined') {
			events[id] = {};
		}

		events[id][eventName] = callback;

		return this;
	};

	this.fireEvent = function(id, eventName) {
		if (typeof events[id] != 'undefined' && typeof events[id][eventName] != 'undefined') {
			events[id][eventName]({
				name: eventName,
				id: id,
				element: this.getPopupElement(id),
				diPopup: this
			});
		}
	};

	this.setCallback = function(name, callback) {
		this.callbacks[name] = callback;

		return this;
	};

	this.getCallback = function(name) {
		return this.callbacks[name] || this.defaultCallbacks[name];
	};

	this.checkBg = function() {
		if (!this.e.$bg.length) {
			this.e.$bg = $('

').appendTo($(document.body)).click(function() {
				self.hide_all();
			});
		}

		return this;
	};

	this.onBg = function(eventName, callback) {
		this.e.$bg.on(eventName, callback);

		return this;
	};

	this.offBg = function(eventName) {
		this.e.$bg.off(eventName);

		return this;
	};

	this.show_bg = function() {
		this.checkBg();

		if (!this.e.$bg.is(':visible')) {
			this.e.$bg.css({
				opacity: 0
			}).show().animate({
				opacity: 1
			});
		}

		return this;
	};

	this.hide_bg = function() {
		this.e.$bg.fadeOut();
	};

	this.getPopupElement = function(id) {
		return this.exists(id)
			? this.$e_ar[id]
			: $();
	};

	function realShow(id) {
		self.getCallback('realShow')(self, id);
	}

	function realHide(id) {
		self.getCallback('realHide')(self, id);
	}

	this.show = function(name, _opts/* or showBackground */) {
		var opts = {
			showBackground: true,
			content: null,
            positioning: globalOptions.positioning,
            positioningX: globalOptions.positioningX,
            positioningY: globalOptions.positioningY,
            mobilePositioning: globalOptions.mobilePositioning,
            livePosition: globalOptions.livePosition,
			afterUpdatePosition: null
		};
		var $e = this.getPopupElement(name);

		if (typeof _opts === 'object') {
			opts = $.extend(opts, _opts);
		} else if (typeof _opts !== 'undefined') {
			opts.showBackground = _opts;
		}

		if (!this.exists(name)) {
			if (opts.content) {
				this.create($.extend({
					name: name
				}, opts));
			}
		} else if (opts.content) {
			this.setContent({
				name: name,
				content: opts.content
			});
		}

		if ($e.data('detach') && !$e.parent().is('body')) {
			$(document.body).append($e.detach());
		}

		this.checkCloseButton(name);

		realShow(name);

		states[name] = true;

		if (opts.showBackground) {
			this.show_bg();
		}

		$e.children('input[type="text"]:visible,textarea:visible').eq(0).focus();

		this.fireEvent(name, 'show');

		return false;
	};

	this.checkCloseButton = function (name) {
		if (!$('.close', this.$e_ar[name]).length && !this.$e_ar[name].data('no-close')) {
			this.$e_ar[name].prepend($('').click(function() {
				self.hide(name);
				self.hide_bg();
			}));
		}

		return this;
	};

	this.create = function (name/* or options*/, content/* = null*/, options/* = null*/) {
		if (typeof name === 'object' && typeof content === 'undefined' && typeof options === 'undefined') {
			options = name;
		} else {
			options = options || {};
			options.name = name;
			options.content = content;
		}

		options = $.extend({
			name: null,
			content: null,
			showCloseButton: true,
			positioning: globalOptions.positioning, // calc position on show
			positioningX: globalOptions.positioningX, // calc x-position on show
			positioningY: globalOptions.positioningY, // calc y-position on show
			mobilePositioning: globalOptions.mobilePositioning, // calc position on mobiles on show
			livePosition: globalOptions.livePosition, // auto update position on window resize
			afterUpdatePosition: null // after update position callback
		}, options);

		var $el = $('
');
		$el
			.addClass('dipopup')
			.data('type', 'dipopup')
			.attr('data-type', 'dipopup')
			.data('name', options.name)
			.attr('data-name', options.name)
			.attr('data-no-close', !options.showCloseButton)
			.data('no-close', !options.showCloseButton)
			.data('after-update-position', options.afterUpdatePosition)
			.data('positioning', options.positioning)
			.attr('data-positioning', options.positioning)
			.data('positioning-x', options.positioningX)
			.attr('data-positioning-x', options.positioningX)
			.data('positioning-y', options.positioningY)
			.attr('data-positioning-y', options.positioningY)
			.data('mobile-positioning', options.mobilePositioning)
			.attr('data-mobile-positioning', options.mobilePositioning)
			.data('live-position', options.livePosition)
			.attr('data-live-position', options.livePosition)
			.html(options.content)
			.appendTo(document.body);

		this.$e_ar[options.name] = $el;

		return this;
	};

	this.exists = function (name) {
		if (typeof this.$e_ar[name] === 'undefined') {
			this.$e_ar[name] = $([
				'#' + this.id_prefix + name + this.id_suffix,
				'.dipopup[data-name="' + name + '"]',
				'[data-type="dipopup"][data-name="' + name + '"]'
			].join(','));
		}

		return !!this.$e_ar[name].length;
	};

	this.setContent = function (options) {
		options = $.extend({
			name: null,
			content: null,
			create: false
		}, options);

		if (!this.exists(options.name)) {
			if (options.create) {
				this.create(options);
			} else {
				return this;
			}
		}

		this.getPopupElement(options.name).html(options.content);

		this
			.checkCloseButton(options.name)
			.update_position(options.name);

		return this;
	};

	this.update_position = function(id) {
		if (this.$e_ar[id]) {
			var positioning = this.$e_ar[id].data('positioning');
			var mobilePositioning = this.$e_ar[id].data('mobile-positioning');

			if (is_mobile && mobilePositioning !== undefined) {
				positioning = mobilePositioning;
			} else if (positioning === undefined) {
				positioning = true;
			}

			if (!positioning) {
				return this;
			}

			var properties = {};

			if (!this.$e_ar[id].data('manual-x') && this.$e_ar[id].data('positioning-x') !== false) {
				properties.marginLeft = this.$e_ar[id].outerWidth() / -2;
			}

			if (!this.$e_ar[id].data('manual-y') && this.$e_ar[id].data('positioning-y') !== false) {
				properties.marginTop = this.$e_ar[id].outerHeight() / -2;
			}

			this.$e_ar[id].css(properties);

			var cb;

			if (cb = this.$e_ar[id].data('after-update-position')) {
				cb(this.$e_ar[id]);
			}
		}

		return this;
	};

	this.visible = function(id) {
		return states[id];
	};

	this.hide = function(id) {
		realHide(id);

		this.fireEvent(id, 'hide');

		states[id] = false;

		var atLeastOneVisible = false;

		for (var i in this.$e_ar) {
			if (this.$e_ar.hasOwnProperty(i)) {
				if (this.visible(i)) {
					atLeastOneVisible = true;

					break;
				}
			}
		}

		if (!atLeastOneVisible) {
			this.hide_bg();
		}
	};

	this.hide_all = function() {
		this.hide_bg();

		for (var id in this.$e_ar) {
			if (this.$e_ar.hasOwnProperty(id)) {
				this.hide(id);
			}
		}

		return false;
	};

	this.thanks = function(title, content) {
		var id = 'thanks';

		this.show(id);

		$('.window_title', this.$e_ar[id]).html(title);
		$('.window_text', this.$e_ar[id]).html(content);
	};

	constructor();
};

var dip = new diPopups();
<commit_msg>dipopup update<commit_after>/*
    // dimaninc popups class

    * 2012/01/07
        * reorganized into a class
*/

var diPopups = function() {
	var self = this,
		states = {},
		events = {};

	var globalOptions = {
        positioning: true,
        positioningX: true,
        positioningY: true,
        mobilePositioning: true,
        livePosition: true
    };

	this.e = {
		$bg: $('#gray-bg')
	};
	this.$e_ar = {};
	this.optsAr = {};
	this.id_prefix = '';
	this.id_suffix = '-dipopup';

	this.defaultCallbacks = {
		realShow: function (obj, id) {
			obj.getPopupElement(id).fadeIn();
			obj.update_position(id);
		},
		realHide: function (obj, id) {
			obj.getPopupElement(id).fadeOut();
		}
	};

	this.callbacks = {};

	function constructor() {
		$(window)
			.off('resize.dipopup-live orientationchange.dipopup-live')
			.on('resize.dipopup-live orientationchange.dipopup-live', function() {
				$.each(self.$e_ar, function(id, $popup) {
					if ($popup.data('live-position')) {
						self.update_position(id);
					}
				});
			});
	}

	this.setGlobalOptions = function(o) {
		globalOptions = $.extend(globalOptions, o || {});

		return this;
	};

	this.disableAutoPosition = function() {
		return this.setGlobalOptions({
            positioning: false,
            positioningX: false,
            positioningY: false,
            mobilePositioning: false,
            livePosition: false
		});
	};

	this.setEvent = function(id, eventName, callback) {
		if (typeof events[id] === 'undefined') {
			events[id] = {};
		}

		events[id][eventName] = callback;

		return this;
	};

	this.fireEvent = function(id, eventName) {
		if (typeof events[id] !== 'undefined' && typeof events[id][eventName] !== 'undefined') {
			events[id][eventName]({
				name: eventName,
				id: id,
				element: this.getPopupElement(id),
				diPopup: this
			});
		}
	};

	this.setCallback = function(name, callback) {
		this.callbacks[name] = callback;

		return this;
	};

	this.getCallback = function(name) {
		return this.callbacks[name] || this.defaultCallbacks[name];
	};

	this.checkBg = function() {
		if (!this.e.$bg.length) {
			this.e.$bg = $('

').appendTo($(document.body)).click(function() {
				self.hide_all();
			});
		}

		return this;
	};

	this.onBg = function(eventName, callback) {
		this.e.$bg.on(eventName, callback);

		return this;
	};

	this.offBg = function(eventName) {
		this.e.$bg.off(eventName);

		return this;
	};

	this.show_bg = function() {
		this.checkBg();

		if (!this.e.$bg.is(':visible')) {
			this.e.$bg.css({
				opacity: 0
			}).show().animate({
				opacity: 1
			});
		}

		return this;
	};

	this.hide_bg = function() {
		this.e.$bg.fadeOut();
	};

	this.getPopupElement = function(id) {
		return this.exists(id)
			? this.$e_ar[id]
			: $();
	};

	function realShow(id) {
		self.getCallback('realShow')(self, id);
	}

	function realHide(id) {
		self.getCallback('realHide')(self, id);
	}

	this.setOptsFor = function(name, opts) {
		this.optsAr[name] = opts;

		var $e = this.getPopupElement(name);

		if ($e) {
            this.copyOptsToDom(name, $e);
        }

		return this;
	};

	this.show = function(name, _opts/* or showBackground */) {
		var opts = {
            showCloseButton: true,
			showBackground: true,
			content: null,
            positioning: globalOptions.positioning,
            positioningX: globalOptions.positioningX,
            positioningY: globalOptions.positioningY,
            mobilePositioning: globalOptions.mobilePositioning,
            livePosition: globalOptions.livePosition,
			afterUpdatePosition: null
		};
		var $e = this.getPopupElement(name);

		if (typeof _opts === 'object') {
			opts = $.extend(opts, _opts);
		} else if (typeof _opts !== 'undefined') {
			opts.showBackground = _opts;
		}

		if (!this.exists(name)) {
			if (opts.content) {
				this.create($.extend({
					name: name
				}, opts));
			}
		} else if (opts.content) {
			this.setContent({
				name: name,
				content: opts.content
			});
		}

        this.setOptsFor(name, opts);

        if ($e.data('detach') && !$e.parent().is('body')) {
			$(document.body).append($e.detach());
		}

		this.checkCloseButton(name);

		realShow(name);

		states[name] = true;

		if (opts.showBackground) {
			this.show_bg();
		}

		$e.children('input[type="text"]:visible,textarea:visible').eq(0).focus();

		this.fireEvent(name, 'show');

		return false;
	};

	this.checkCloseButton = function (name) {
		if (!$('.close', this.$e_ar[name]).length && !this.$e_ar[name].data('no-close')) {
			this.$e_ar[name].prepend($('').click(function() {
				self.hide(name);
				self.hide_bg();
			}));
		}

		return this;
	};

	this.create = function (name/* or options*/, content/* = null*/, options/* = null*/) {
		if (typeof name === 'object' && typeof content === 'undefined' && typeof options === 'undefined') {
			options = name;
		} else {
			options = options || {};
			options.name = name;
			options.content = content;
		}

		options = $.extend({
			name: null,
			content: null,
			showCloseButton: true,
			positioning: globalOptions.positioning, // calc position on show
			positioningX: globalOptions.positioningX, // calc x-position on show
			positioningY: globalOptions.positioningY, // calc y-position on show
			mobilePositioning: globalOptions.mobilePositioning, // calc position on mobiles on show
			livePosition: globalOptions.livePosition, // auto update position on window resize
			afterUpdatePosition: null // after update position callback
		}, options);

		var $el = $('
');
		this.copyOptsToDom(name, $el);
		$el
			.addClass('dipopup')
			.data('type', 'dipopup')
			.attr('data-type', 'dipopup')
			.html(options.content)
			.appendTo(document.body);

		this.$e_ar[options.name] = $el;

		return this;
	};

	this.copyOptsToDom = function(name, $el) {
		var options = this.optsAr[name];

        $el
            .data('name', options.name)
            .attr('data-name', options.name)
            .attr('data-no-close', !options.showCloseButton)
            .data('no-close', !options.showCloseButton)
            .data('after-update-position', options.afterUpdatePosition)
            .data('positioning', options.positioning)
            .attr('data-positioning', options.positioning)
            .data('positioning-x', options.positioningX)
            .attr('data-positioning-x', options.positioningX)
            .data('positioning-y', options.positioningY)
            .attr('data-positioning-y', options.positioningY)
            .data('mobile-positioning', options.mobilePositioning)
            .attr('data-mobile-positioning', options.mobilePositioning)
            .data('live-position', options.livePosition)
            .attr('data-live-position', options.livePosition);

		return this;
	};

	this.exists = function (name) {
		if (typeof this.$e_ar[name] === 'undefined') {
			this.$e_ar[name] = $([
				'#' + this.id_prefix + name + this.id_suffix,
				'.dipopup[data-name="' + name + '"]',
				'[data-type="dipopup"][data-name="' + name + '"]'
			].join(','));
		}

		return !!this.$e_ar[name].length;
	};

	this.setContent = function (options) {
		options = $.extend({
			name: null,
			content: null,
			create: false
		}, options);

		if (!this.exists(options.name)) {
			if (options.create) {
				this.create(options);
			} else {
				return this;
			}
		}

		this.getPopupElement(options.name).html(options.content);

		this
			.checkCloseButton(options.name)
			.update_position(options.name);

		return this;
	};

	this.update_position = function(id) {
		if (this.$e_ar[id]) {
			var positioning = this.$e_ar[id].data('positioning');
			var mobilePositioning = this.$e_ar[id].data('mobile-positioning');

			if (is_mobile && mobilePositioning !== undefined) {
				positioning = mobilePositioning;
			} else if (positioning === undefined) {
				positioning = true;
			}

			if (!positioning) {
				return this;
			}

			var properties = {};

			if (!this.$e_ar[id].data('manual-x') && this.$e_ar[id].data('positioning-x') !== false) {
				properties.marginLeft = this.$e_ar[id].outerWidth() / -2;
			}

			if (!this.$e_ar[id].data('manual-y') && this.$e_ar[id].data('positioning-y') !== false) {
				properties.marginTop = this.$e_ar[id].outerHeight() / -2;
			}

			this.$e_ar[id].css(properties);

			var cb;

			if (cb = this.$e_ar[id].data('after-update-position')) {
				cb(this.$e_ar[id]);
			}
		}

		return this;
	};

	this.visible = function(id) {
		return states[id];
	};

	this.hide = function(id) {
		realHide(id);

		this.fireEvent(id, 'hide');

		states[id] = false;

		var atLeastOneVisible = false;

		for (var i in this.$e_ar) {
			if (this.$e_ar.hasOwnProperty(i)) {
				if (this.visible(i)) {
					atLeastOneVisible = true;

					break;
				}
			}
		}

		if (!atLeastOneVisible) {
			this.hide_bg();
		}
	};

	this.hide_all = function() {
		this.hide_bg();

		for (var id in this.$e_ar) {
			if (this.$e_ar.hasOwnProperty(id)) {
				this.hide(id);
			}
		}

		return false;
	};

	this.thanks = function(title, content) {
		var id = 'thanks';

		this.show(id);

		$('.window_title', this.$e_ar[id]).html(title);
		$('.window_text', this.$e_ar[id]).html(content);
	};

	constructor();
};

var dip = new diPopups();
<|endoftext|>


<commit_before>$(function () {
    initTable();
    setSize();
});

function doQuery(params){
    $('#demo-table').bootstrapTable('refresh');    //刷新表格
}

// 初始化主表
function initTable(){
    var url = "http://localhost:3000/books/getBook";

    $('#demo-table').bootstrapTable({
        method:'POST',
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded;charset=utf-8",
        cache: false,
        striped: true,                      //是否显示行间隔色
        detailView: true,                  //父子表
        detailFormatter:getChildTable,
        sidePagination: 'server',         //分页方式：client客户端分页，server服务端分页（*）
        url:url,
        showRefresh:'true',
        width:$(window).width(),
        height:$(window).width(),
        showColumns:true,
        pagination:true,
        queryParams:queryParams,
        queryParamsType:'',
        minimumCountColumns:2,
        pageNumber:1,                       //初始化加载第一页，默认第一页
        pageSize: 1,                       //每页的记录行数（*）
        pageList: [10, 25, 50, 100],        //可供选择的每页的行数（*）

        exportDataType: 'all',
        responseHandler:responseHandler,
        columns: [
        // {
        //     field: '',
        //     title: '',
        //     formatter: function (value, row, index) {
        //         return index+1;
        //     }
        // },
        {
            field : 'bid',
            title : '书籍编号',
            align : 'center',
            valign : 'middle'
        },
        {
            field : 'bookName',
            title : '书籍名',
            align : 'center',
            valign : 'middle'
        },
        {
            field : 'bookChapter',
            title : '书籍章节',
            align : 'center',
            valign : 'middle'
        }]
    });
}

function queryParams(params) {
    var param = {
        orgCode : $("#orgCode").val(),
        userName : $("#userName").val(),
        startDate : $("#startDate").val(),
        endDate : $("#endDate").val(),
        limit : this.limit, // 页面大小
        offset : this.offset, // 页码
        pageNumber : this.pageNumber,
        pageSize : this.pageSize
    }
    return param;
}

// 用于server 分页，表格数据量太大的话 不想一次查询所有数据，可以使用server分页查询，数据量小的话可以直接把sidePagination: "server"  改为 sidePagination: "client" ，同时去掉responseHandler: responseHandler就可以了，
function responseHandler(res) {

    if (res) {
        console.log(res);
        return {
            "rows" : res.rows,
            "total" : res.total
        };
    } else {
        return {
            "rows" : [],
            "total" : 0
        };
    }
}

// 生成子表
function getChildTable(index, row, $detail) {

    var url = 'http://localhost:8080/getChapters?random='+Math.random();
    var parentid = row.bookNo;

    var cur_table = $detail.html('

').find('table');
    console.log(cur_table);
    $('#child_table').bootstrapTable({
        url: url,
        method: 'POST',
        queryParams: {parentId: parentid},
        ajaxOptions: {parentId: parentid},
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded;charset=utf-8",
        cache: false,
        striped: true,                      //是否显示行间隔色
        width:$(window).width(),
        queryParamsType:'',
        pageNumber:1,                       //初始化加载第一页，默认第一页
        pageSize: 1000000,                       //每页的记录行数（*）
        responseHandler:function(res){
            return res.rows;
        },
        columns: [
            {
                field: 'chapterId',
                title: '章节编号',
                align: 'center',

            },
            {
                field: 'chapterName',
                title: '章节名',
                align: 'center',

            },
            {
                field: 'downloadUrl',
                title: '下载链接',
                align: 'center',
                valign:'middle',
                formatter: function(value) {
                    return '下载'
                },
            }
        ],
        //无线循环取子表，直到子表里面没有记录
        onExpandRow: function (index, row, $Subdetail) {
            getChildTalbe(index, row, $Subdetail);
        }
    });
}
function setSize(){
    var h = document.body.scrollHeight;
    parent.document.getElementById("childFrame").style.height = h + "px";
}<commit_msg>fix download's page bug<commit_after>$(function () {
    initTable();
});

function doQuery(params){
    $('#demo-table').bootstrapTable('refresh');    //刷新表格
}

// 初始化主表
function initTable(){
    var url = "http://localhost:3000/books/getBook";

    $('#demo-table').bootstrapTable({
        method:'POST',
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded;charset=utf-8",
        cache: false,
        striped: true,                      //是否显示行间隔色
        detailView: true,                  //父子表
        detailFormatter:getChildTable,
        sidePagination: 'server',         //分页方式：client客户端分页，server服务端分页（*）
        url:url,
        showRefresh:'true',
        width:$(window).width(),
        height:$(window).width(),
        showColumns:true,
        pagination:true,
        queryParams:queryParams,
        queryParamsType:'',
        minimumCountColumns:2,
        pageNumber:1,                       //初始化加载第一页，默认第一页
        pageSize: 1,                       //每页的记录行数（*）
        pageList: [10, 25, 50, 100],        //可供选择的每页的行数（*）

        exportDataType: 'all',
        responseHandler:responseHandler,
        columns: [
        // {
        //     field: '',
        //     title: '',
        //     formatter: function (value, row, index) {
        //         return index+1;
        //     }
        // },
        {
            field : 'bid',
            title : '书籍编号',
            align : 'center',
            valign : 'middle'
        },
        {
            field : 'bookName',
            title : '书籍名',
            align : 'center',
            valign : 'middle'
        },
        {
            field : 'bookChapter',
            title : '书籍章节',
            align : 'center',
            valign : 'middle'
        }]
    });
}

function queryParams(params) {
    var param = {
        orgCode : $("#orgCode").val(),
        userName : $("#userName").val(),
        startDate : $("#startDate").val(),
        endDate : $("#endDate").val(),
        limit : this.limit, // 页面大小
        offset : this.offset, // 页码
        pageNumber : this.pageNumber,
        pageSize : this.pageSize
    }
    return param;
}

// 用于server 分页，表格数据量太大的话 不想一次查询所有数据，可以使用server分页查询，数据量小的话可以直接把sidePagination: "server"  改为 sidePagination: "client" ，同时去掉responseHandler: responseHandler就可以了，
function responseHandler(res) {

    if (res) {
        console.log(res);
        return {
            "rows" : res.rows,
            "total" : res.total
        };
    } else {
        return {
            "rows" : [],
            "total" : 0
        };
    }
}

// 生成子表
function getChildTable(index, row, $detail) {

    var url = 'http://localhost:8080/getChapters?random='+Math.random();
    var parentid = row.bookNo;

    var cur_table = $detail.html('

').find('table');
    console.log(cur_table);
    $('#child_table').bootstrapTable({
        url: url,
        method: 'POST',
        queryParams: {parentId: parentid},
        ajaxOptions: {parentId: parentid},
        dataType: 'json',
        contentType: "application/x-www-form-urlencoded;charset=utf-8",
        cache: false,
        striped: true,                      //是否显示行间隔色
        width:$(window).width(),
        queryParamsType:'',
        pageNumber:1,                       //初始化加载第一页，默认第一页
        pageSize: 1000000,                       //每页的记录行数（*）
        responseHandler:function(res){
            return res.rows;
        },
        columns: [
            {
                field: 'chapterId',
                title: '章节编号',
                align: 'center',

            },
            {
                field: 'chapterName',
                title: '章节名',
                align: 'center',

            },
            {
                field: 'downloadUrl',
                title: '下载链接',
                align: 'center',
                valign:'middle',
                formatter: function(value) {
                    return '下载'
                },
            }
        ],
        //无线循环取子表，直到子表里面没有记录
        onExpandRow: function (index, row, $Subdetail) {
            getChildTalbe(index, row, $Subdetail);
        }
    });
}<|endoftext|>


<commit_before>const chat = function () {
    'use strict';
    let buffers = {};
    const recentEntriesCutoff = 100;
    const groupChat = 'group';
    let current = groupChat;
    let newMessageCount = 0;

    function timestamp() {
        return new Date(Date.now()).toLocaleTimeString('en-US', { hour12: false });
    };

    function addJoin(buddy, timestamp) {
        addEntry(groupChat, new Join(buddy, timestamp));
    }
    function addLeave(buddy, timestamp) {
        addEntry(groupChat, new Leave(buddy, timestamp));
    }
    function addGroupMessage(buddy, timestamp, body) {
        addEntry(groupChat, new Message(buddy, timestamp, body));
    }
    // `sender` can be me or the buddy in this chat.
    function addPrivateMessage(buddy, sender, timestamp, body) {
        addEntry(buddy.id, new Message(sender, timestamp, body));
    }
    function addDecryptError(buddy, timestamp) {
        addEntry(groupChat, new DecryptError(buddy, timestamp));
    }
    function addMissingRecipients(buddies) {
        // TODO: implement
        return;
    }

    function reset() {
        buffers = {};
        current = groupChat;
        $('#chatWindow').empty();
    }

    function addEntry(chat, entry) {
        if (!(chat in buffers)) {
            buffers[chat] = [];
        }
        if (!(entry instanceof Entry)) {
            throw 'Entry type required';
        }
        buffers[chat].push(entry);

        if (chat === current) {
            const chatWindow = $('#chatWindow');
            const wasScrolledDown = (Math.ceil(chatWindow.scrollTop() + chatWindow.innerHeight()) >= chatWindow[0].scrollHeight);

            loadRecentEntries(1);
            $('#chatWindow .line').last().animate({ 'top': 0 }, 100);

            if (wasScrolledDown) {
                // Scroll the chat window down.
                chatWindow.stop().animate({ scrollTop: chatWindow[0].scrollHeight });

                const chatEntries = chatWindow.children();
                if (chatEntries.length > recentEntriesCutoff) {
                    // Keep the chat window trimmed to `recentEntriesCutoff`.
                    chatEntries.slice(0, chatEntries.length - recentEntriesCutoff).remove();
                }
            }
            // TODO: show indicator for new messages if the user was scrolled up
        } else {
            $('#buddy-' + chat).addClass('newMessage');
        }

        if (entry instanceof Message || entry instanceof Join) {
            if (!entry.buddy.ignored()) {
                newMessageCount++;
                if (!document.hasFocus()) {
                    document.title = '(' + newMessageCount + ') ' + Cryptodog.me.nickname + '@' + Cryptodog.me.conversation;
                }
            }
        }
        // TODO: Handle audio and desktop notifications
    }

    function switchTo(chat) {
        $('#userInputText').focus();
        if (chat === current) {
            return;
        }
        current = chat;
        if (!(current in buffers)) {
            buffers[current] = [];
        }
        $('#chatWindow').empty();
        loadRecentEntries();
        $('#chatWindow .line').addClass('no-animate');
        const last = $('#chatWindow').children().last();
        if (last.length > 0) {
            last.get(0).scrollIntoView();
        }
    }

    // Render the `recentEntriesCutoff` messages prior to those already displayed.
    function loadPreviousEntries() {
        const numDisplayed = $('#chatWindow').children().length;
        const prev = buffers[current].slice(0, buffers[current].length - numDisplayed).slice(-recentEntriesCutoff);
        for (let i = prev.length - 1; i >= 0; i--) {
            $('#chatWindow').prepend(prev[i].render());
        }
        $('#chatWindow .line').addClass('no-animate');
    }

    // Render the `numEntries` most recent entries, or the `recentEntriesCutoff` most recent if `numEntries` is undefined.
    function loadRecentEntries(numEntries) {
        buffers[current].slice(-(numEntries || recentEntriesCutoff)).forEach(entry => {
            $('#chatWindow').append(entry.render());
        });
    }

    class Entry {
        constructor(buddy, timestamp) {
            this.buddy = buddy;
            this.timestamp = timestamp;
        }
    }
    class Message extends Entry {
        static linkPattern = /(\s*)(https?:\/\/\S+)/gi;

        constructor(buddy, timestamp, body) {
            super(buddy, timestamp);
            this.body = body;
        }

        autolink() {
            this.escapedBody = this.escapedBody.replace(Message.linkPattern, '$1$2');
        }

        markup() {
            // For now, don't allow formatting and links in the same message.
            if (!this.escapedBody.match(Message.linkPattern)) {
                const strongItalicPattern = /\*\*\*((?!\s).+)\*\*\*/gi;
                const strongPattern = /\*\*((?!\s).+)\*\*/gi;
                const italicPattern = /\*((?!\s).+)\*/gi;

                this.escapedBody = this.escapedBody.replace(strongItalicPattern, '$1')
                    .replace(strongPattern, '$1')
                    .replace(italicPattern, '$1');
            }
        }

        render() {
            /* HTML-encode the message body before auto-linking.
               Mustache.js also encodes forward slashes, so we unescape them at the end to allow links. */
            this.escapedBody = Mustache.escape(this.body).replace(///g, '/');
            this.markup();
            this.autolink();

            return Mustache.render(template.message, {
                nickname: this.buddy.nickname,
                timestamp: this.timestamp,
                // body is not HTML-encoded in the message template.
                body: this.escapedBody,
                color: this.buddy.color
            });
        }
    };
    class Join extends Entry {
        render() {
            return Mustache.render(template.userJoin, {
                nickname: this.buddy.nickname,
                timestamp: this.timestamp,
                color: this.buddy.color
            });
        }
    }
    class Leave extends Entry {
        render() {
            return Mustache.render(template.userLeave, {
                nickname: this.buddy.nickname,
                timestamp: this.timestamp,
            });
        }
    }
    class DecryptError extends Entry {
        render() {
            return Mustache.render(template.decryptError, {
                nickname: this.buddy.nickname,
            });
        }
    }

    $(document).ready(function () {
        'use strict';

        $(window).focus(function () {
            if (Cryptodog.me.nickname && Cryptodog.me.conversation) {
                newMessageCount = 0;
                document.title = Cryptodog.me.nickname + '@' + Cryptodog.me.conversation;
                $('#userInputText').focus();
            }
        });

        $('#chatWindow').scroll(function () {
            if ($('#chatWindow').scrollTop() === 0) {
                // Prevent auto-scrolling to the top when we prepend the entries.
                $('#chatWindow').scrollTop(1);
                loadPreviousEntries();
            }
        });

        // Show and hide timestamp when hovering over a message's sender element.
        $('#chatWindow').on('mouseenter', '.sender', function () {
            $(this).find('.nickname').text($(this).attr('data-timestamp'));
        });
        $('#chatWindow').on('mouseleave', '.sender', function () {
            $(this).find('.nickname').text($(this).attr('data-sender'));
        });

        $('#userInputSubmit').click(function () {
            $('#userInput').submit();
        });

        let composing = false;
        $('#userInputText').keydown(function (e) {
            if (e.key === 'Tab') {
                e.preventDefault();
                if (!$(this).val()) {
                    return;
                }
                const nickComplete = tabComplete($(this).val());
                if (nickComplete) {
                    $(this).val(nickComplete);
                }
                return;
            }
            if (e.key === 'Enter') {
                e.preventDefault();
                $('#userInput').submit();
                composing = false;
                return;
            }

            // XXX: potentially hacky way to determine if key is a printable character
            if (e.key.length !== 1) {
                return;
            }

            if (!composing) {
                composing = true;

                let destination;
                if (current !== groupChat) {
                    destination = Cryptodog.userFromId(current);
                }
                meta.sendComposing(destination);

                window.setTimeout(function () {
                    meta.sendPaused(destination);
                    composing = false;
                }, 7000);
            }
        });

        $('#userInput').submit(function (e) {
            e.preventDefault();
            const timestamp = new Date(Date.now()).toLocaleTimeString('en-US', { hour12: false });
            const message = $.trim($('#userInputText').val());
            $('#userInputText').val('');
            if (!message.length) {
                return true;
            }

            if (current !== groupChat) {
                const recipient = Cryptodog.userFromId(current);
                meta.sendPrivateTextMessage(recipient, message);
                addPrivateMessage(recipient, Cryptodog.me, timestamp, message);
            } else {
                meta.sendGroupTextMessage(message);
                // TODO: handle missing recipients
                addGroupMessage(Cryptodog.me, timestamp, message);
            }
        });

        function tabComplete(input) {
            const potentials = [];
            for (const user of Cryptodog.allUsers()) {
                potentials.push({
                    score: user.nickname.score(input.match(/(\S)+$/)[0], 0.01),
                    value: user.nickname
                });
            }
            var largest = potentials[0];

            // find item with largest score
            potentials.forEach(function (item) {
                if (item.score > largest.score) {
                    largest = item;
                }
            }, this);

            let suffix;
            if (input.match(/\s/)) {
                suffix = ' ';
            } else {
                suffix = ': ';
            }
            if (largest.score < 0.1) {
                // cut-off matching attempt if all scores are low
                return input;
            }
            return input.replace(/(\S)+$/, largest.value + suffix);
        };
    });

    return {
        timestamp,
        switchTo,
        addJoin,
        addLeave,
        addGroupMessage,
        addPrivateMessage,
        addDecryptError,
        addMissingRecipients,
        reset
    };
}();
<commit_msg>Don't try to increment message count for user's own messages<commit_after>const chat = function () {
    'use strict';
    let buffers = {};
    const recentEntriesCutoff = 100;
    const groupChat = 'group';
    let current = groupChat;
    let newMessageCount = 0;

    function timestamp() {
        return new Date(Date.now()).toLocaleTimeString('en-US', { hour12: false });
    };

    function addJoin(buddy, timestamp) {
        addEntry(groupChat, new Join(buddy, timestamp));
    }
    function addLeave(buddy, timestamp) {
        addEntry(groupChat, new Leave(buddy, timestamp));
    }
    function addGroupMessage(buddy, timestamp, body) {
        addEntry(groupChat, new Message(buddy, timestamp, body));
    }
    // `sender` can be me or the buddy in this chat.
    function addPrivateMessage(buddy, sender, timestamp, body) {
        addEntry(buddy.id, new Message(sender, timestamp, body));
    }
    function addDecryptError(buddy, timestamp) {
        addEntry(groupChat, new DecryptError(buddy, timestamp));
    }
    function addMissingRecipients(buddies) {
        // TODO: implement
        return;
    }

    function reset() {
        buffers = {};
        current = groupChat;
        $('#chatWindow').empty();
    }

    function addEntry(chat, entry) {
        if (!(chat in buffers)) {
            buffers[chat] = [];
        }
        if (!(entry instanceof Entry)) {
            throw 'Entry type required';
        }
        buffers[chat].push(entry);

        if (chat === current) {
            const chatWindow = $('#chatWindow');
            const wasScrolledDown = (Math.ceil(chatWindow.scrollTop() + chatWindow.innerHeight()) >= chatWindow[0].scrollHeight);

            loadRecentEntries(1);
            $('#chatWindow .line').last().animate({ 'top': 0 }, 100);

            if (wasScrolledDown) {
                // Scroll the chat window down.
                chatWindow.stop().animate({ scrollTop: chatWindow[0].scrollHeight });

                const chatEntries = chatWindow.children();
                if (chatEntries.length > recentEntriesCutoff) {
                    // Keep the chat window trimmed to `recentEntriesCutoff`.
                    chatEntries.slice(0, chatEntries.length - recentEntriesCutoff).remove();
                }
            }
            // TODO: show indicator for new messages if the user was scrolled up
        } else {
            $('#buddy-' + chat).addClass('newMessage');
        }

        if (entry instanceof Message || entry instanceof Join) {
            // TODO: move this check somewhere more sensible
            if ((entry.buddy !== Cryptodog.me) && !entry.buddy.ignored()) {
                newMessageCount++;
                if (!document.hasFocus()) {
                    document.title = '(' + newMessageCount + ') ' + Cryptodog.me.nickname + '@' + Cryptodog.me.conversation;
                }
            }
        }
        // TODO: Handle audio and desktop notifications
    }

    function switchTo(chat) {
        $('#userInputText').focus();
        if (chat === current) {
            return;
        }
        current = chat;
        if (!(current in buffers)) {
            buffers[current] = [];
        }
        $('#chatWindow').empty();
        loadRecentEntries();
        $('#chatWindow .line').addClass('no-animate');
        const last = $('#chatWindow').children().last();
        if (last.length > 0) {
            last.get(0).scrollIntoView();
        }
    }

    // Render the `recentEntriesCutoff` messages prior to those already displayed.
    function loadPreviousEntries() {
        const numDisplayed = $('#chatWindow').children().length;
        const prev = buffers[current].slice(0, buffers[current].length - numDisplayed).slice(-recentEntriesCutoff);
        for (let i = prev.length - 1; i >= 0; i--) {
            $('#chatWindow').prepend(prev[i].render());
        }
        $('#chatWindow .line').addClass('no-animate');
    }

    // Render the `numEntries` most recent entries, or the `recentEntriesCutoff` most recent if `numEntries` is undefined.
    function loadRecentEntries(numEntries) {
        buffers[current].slice(-(numEntries || recentEntriesCutoff)).forEach(entry => {
            $('#chatWindow').append(entry.render());
        });
    }

    class Entry {
        constructor(buddy, timestamp) {
            this.buddy = buddy;
            this.timestamp = timestamp;
        }
    }
    class Message extends Entry {
        static linkPattern = /(\s*)(https?:\/\/\S+)/gi;

        constructor(buddy, timestamp, body) {
            super(buddy, timestamp);
            this.body = body;
        }

        autolink() {
            this.escapedBody = this.escapedBody.replace(Message.linkPattern, '$1$2');
        }

        markup() {
            // For now, don't allow formatting and links in the same message.
            if (!this.escapedBody.match(Message.linkPattern)) {
                const strongItalicPattern = /\*\*\*((?!\s).+)\*\*\*/gi;
                const strongPattern = /\*\*((?!\s).+)\*\*/gi;
                const italicPattern = /\*((?!\s).+)\*/gi;

                this.escapedBody = this.escapedBody.replace(strongItalicPattern, '$1')
                    .replace(strongPattern, '$1')
                    .replace(italicPattern, '$1');
            }
        }

        render() {
            /* HTML-encode the message body before auto-linking.
               Mustache.js also encodes forward slashes, so we unescape them at the end to allow links. */
            this.escapedBody = Mustache.escape(this.body).replace(///g, '/');
            this.markup();
            this.autolink();

            return Mustache.render(template.message, {
                nickname: this.buddy.nickname,
                timestamp: this.timestamp,
                // body is not HTML-encoded in the message template.
                body: this.escapedBody,
                color: this.buddy.color
            });
        }
    };
    class Join extends Entry {
        render() {
            return Mustache.render(template.userJoin, {
                nickname: this.buddy.nickname,
                timestamp: this.timestamp,
                color: this.buddy.color
            });
        }
    }
    class Leave extends Entry {
        render() {
            return Mustache.render(template.userLeave, {
                nickname: this.buddy.nickname,
                timestamp: this.timestamp,
            });
        }
    }
    class DecryptError extends Entry {
        render() {
            return Mustache.render(template.decryptError, {
                nickname: this.buddy.nickname,
            });
        }
    }

    $(document).ready(function () {
        'use strict';

        $(window).focus(function () {
            if (Cryptodog.me.nickname && Cryptodog.me.conversation) {
                newMessageCount = 0;
                document.title = Cryptodog.me.nickname + '@' + Cryptodog.me.conversation;
                $('#userInputText').focus();
            }
        });

        $('#chatWindow').scroll(function () {
            if ($('#chatWindow').scrollTop() === 0) {
                // Prevent auto-scrolling to the top when we prepend the entries.
                $('#chatWindow').scrollTop(1);
                loadPreviousEntries();
            }
        });

        // Show and hide timestamp when hovering over a message's sender element.
        $('#chatWindow').on('mouseenter', '.sender', function () {
            $(this).find('.nickname').text($(this).attr('data-timestamp'));
        });
        $('#chatWindow').on('mouseleave', '.sender', function () {
            $(this).find('.nickname').text($(this).attr('data-sender'));
        });

        $('#userInputSubmit').click(function () {
            $('#userInput').submit();
        });

        let composing = false;
        $('#userInputText').keydown(function (e) {
            if (e.key === 'Tab') {
                e.preventDefault();
                if (!$(this).val()) {
                    return;
                }
                const nickComplete = tabComplete($(this).val());
                if (nickComplete) {
                    $(this).val(nickComplete);
                }
                return;
            }
            if (e.key === 'Enter') {
                e.preventDefault();
                $('#userInput').submit();
                composing = false;
                return;
            }

            // XXX: potentially hacky way to determine if key is a printable character
            if (e.key.length !== 1) {
                return;
            }

            if (!composing) {
                composing = true;

                let destination;
                if (current !== groupChat) {
                    destination = Cryptodog.userFromId(current);
                }
                meta.sendComposing(destination);

                window.setTimeout(function () {
                    meta.sendPaused(destination);
                    composing = false;
                }, 7000);
            }
        });

        $('#userInput').submit(function (e) {
            e.preventDefault();
            const timestamp = new Date(Date.now()).toLocaleTimeString('en-US', { hour12: false });
            const message = $.trim($('#userInputText').val());
            $('#userInputText').val('');
            if (!message.length) {
                return true;
            }

            if (current !== groupChat) {
                const recipient = Cryptodog.userFromId(current);
                meta.sendPrivateTextMessage(recipient, message);
                addPrivateMessage(recipient, Cryptodog.me, timestamp, message);
            } else {
                meta.sendGroupTextMessage(message);
                // TODO: handle missing recipients
                addGroupMessage(Cryptodog.me, timestamp, message);
            }
        });

        function tabComplete(input) {
            const potentials = [];
            for (const user of Cryptodog.allUsers()) {
                potentials.push({
                    score: user.nickname.score(input.match(/(\S)+$/)[0], 0.01),
                    value: user.nickname
                });
            }
            var largest = potentials[0];

            // find item with largest score
            potentials.forEach(function (item) {
                if (item.score > largest.score) {
                    largest = item;
                }
            }, this);

            let suffix;
            if (input.match(/\s/)) {
                suffix = ' ';
            } else {
                suffix = ': ';
            }
            if (largest.score < 0.1) {
                // cut-off matching attempt if all scores are low
                return input;
            }
            return input.replace(/(\S)+$/, largest.value + suffix);
        };
    });

    return {
        timestamp,
        switchTo,
        addJoin,
        addLeave,
        addGroupMessage,
        addPrivateMessage,
        addDecryptError,
        addMissingRecipients,
        reset
    };
}();
<|endoftext|>


<commit_before>document.addEventListener('DOMContentLoaded', function(event) {

    var farmers = [],
        markers = [],
        countries = [],
        farmerLegend = {},
        beatMe = [],
        rotation;

    var currentInfo = document.getElementsByTagName('ul')[0],
        body = document.getElementsByTagName('body')[0];

    var earth = new WE.map('earth_div', {
        sky: true,
        atmosphere: true
    });

    // This line adds support for MapQuest Aerial tiles
    WE.tileLayer('http://otile2.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg', {
        attribution: 'Tiles Courtesy of MapQuest.'
    }).addTo(earth);

    // Start a rotation animation
    rotation = setInterval(function() {
        var c = earth.getPosition();
        if (c[0] && c[1]) {
            earth.setCenter([c[0], c[1] + 0.1]);
        }
    }, 30);

    // Opening and closing the side menu
    document.getElementById('open-side-menu').addEventListener('click', function() {
        body.classList.toggle('open');
    }, false);

    // Maps all farmers to a random color and generates
    // an object of countries. Also keeps track of
    // heartbeat count.
    var generateLegend = function(farmers) {
        countries = [];
        beatMe = [];

        for (var j = 0; j < farmers.length; j++) {
            if (!(farmers[j].id in farmerLegend)) {
                farmerLegend[farmers[j].id] = {
                    color: randomColor(farmers[j].online),
                    beats: farmers[j].heartbeats
                };
            }

            // Add farmer id to beatMe list if heartbeats have incremented.
            if (farmers[j].heartbeats > farmerLegend[farmers[j].id].beats) {
                beatMe.push(farmers[j].id);
                farmerLegend[farmers[j].id].beats = farmers[j].heartbeats;
            }

            // Update country list.
            var country = farmers[j].location.country;
            if (countries.indexOf(country) === -1) {
                countries.push(country);
            }
        }
    }

    // This function sends an ajax get request to fetch
    // a list of farmers and coordinates, then renders
    // the globe.
    var getData = function() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                var data = JSON.parse(xhr.responseText);
                farmers = data.farmers.filter(function(farmer) {
                    return Math.round(farmer.uptime) > 0;
                });
                generateLegend(farmers);
                render();
            }

        }
        xhr.open('GET', 'http://verify.driveshare.org/api/downstream/status/list/by/d/uptime');
        xhr.send();
    }

    // Return window width.
    function getWidth() {
        if (self.innerHeight) {
            return self.innerWidth;
        }

        if (document.documentElement && document.documentElement.clientHeight) {
            return document.documentElement.clientWidth;
        }

        if (document.body) {
            return document.body.clientWidth;
        }
    }

    // Fetch data for the globe once initially,
    // and then every 5 seconds.
    getData();
    setInterval(getData, 5000);

    // This function returns a random color. If node is offline, it will return
    // a random color with 50% opacity.
    var randomColor = function(isOnline) {
        return 'rgba(' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ', ' + (isOnline ? 1 : 0.25) + ')';
    }

    // Returns white or black class depending on which
    // color contrasts more with submitted color.
    var invertColor = function(color) {
        // If color is hex-formatted, convert to RGB
        if (color.indexOf('#') !== -1) {
            var rgbArray = [hexToR(color), hexToG(color), hexToB(color)];
        } else {
            var rgbArray = color.replace('rgb(', '').replace(')', '').split(',');
        }

        // Calculate luminance.
        var L = (299 * rgbArray[0] + 587 * rgbArray[1] + 114 * rgbArray[2]) / 1000;

        // Return black or white, depending on luminance.
        return L > 130 ? 'highlightBlack' : 'highlightWhite';
    }

    // This function handles everything related to updating the HTML
    // with the returned data from the AJAX call:
    var render = function() {
        var entry;
        if (!farmers.length) {

            // If there isn't anybody online
            // Show an appropriate message and empty the summary and the list section
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'block';
            document.getElementsByClassName('.summary')[0].innerHTML = '';
        } else {
            // If there are people online:
            // On every call clear the side-menu list and globe markers and fill them up with new info.
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'none';

            for (var i = 0; i < farmers.length; i++) {
                // Populate the side-menu with the farmers, ranked by uptime.
                var newEntry = document.createElement('li');
                newEntry.setAttribute('id', 'farmer' + (i + 1));
                newEntry.className = 'entry ' + (!farmers[i].online ? 'offline' : '');

                var newColorSpan = document.createElement('span');
                newColorSpan.className = 'color-effect';
                newColorSpan.style.backgroundColor = farmerLegend[farmers[i].id].color;
                newEntry.appendChild(newColorSpan);

                var newDiv = document.createElement('div');
                newDiv.className = 'farmerId';
                newDiv.innerHTML = farmers[i].address;
                newEntry.appendChild(newDiv);

                var newCountSpan = document.createElement('span');
                newCountSpan.className = 'count';
                newCountSpan.innerHTML = Math.round(farmers[i].uptime) + '%';
                newEntry.appendChild(newCountSpan)

                currentInfo.appendChild(newEntry);
            }

            // Summary information
            document.getElementsByClassName('summary')[0].innerHTML = farmers.length + ' farmers / ' + countries.length + ' countries';
        }

        // Adding the markers to the globe.
        // First remove all existing markers from the globe and clear the array they are stored in.
        markers.forEach(function(b) {
            earth.removeMarker(b);
        });
        markers = [];

        // For each ip location create a new marker
        farmers.forEach(function(a) {
            var country = a.location.country;
            var city = a.location.city;
            var state = a.location.state;
            var numContracts = a.contracts;
            var heartbeats = a.heartbeats;
            var testFileSize = a.size;
            var firstBits = a.address.slice(0, 4);

            markers.push(WE.marker([a.location.lat, a.location.lon]).addTo(earth).bindPopup('Farmer ' + firstBits + '… from ' + (city ? city : country) + '' + (state ? ', ' + state + '' : '') + ' has ' + numContracts + ' contract(s) and passed ' + heartbeats + ' heartbeats with ' + testFileSize + '-byte test files.', {
                maxWidth: 150,
                maxHeight: 100,
                closeButton: true
            }));

            // Color the markers.
            var currentMarker = markers[markers.length - 1].element.firstChild;
            currentMarker.style.background = farmerLegend[a.id].color;
            if (beatMe.indexOf(a.id) > -1) {
                currentMarker.classList.add('beat');
            } else if (!a.online) {
                currentMarker.classList.add('offline');
            }
        });

        // Rotate the globe to the desired farmer from the list
        var entries = document.getElementsByClassName('entry');

        for (var i = 0; i < entries.length; i++) {
            entries[i].addEventListener('click', function() {
                if (getWidth() < 800) {
                    body.classList.remove('open');
                }
                clearInterval(rotation);
                var index = this.getAttribute('id').split('farmer')[1] - 1;
                earth.panTo([farmers[index].location.lat, farmers[index].location.lon]);
            });

            entries[i].addEventListener('mouseover', function() {
                this.classList.add(invertColor(this.getElementsByClassName('color-effect')[0].style.backgroundColor));
            });

            entries[i].addEventListener('mouseout', function() {
                this.classList.remove('highlightWhite');
                this.classList.remove('highlightBlack');
            });
        }
    }

    var earthDiv = document.getElementById('earth_div');
    ['click', 'touchstart', 'touchend'].forEach(function(event) {
        earthDiv.addEventListener(event, function() {
            clearInterval(rotation);
            if (getWidth() < 800) {
                body.classList.remove('open');
            }
        });
    });

    // Open the side menu on start up if the app is running on a big screen.
    if (getWidth() > 800) {
        body.classList.add('open');
    } else {
        earth.setZoom(1.8);
    }

});
<commit_msg>Make DOM manipulation more efficient on entries update.<commit_after>document.addEventListener('DOMContentLoaded', function(event) {

    var farmers = [],
        markers = [],
        countries = [],
        farmerLegend = {},
        beatMe = [],
        rotation;

    var currentInfo = document.getElementsByTagName('ul')[0],
        body = document.getElementsByTagName('body')[0];

    var earth = new WE.map('earth_div', {
        sky: true,
        atmosphere: true
    });

    // This line adds support for MapQuest Aerial tiles
    WE.tileLayer('http://otile2.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg', {
        attribution: 'Tiles Courtesy of MapQuest.'
    }).addTo(earth);

    // Start a rotation animation
    rotation = setInterval(function() {
        var c = earth.getPosition();
        if (c[0] && c[1]) {
            earth.setCenter([c[0], c[1] + 0.1]);
        }
    }, 30);

    // Opening and closing the side menu
    document.getElementById('open-side-menu').addEventListener('click', function() {
        body.classList.toggle('open');
    }, false);

    // Maps all farmers to a random color and generates
    // an object of countries. Also keeps track of
    // heartbeat count.
    var generateLegend = function(farmers) {
        countries = [];
        beatMe = [];

        for (var j = 0; j < farmers.length; j++) {
            if (!(farmers[j].id in farmerLegend)) {
                farmerLegend[farmers[j].id] = {
                    color: randomColor(farmers[j].online),
                    beats: farmers[j].heartbeats
                };
            }

            // Add farmer id to beatMe list if heartbeats have incremented.
            if (farmers[j].heartbeats > farmerLegend[farmers[j].id].beats) {
                beatMe.push(farmers[j].id);
                farmerLegend[farmers[j].id].beats = farmers[j].heartbeats;
            }

            // Update country list.
            var country = farmers[j].location.country;
            if (countries.indexOf(country) === -1) {
                countries.push(country);
            }
        }
    }

    // This function sends an ajax get request to fetch
    // a list of farmers and coordinates, then renders
    // the globe.
    var getData = function() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                var data = JSON.parse(xhr.responseText);
                farmers = data.farmers.filter(function(farmer) {
                    return Math.round(farmer.uptime) > 0;
                });
                generateLegend(farmers);
                render();
            }

        }
        xhr.open('GET', 'http://verify.driveshare.org/api/downstream/status/list/by/d/uptime');
        xhr.send();
    }

    // Return window width.
    function getWidth() {
        if (self.innerHeight) {
            return self.innerWidth;
        }

        if (document.documentElement && document.documentElement.clientHeight) {
            return document.documentElement.clientWidth;
        }

        if (document.body) {
            return document.body.clientWidth;
        }
    }

    // Fetch data for the globe once initially,
    // and then every 5 seconds.
    getData();
    setInterval(getData, 5000);

    // This function returns a random color. If node is offline, it will return
    // a random color with 50% opacity.
    var randomColor = function(isOnline) {
        return 'rgba(' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ',' + Math.floor(Math.random() * 256) + ', ' + (isOnline ? 1 : 0.25) + ')';
    }

    // Returns white or black class depending on which
    // color contrasts more with submitted color.
    var invertColor = function(color) {
        // If color is hex-formatted, convert to RGB
        if (color.indexOf('#') !== -1) {
            var rgbArray = [hexToR(color), hexToG(color), hexToB(color)];
        } else {
            var rgbArray = color.replace('rgb(', '').replace(')', '').split(',');
        }

        // Calculate luminance.
        var L = (299 * rgbArray[0] + 587 * rgbArray[1] + 114 * rgbArray[2]) / 1000;

        // Return black or white, depending on luminance.
        return L > 130 ? 'highlightBlack' : 'highlightWhite';
    }

    // This function handles everything related to updating the HTML
    // with the returned data from the AJAX call:
    var render = function() {
        var entry;
        if (!farmers.length) {

            // If there isn't anybody online
            // Show an appropriate message and empty the summary and the list section
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'block';
            document.getElementsByClassName('.summary')[0].innerHTML = '';
        } else {
            // If there are people online:
            // On every call clear the side-menu list and globe markers and fill them up with new info.
            currentInfo.innerHTML = null;
            document.getElementById('no-farmers').style.display = 'none';

            var entriesContainer = document.createDocumentFragment();

            for (var i = 0; i < farmers.length; i++) {
                // Populate the side-menu with the farmers, ranked by uptime.
                var newEntry = document.createElement('li');
                newEntry.setAttribute('id', 'farmer' + (i + 1));
                newEntry.className = 'entry ' + (!farmers[i].online ? 'offline' : '');

                var newColorSpan = document.createElement('span');
                newColorSpan.className = 'color-effect';
                newColorSpan.style.backgroundColor = farmerLegend[farmers[i].id].color;
                newEntry.appendChild(newColorSpan);

                var newDiv = document.createElement('div');
                newDiv.className = 'farmerId';
                newDiv.innerHTML = farmers[i].address;
                newEntry.appendChild(newDiv);

                var newCountSpan = document.createElement('span');
                newCountSpan.className = 'count';
                newCountSpan.innerHTML = Math.round(farmers[i].uptime) + '%';
                newEntry.appendChild(newCountSpan)

                entriesContainer.appendChild(newEntry);
            }

            currentInfo.appendChild(entriesContainer);

            // Summary information
            document.getElementsByClassName('summary')[0].innerHTML = farmers.length + ' farmers / ' + countries.length + ' countries';
        }

        // Adding the markers to the globe.
        // First remove all existing markers from the globe and clear the array they are stored in.
        markers.forEach(function(b) {
            earth.removeMarker(b);
        });
        markers = [];

        // For each ip location create a new marker
        farmers.forEach(function(a) {
            var country = a.location.country;
            var city = a.location.city;
            var state = a.location.state;
            var numContracts = a.contracts;
            var heartbeats = a.heartbeats;
            var testFileSize = a.size;
            var firstBits = a.address.slice(0, 4);

            markers.push(WE.marker([a.location.lat, a.location.lon]).addTo(earth).bindPopup('Farmer ' + firstBits + '… from ' + (city ? city : country) + '' + (state ? ', ' + state + '' : '') + ' has ' + numContracts + ' contract(s) and passed ' + heartbeats + ' heartbeats with ' + testFileSize + '-byte test files.', {
                maxWidth: 150,
                maxHeight: 100,
                closeButton: true
            }));

            // Color the markers.
            var currentMarker = markers[markers.length - 1].element.firstChild;
            currentMarker.style.background = farmerLegend[a.id].color;
            if (beatMe.indexOf(a.id) > -1) {
                currentMarker.classList.add('beat');
            } else if (!a.online) {
                currentMarker.classList.add('offline');
            }
        });

        // Rotate the globe to the desired farmer from the list
        var entries = document.getElementsByClassName('entry');

        for (var i = 0; i < entries.length; i++) {
            entries[i].addEventListener('click', function() {
                if (getWidth() < 800) {
                    body.classList.remove('open');
                }
                clearInterval(rotation);
                var index = this.getAttribute('id').split('farmer')[1] - 1;
                earth.panTo([farmers[index].location.lat, farmers[index].location.lon]);
            });

            entries[i].addEventListener('mouseover', function() {
                this.classList.add(invertColor(this.getElementsByClassName('color-effect')[0].style.backgroundColor));
            });

            entries[i].addEventListener('mouseout', function() {
                this.classList.remove('highlightWhite');
                this.classList.remove('highlightBlack');
            });
        }
    }

    var earthDiv = document.getElementById('earth_div');
    ['click', 'touchstart', 'touchend'].forEach(function(event) {
        earthDiv.addEventListener(event, function() {
            clearInterval(rotation);
            if (getWidth() < 800) {
                body.classList.remove('open');
            }
        });
    });

    // Open the side menu on start up if the app is running on a big screen.
    if (getWidth() > 800) {
        body.classList.add('open');
    } else {
        earth.setZoom(1.8);
    }

});
<|endoftext|>