<commit_before>var tokTypes = require("babel-core").acorn.tokTypes;
var traverse = require("babel-core").traverse;
var t        = require("babel-core").types;

exports.toToken = function (token) {
  var type = token.type;

  if (type === tokTypes.name) {
    token.type = "Identifier";
  } else if (type === tokTypes.semi || type === tokTypes.comma ||
             type === tokTypes.parenL || type === tokTypes.parenR ||
             type === tokTypes.braceL || type === tokTypes.braceR ||
             type === tokTypes.slash || type === tokTypes.dot ||
             type === tokTypes.bracketL || type === tokTypes.bracketR ||
             type === tokTypes.ellipsis || type === tokTypes.arrow ||
             type === tokTypes.star ||
             type.isAssign) {
    token.type = "Punctuator";
    if (!token.value) token.value = type.type;
  } else if (type === tokTypes.jsxTagStart) {
    token.type = "Punctuator";
    token.value = "<";
  } else if (type === tokTypes.jsxTagEnd) {
    token.type = "Punctuator";
    token.value = ">";
  } else if (type === tokTypes.jsxName) {
    token.type = "JSXIdentifier";
  } else if (type.keyword) {
    token.type = "Keyword";
  } else if (type === tokTypes.num) {
    token.type = "Numeric";
    token.value = String(token.value);
  } else if (type === tokTypes.string) {
    token.type = "String";
    token.value = JSON.stringify(token.value);
  }

  return token;
};

exports.toAST = function (ast) {
  traverse(ast, astTransformVisitor);
};

function isCompatTag(tagName) {
  return tagName && /^[a-z]|\-/.test(tagName);
}

var astTransformVisitor = {
  noScope: true,
  exit: function (node, parent) {
    if (t.isSpreadProperty(node)) {
      node.type = "Property";
      node.kind = "init";
      node.computed = true;
      node.key = node.value = node.argument;
      delete node.argument;
    }

    if (t.isRestElement(node)) {
      return node.argument;
    }

    // playground

    if (t.isAssignmentExpression(node)) {
      if (node.operator === "||=" || node.operator === "?=") {
        node.operator = "+=";
      }
    }

    // modules

    if (t.isImportDeclaration(node)) {
      delete node.isType;
    }

    if (t.isExportDeclaration(node)) {
      if (node.default) {
        delete node.specifiers;
        delete node.source;
        node.type = "ExportDefaultDeclaration";
        if (node.declaration.type === "FunctionExpression") {
          node.declaration.type = "FunctionDeclaration";
        } else if (node.declaration.type === "ClassExpression") {
          node.declaration.type = "ClassDeclaration";
        }
      } else if (t.isExportBatchSpecifier(node.specifiers[0])) {
        node.type = "ExportAllDeclaration";
        delete node.specifiers;
        delete node.declaration;
      } else {
        node.type = "ExportNamedDeclaration";
      }
      delete node.default;
    }

    if (t.isExportSpecifier(node)) {
      node.local = node.id;
      node.exported = node.name || node.id;
      delete node.id;
      delete node.name;
    }

    if (t.isImportSpecifier(node)) {
      node.local = node.name || node.id;
      if (node.default) {
        node.type = "ImportDefaultSpecifier";
      } else {
        node.imported = node.id;
      }
      delete node.id;
      delete node.name;
      delete node.default;
    }

    if (t.isImportBatchSpecifier(node)) {
      // ImportBatchSpecifier<name> => ImportNamespaceSpecifier<id>
      node.type = "ImportNamespaceSpecifier";
      node.local = node.name;
      delete node.name;
    }

    // classes

    if (t.isReferencedIdentifier(node, parent, { name: "super" })) {
      return t.inherits(t.thisExpression(), node);
    }

    if (t.isClassProperty(node)) {
      // eslint doesn't like these
      this.remove();
    }

    // functions

    if (t.isFunction(node)) {
      node.defaults = [];
      node.params = node.params.map(function (param) {
        if (t.isAssignmentPattern(param)) {
          node.defaults.push(param.right);
          return param.left;
        } else {
          node.defaults.push(null);
          return param;
        }
      });

      node.rest = null;
      if (node.async) node.generator = true;
      delete node.async;
    }

    if (t.isAwaitExpression(node)) {
      node.type = "YieldExpression";
      node.delegate = node.all;
      delete node.all;
    }
  }
};
<commit_msg>acorn-to-esprima: fix ExportNamedDeclaration, closes #48<commit_after>var tokTypes = require("babel-core").acorn.tokTypes;
var traverse = require("babel-core").traverse;
var t        = require("babel-core").types;

exports.toToken = function (token) {
  var type = token.type;

  if (type === tokTypes.name) {
    token.type = "Identifier";
  } else if (type === tokTypes.semi || type === tokTypes.comma ||
             type === tokTypes.parenL || type === tokTypes.parenR ||
             type === tokTypes.braceL || type === tokTypes.braceR ||
             type === tokTypes.slash || type === tokTypes.dot ||
             type === tokTypes.bracketL || type === tokTypes.bracketR ||
             type === tokTypes.ellipsis || type === tokTypes.arrow ||
             type === tokTypes.star ||
             type.isAssign) {
    token.type = "Punctuator";
    if (!token.value) token.value = type.type;
  } else if (type === tokTypes.jsxTagStart) {
    token.type = "Punctuator";
    token.value = "<";
  } else if (type === tokTypes.jsxTagEnd) {
    token.type = "Punctuator";
    token.value = ">";
  } else if (type === tokTypes.jsxName) {
    token.type = "JSXIdentifier";
  } else if (type.keyword) {
    token.type = "Keyword";
  } else if (type === tokTypes.num) {
    token.type = "Numeric";
    token.value = String(token.value);
  } else if (type === tokTypes.string) {
    token.type = "String";
    token.value = JSON.stringify(token.value);
  }

  return token;
};

exports.toAST = function (ast) {
  traverse(ast, astTransformVisitor);
};

function isCompatTag(tagName) {
  return tagName && /^[a-z]|\-/.test(tagName);
}

var astTransformVisitor = {
  noScope: true,
  exit: function (node, parent) {
    if (t.isSpreadProperty(node)) {
      node.type = "Property";
      node.kind = "init";
      node.computed = true;
      node.key = node.value = node.argument;
      delete node.argument;
    }

    if (t.isRestElement(node)) {
      return node.argument;
    }

    // playground

    if (t.isAssignmentExpression(node)) {
      if (node.operator === "||=" || node.operator === "?=") {
        node.operator = "+=";
      }
    }

    // modules

    if (t.isImportDeclaration(node)) {
      delete node.isType;
    }

    if (t.isExportDeclaration(node)) {
      if (node.default) {
        delete node.specifiers;
        delete node.source;
        node.type = "ExportDefaultDeclaration";
        if (node.declaration.type === "FunctionExpression") {
          node.declaration.type = "FunctionDeclaration";
        } else if (node.declaration.type === "ClassExpression") {
          node.declaration.type = "ClassDeclaration";
        }
      } else if (node.specifiers && t.isExportBatchSpecifier(node.specifiers[0])) {
        node.type = "ExportAllDeclaration";
        delete node.specifiers;
        delete node.declaration;
      } else {
        node.type = "ExportNamedDeclaration";
      }
      delete node.default;
    }

    if (t.isExportSpecifier(node)) {
      node.local = node.id;
      node.exported = node.name || node.id;
      delete node.id;
      delete node.name;
    }

    if (t.isImportSpecifier(node)) {
      node.local = node.name || node.id;
      if (node.default) {
        node.type = "ImportDefaultSpecifier";
      } else {
        node.imported = node.id;
      }
      delete node.id;
      delete node.name;
      delete node.default;
    }

    if (t.isImportBatchSpecifier(node)) {
      // ImportBatchSpecifier<name> => ImportNamespaceSpecifier<id>
      node.type = "ImportNamespaceSpecifier";
      node.local = node.name;
      delete node.name;
    }

    // classes

    if (t.isReferencedIdentifier(node, parent, { name: "super" })) {
      return t.inherits(t.thisExpression(), node);
    }

    if (t.isClassProperty(node)) {
      // eslint doesn't like these
      this.remove();
    }

    // functions

    if (t.isFunction(node)) {
      node.defaults = [];
      node.params = node.params.map(function (param) {
        if (t.isAssignmentPattern(param)) {
          node.defaults.push(param.right);
          return param.left;
        } else {
          node.defaults.push(null);
          return param;
        }
      });

      node.rest = null;
      if (node.async) node.generator = true;
      delete node.async;
    }

    if (t.isAwaitExpression(node)) {
      node.type = "YieldExpression";
      node.delegate = node.all;
      delete node.all;
    }
  }
};
<|endoftext|>


<commit_before>var addChild = function (arr,child,parent,toend,cutoff)
{
	arr = arr === undefined ? [] : arr;
	toend = toend === undefined ? false : toend;
	parent = parent === undefined ? arr.length - 1 : parent ;
	var res = arr.slice(0,parent+1);
	var rest = arr.slice(parent+1);
	if (toend){
		while (rest.length>0 && rest[0].parent>=parent) {
			var target = rest.shift();
			res.push(target);
		}
	}
	var offset = res.length;
	if (cutoff) cutoff(offset);
	for (var c in child) {
		var target=[];
		for (var key in child[c]) target[key] = child[c][key];
		if (target.parent==-1) target.parent = parent;
		else target.parent += offset;
		res.push(target);
	}
	for (var c in rest) {
		var target=[];
		for (var key in rest[c]) target[key] = child[c][key];
		target.parent += target.parent > parent ? child.length : 0;
		res.push(target);
	}
	return res;
}

var emmet = function (stremmet,parent)
{
	var res = [];
	var openbranch = 0;
	var multi = '';
	var temp = '';
	var brackets = '';
	var targetText = 0; // tag
	parent = parent === undefined ? -1 : parent;
	var current = []; //['',{},parent];
	current.name = '';
	current.attrs = {};
	current.parent = parent;
	var flush = function(pos)
	{
		var resolve$ = function (from,nstr) {
			var newTag = []; //[from[0],{},from[2]];
			newTag.attrs = {};
			for (var key in from) { if(key!='attrs') newTag[key] = from[key]; }
			for (var key in from.attrs) {
				var val = from.attrs[key];
				var dollars = val.match(/[\$]+/);
				if (dollars) for (var j = 0; j < dollars.length; j++) {
					var pad = '';
					for (var k=0; k< dollars[j].length - nstr.length ; k++) {pad += '0';}
					val = val.replace(dollars[j],pad+nstr);
				}
				newTag.attrs[key] = val;
			}
			return newTag;
		}
		var children = null;
		if (current.children) {
			children = current.children;
			delete current.children;
		};
		if (multi.length>0) {
			var m = parseInt(multi);
			if(!isNaN(m)) {
				for (var i = 0; i < m; i++) {
					var nstr = (i+1).toString();
					if (current.name.length>0) res.push(resolve$(current,nstr));
					else if (children) {
						var childnodes = [];
						for (var j = 0; j < children.length; j++) {
							childnodes.push(resolve$(children[j],nstr));
						};
						res = addChild(res,childnodes,parent,true)
					}
					if (stremmet.length > pos && stremmet[pos]=='>') res = addChild(res,emmet(stremmet.substring(pos+1)));
				}
			}
		} else if (current.name.length>0){
			res.push(current);
		} else if (children) res = addChild(res,children,parent,true);
		targetText = 0;
		multi = '';
		brackets = '';
		current = [] //'',{},parent];
		current.name = '';
		current.attrs = {};
		current.parent = parent;
	}
	var addChar = function(ch) // can move () here
	{
		switch (targetText) {
			case 0: //tag
			current.name += ch;
			break;
			case 1: //class
			if (!current.attrs['class']) current.attrs['class']='';
			current.attrs['class'] +=ch;
			break;
			case 2: //id
			if (!current.attrs['id']) current.attrs['id']='';
			current.attrs['id'] +=ch;
			break;
			case 3: //args
			brackets += ch;
			break;
			case 4: //multi
			multi += ch;
			break;
			default:
		}
	}
	var readbrackets = function ()
	{
		var key = '';
		var val = '';
		var openquote = false;
		var slashed = false;
		var valactive = false;
		for (var i = 0; i < brackets.length; i++) {
			var b = brackets[i];
			if (!openquote && b==' ' && key.length > 0) {
				current.attrs[key] = val;
				key='';
				val='';
				valactive = false;
			} else if (b=='"' && !slashed) {
				openquote = !openquote;
			} else if (valactive) {
				val += b;
			} else if (!openquote && b=='=') {
				valactive = true;
			} else {
				key += b;
			}
			if (b=='\\') slashed = !slashed;
			else slashed = false;
		}
		if (key.length > 0) {
			current.attrs[key] = val;
			key='';
			val='';
			valactive = false;
		}
	}

	// parentesies
	for (var i=0; i<stremmet.length ; i++)
	{
		var cc = stremmet[i];
		if (cc=='(') {
			openbranch ++;
			if (openbranch>1) {
				temp += '(';
			}
		} else if (cc==')') {
			openbranch --;
			if (openbranch == 0) {
				//res = addChild(res,emmet(temp),parent,true); // res.concat(emmet(temp,parent));
				current.children = emmet(temp);
				temp = ''; //clear temp
			} else if (openbranch == -1) {
				//bad string
			} else if (openbranch>0) {
				temp += ')';
			}
		} else if (openbranch > 0) temp += cc ;
		else {

			if (cc==']') {
				targetText = 0;
				readbrackets();
				brackets = '';
			} else if (targetText == 3) {
				addChar(cc);
			} else if (cc=='[') {
				targetText = 3;
			} else if (cc == '+') {
				flush(i);
			} else if (cc == '>') {
				if (multi.length>0) {
					flush(i);
					return res;
				}
				flush(i);
				parent=res.length-1;
				current.parent=parent;
			} else if (cc == '.') {
				targetText = 1;
				if (current.attrs['class']) addChar(' ');
			} else if (cc == '#') {
				targetText = 2;
			} else if (cc == '*') {
				targetText = 4;
			} else {
				addChar(cc);
			}
		}
	}
	flush(stremmet.length);
	return res;
}

var render = function (arr)
{
	var res = '';
	var tail = [];
	var special={'!--':'-->'};
	var singletons=['!doctype','area','base','br','col','command','embed','hr','img','input','link','meta','param','source'];
	var urlAttrs=['src','href'];
	var quotedAttr = function (attr) {
		var val = tag.sttrs[attr];
		if (val.indexOf('"')) {
			if (urlAttrs.indexOf(attr)>-1) return val.replace(/\"/g,'%22');
			else return val.replace(/\"/g,'"');
		} else return val;
	}
	var handleTag = function (tag) {
		res += '<' + tag.name ;
		for (var arg in tag.attrs){
			if (typeof(tag.attrs[arg])=='boolean' && tag.attrs[arg]) res += ' ' + arg;
			else res += ' ' + arg + '="' + tag.attrs[arg] + '"';
		}
		if (singletons.indexOf(tag.name)>-1) {
			res += ' />';
			if (tag.textAfter) {
				res += tag.textAfter;
			}
		} else if (special[tag.name]) {
			var after ='';
			if (tag.text) res += tag.text;
			if (tag.textAfter) after = tag.textAfter;
			res += special[tag.name] + after;
		} else {
			res += '>';
			var after ='';
			if (tag.text) {
				res += tag.text;
				if (tag.textAfter) after = tag.textAfter;
			}
			tail.unshift(['</' + tag.name + '>'+after,tag.parent]);
		}
	}
	// var lparent = arr.name.parent;
	for( var i=0;i<arr.length;i++){
		var line = arr[i];
		var cparent = line.parent;
		// if(tail.length > 0 && lparent >= cparent){}
		while (tail.length > 0 && tail[0][1] >= cparent) res += tail.shift().shift();
		handleTag(line);
		// lparent = cparent;
	}
	while (tail.length > 0) res += tail.shift().shift();
	return res;
}

var domobj = function (flatdom)
{
	var res = {'flat':flatdom,'classes':{},'ids':{},'tags':{}};
	for (var i = 0; i < flatdom.length; i++) {
		var line=flatdom[i];
		var tag =line.name;
		var id = line.attrs['id']===undefined ? null : line.attrs['id'];
		var classes = line.attrs['class']===undefined ? [] : line.attrs['class'].split(' ');
		if (tag){
			if (res.tags[tag]) res.tags[tag].push(i);
			else res.tags[tag] = [i];
			if (id) res.ids[id]=i;
			while (classes.length>0){
				className = classes.pop();
				if (res.classes[className]) res.classes[className].push(i);
				else res.classes[className] = [i];
			}
		}
	}
	return res;
}

var select = require('./dom.select.js').sel;

var handle = function(dom,mobj){
	var flat = dom.flat;
	var sel = mobj.sel ? mobj.sel : (mobj.target ? select(dom,mobj.target) : []);
	if(!mobj || sel.length == 0) return dom;
	switch (mobj.type) {
		case 'add':
		var nodes = null;
		if(mobj.emmet) nodes = emmet(mobj.emmet);
		else if(mobj.flat) nodes = mobj.flat ;
		if(nodes) {
			for (var i = 0; i < sel.length; i++) {
				var offset = i*nodes.length;
				flat = addChild(flat,nodes,sel[i]+offset,true);
			}
			return domobj(flat);
		}
		break;
		case 'set':
		if(mobj.attr) {
			for (var i = 0; i < sel.length; i++) {
				for (var key in mobj.attr) {
					flat[sel[i]][1][key] = mobj.attr[key];
				}
			}
		}
		if(mobj.text) {
			for (var i = 0; i < sel.length; i++) {
				flat[sel[i]].text = mobj.text;
			}
		}
		break;
	}
	if(mobj.rebuild) return domobj(flat);
	else {
		dom.flat = flat;
		return dom;
	}
}

var domObject = function () {
	var len=arguments.length;
}

domObject.prototype.type = "Basic.Dom";

//exports.sampleemmet='html>(head>link[rel]+(b>link.boo$[href]*1)+script)+(body>(div.out.gooz[a=1 b=" l "]>(div#in>img[src="boo.jpg"]+qq.boo#soo.too[ad="\\"\\"\\b"])+(div.another$$*3+(div.another$$$$*5>img#t$$*2+br*3)+div.another+(p.o+p.b)))+p#text)';
//exports.samplehtml=[['html',{},-1],['head',{},0],['body',{},0],['div',{class:'.out'},2],['div',{id:'#in'},3],['$plain',{text:'yoyo'},2]];
exports.hand=handle;
exports.zen=emmet;
exports.select=select;
exports.dom=domobj;
exports.rndr=render;
exports.addchild=addChild;<commit_msg>branch-cleanup<commit_after><|endoftext|>


<commit_before>"use strict";

var $ = require('../jquery')
  , Backbone = require('../backbone')
  , Cocktail = require('backbone.cocktail')
  , RelatedTopicsView = require('./related_topics')
  , ZoteroDataView = require('./edit_zotero')
  , ScanListView = require('./scan_list')
  , SaveItemMixin = require('./generic/save_item_mixin')
  , DocumentView

module.exports = DocumentView = Backbone.View.extend({
  events: {
    'click .js-edit-manually': 'editCitationManually',
    'click .js-generate-citation': 'generateCitation'
  },

  initialize: function () {
    var that = this;

    this.topicListView = new RelatedTopicsView({ collection: that.model.relatedTopics });
    this.scanListView = new ScanListView({ collection: that.model.scans });
    this.zoteroView = new ZoteroDataView({ zoteroData: that.model.get('zotero_data') });

    this.listenTo(this.zoteroView, 'updatedZoteroData', function (data) {
      that.model.set('zotero_data', data);
    });

    this.listenToOnce(this.zoteroView, 'rendered', function () {
      if (
        that.model.isNew() ||
        $(that.model.get('description')).text() === that.zoteroView.updateZoteroData().citation
      ) {
        this.generateCitation();
      } else {
        this.editCitationManually();
      }
    });

    this.render();
  },

  render: function () {
    var that = this
      , template = require('../templates/document.html')

    this.$el.html( template({ doc: that.model }));
    this.topicListView.$el.appendTo( that.$('#document-related-topics') );
    this.zoteroView.$el.appendTo( that.$('#document-zotero-data') );

    if (!this.model.isNew()) {
      this.scanListView.$el.appendTo( that.$('#document-scans') );
    }

    this.$citationEditToggle = this.$('#document-citation button');
  },

  editCitationManually: function () {
    var that = this;

    this.$citationEditToggle.show()
      .removeClass('js-edit-manually')
      .addClass('js-generate-citation')
      .text('Generate from metadata')

    this.stopListening(this.zoteroView, 'updatedCitation');
    this.$('#printed-citation')
      .on('editor:input', function (e, val) {
        that.model.set('description', val);
      })
      .editText({
        initialValue: that.model.get('description'),
        toolbarType: 'minimal',
        height: 50
      });

    return false;
  },

  generateCitation: function () {
    var that = this
      , citation = this.$('#printed-citation')

    if ('editor' in citation.data()) {
      this.$('#printed-citation')
        .off('editor:input')
        .editText('destroy');
    }

    this.$citationEditToggle.show()
      .removeClass('js-generate-citation')
      .addClass('js-edit-manually')
      .text('Edit manually')

    this.listenTo(this.zoteroView, 'updatedCitation', function (data) {
      if (data) {
        that.$('#printed-citation').html(data);
      } else {
        that.$('#printed-citation').html(
          'Enter document metadata to generate a citation');
      }
      that.model.set('description', data);
    });

    this.zoteroView.updateZoteroData();

    return false;
  }
});

Cocktail.mixin(DocumentView, SaveItemMixin);
<commit_msg>Set zotero_data to null, not blank object if empty<commit_after>"use strict";

var _ = require('underscore')
  , $ = require('../jquery')
  , Backbone = require('../backbone')
  , Cocktail = require('backbone.cocktail')
  , RelatedTopicsView = require('./related_topics')
  , ZoteroDataView = require('./edit_zotero')
  , ScanListView = require('./scan_list')
  , SaveItemMixin = require('./generic/save_item_mixin')
  , DocumentView

module.exports = DocumentView = Backbone.View.extend({
  events: {
    'click .js-edit-manually': 'editCitationManually',
    'click .js-generate-citation': 'generateCitation'
  },

  initialize: function () {
    var that = this;

    this.topicListView = new RelatedTopicsView({ collection: that.model.relatedTopics });
    this.scanListView = new ScanListView({ collection: that.model.scans });
    this.zoteroView = new ZoteroDataView({ zoteroData: that.model.get('zotero_data') });

    this.listenTo(this.zoteroView, 'updatedZoteroData', function (data) {
      data = _.isEmpty(data) ? null : data;
      that.model.set('zotero_data', data);
    });

    this.listenToOnce(this.zoteroView, 'rendered', function () {
      if (
        that.model.isNew() ||
        $(that.model.get('description')).text() === that.zoteroView.updateZoteroData().citation
      ) {
        this.generateCitation();
      } else {
        this.editCitationManually();
      }
    });

    this.render();
  },

  render: function () {
    var that = this
      , template = require('../templates/document.html')

    this.$el.html( template({ doc: that.model }));
    this.topicListView.$el.appendTo( that.$('#document-related-topics') );
    this.zoteroView.$el.appendTo( that.$('#document-zotero-data') );

    if (!this.model.isNew()) {
      this.scanListView.$el.appendTo( that.$('#document-scans') );
    }

    this.$citationEditToggle = this.$('#document-citation button');
  },

  editCitationManually: function () {
    var that = this;

    this.$citationEditToggle.show()
      .removeClass('js-edit-manually')
      .addClass('js-generate-citation')
      .text('Generate from metadata')

    this.stopListening(this.zoteroView, 'updatedCitation');
    this.$('#printed-citation')
      .on('editor:input', function (e, val) {
        that.model.set('description', val);
      })
      .editText({
        initialValue: that.model.get('description'),
        toolbarType: 'minimal',
        height: 50
      });

    return false;
  },

  generateCitation: function () {
    var that = this
      , citation = this.$('#printed-citation')

    if ('editor' in citation.data()) {
      this.$('#printed-citation')
        .off('editor:input')
        .editText('destroy');
    }

    this.$citationEditToggle.show()
      .removeClass('js-generate-citation')
      .addClass('js-edit-manually')
      .text('Edit manually')

    this.listenTo(this.zoteroView, 'updatedCitation', function (data) {
      if (data) {
        that.$('#printed-citation').html(data);
      } else {
        that.$('#printed-citation').html(
          'Enter document metadata to generate a citation');
      }
      that.model.set('description', data);
    });

    this.zoteroView.updateZoteroData();

    return false;
  }
});

Cocktail.mixin(DocumentView, SaveItemMixin);
<|endoftext|>


<commit_before>/***************************************************************************
 * mainView.js : UI control implementation for main-view.html
 ***************************************************************************/

/*
 * Authors:
 *  Jose Dapena Paz <jdapena@igalia.com>
 *
 * Copyright (c) 2012, Igalia, S.L.
 * All rights reserved.
 *
 * See license.js
 */

function fillComposerFrom (accounts)
{
    $("#composer-from-choice").html("");
    for (i in accounts) {
	dumpAccountOptionInComposerFrom (accounts[i], "#composer-from-choice");
    }
}

function fillAccountsList (accounts)
{
    clearAccountsList ();

    for (i in accounts) {
	dumpAccountInAccountsList (accounts[i], "#page-accounts #accounts-list");
    }
    dumpAddAccountInAccountsList ("#page-accounts #accounts-list");

    $("#page-accounts #accounts-list").listview('refresh');
}

function fillAccountsListCounts ()
{
    $("#page-accounts #accounts-list").listview();

    for (i in globalStatus.folders) {
	account = globalStatus.folders[i];
	accountId = account.accountId;
	inbox = account.folders["INBOX"];
	if (!inbox)
	    inbox = account.folders["inbox"];
	accountItemId = "account-item-"+accountId;
	$("#page-accounts #account-item-"+accountId+" .account-count").text(inbox.unreadCount);
	if (inbox && inbox.unreadCount > 0) {
	    $("#page-accounts #account-item-"+accountId+" .account-count").show();
	} else {
	    $("#page-accounts #account-item-"+accountId+" .account-count").hide();
	}
    }
    $("#page-accounts #accounts-list").listview('refresh');
}

function fillFoldersList(accountId)
{
    for (i in globalStatus.folders) {
	account = globalStatus.folders[i];
	if (account.accountId == accountId) {
	    $("#page-folders-title").text(account.accountName + " folders");

	    $("#page-folders #folders-list").html("");
	    for (fullName in account.folders) {

		folder = account.folders[fullName];
		dumpFolderInFolderList (accountId, fullName, folder,
					"#page-folders #folders-list");

		if (fullName == "INBOX") {
		    accountItemId = "account-item-"+accountId;
		    $("#page-accounts #"+accountItemId+" .countSpan:first").text(folder.unreadCount);
		    if (folder.unreadCount > 0)
			$("#page-accounts #"+accountItemId+" .countSpan:first").show();
		    else
			$("#page-accounts #"+accountItemId+" .countSpan:first").hide();
		}
	    }
	    $("#page-folders #folders-list").listview('refresh');

	    break;
	}
    }
}

function fillMessageViewHeader (message)
{
    dumpMessageAsViewHeader (message, "#page-message");
}

function fillMessageViewBody (message)
{
    globalStatus.messageStructure = message;
    dumpDataWrapper (message, "#page-message #iframe-container");
}

function clearMessageViewBody ()
{
    $("#page-message #iframe-container").text("");
}

function clearAccountsList ()
{
    $("#accounts-list").html("");
}

function abortShowMessages ()
{
    if ('showMessages' in globalStatus.requests) {
	globalStatus.requests["showMessages"].abort();
    }
}

function markMessageAsRead(uid)
{
    iwkRequest ("flagMessage", "Marking message as read", {
	account: globalStatus.currentAccount,
	folder: globalStatus.currentFolder,
	message: uid,
	setFlags: "seen"
    }).done(function (msg) {
	$("#message-item-"+uid).removeClass("iwk-unread-item");
	$("#message-item-"+uid).addClass("iwk-read-item");
	$("#message-view-mark-as-read").hide();
	$("#message-view-mark-as-unread").show()
    });
}

function markMessageAsUnread(uid)
{
    iwkRequest ("flagMessage", "Marking message as unread", {
	account: globalStatus.currentAccount,
	folder: globalStatus.currentFolder,
	message: uid,
	unsetFlags: "seen"
    }).done(function (msg) {
	$("#message-item-"+uid).removeClass("iwk-read-item");
	$("#message-item-"+uid).addClass("iwk-unread-item");
	$("#message-view-mark-as-unread").hide();
	$("#message-view-mark-as-read").show();
    });
}

function markAsRead ()
{
    markMessageAsRead (globalStatus.currentMessage);
}

function markAsUnread ()
{
    markMessageAsUnread (globalStatus.currentMessage);
}

function reply ()
{
    console.log(globalStatus.messageStructure);
    console.log (JSON.stringify(globalStatus.messageStructure, undefined, 4));
    bodies = dataWrapperGetBodies (globalStatus.messageStructure)
    console.log (JSON.stringify(bodies));
    $.mobile.changePage("#composer");

    // We should somehow concatenate the bodies. Now we simply use the first body we find.
    if (bodies.length > 0) {
	$.ajax({
	    url: bodies[0].uri,
	    dataType: 'jsonp',
	}).done(function(result) {
	    body = "\n-- Original message --\n"+result.data;
	    $("#composer-body").text(body);
	}).error(function (jqXHR, textStatus, errorThrown) {
	    console.log(JSON.stringify (errorThrown));
	}).complete(function (jqXHR, textStatus) {
	    console.log(textStatus);
	});
    }
}

function showMessage(message)
{
    globalStatus.messageStructure = null;
    clearMessageViewBody ();
    fillMessageViewHeader (message);
    if ('getMessage' in globalStatus.requests) {
	globalStatus.requests['getMessage'].abort();
    }

    globalStatus.requests["getMessage"] = iwkRequest ("getMessage", "Getting message", {
	account: globalStatus.currentAccount,
	folder: globalStatus.currentFolder,
	message: message.uid
    }).done(function (msg) {
	fillMessageViewBody (msg.result);
	markMessageAsRead (message.uid);
    }).always(function(jqXHR, textStatus, errorThrown) {
	if ('getMessage' in globalStatus.requests)
	    delete globalStatus.requests["getMessage"];
    });
}

function showNextMessage()
{
    current = null;
    $("#messages-list .iwk-message-item-link").each (function (index) {
	if (current != null) {
	    globalStatus.currentMessage = this.messageUid;
	    showMessage (this.message);
	    return false;
	}
	if (this.messageUid == globalStatus.currentMessage)
	    current = this;
    });
    $("#message-view-next-button").removeClass("ui-btn-active");
    return true;
}

function showPreviousMessage ()
{
    previous = null;
    $("#messages-list .iwk-message-item-link").each (function (index) {
	if (this.messageUid == globalStatus.currentMessage) {
	    if (previous) {
		globalStatus.currentMessage = previous.messageUid;
		showMessage (previous.message);
	    }
	    return false;
	}
	previous = this;
    });
    $("#message-view-previous-button").removeClass("ui-btn-active");
    return true;
}

function showMessages(accountId, folderId, onlyNew)
{
    try {
	if ('showMessages' in globalStatus.requests) {
	    globalStatus.requests["showMessages"].abort();
	}

	$("#messages-list-get-more-list").hide();
	$("#messages-list-getting-more-list").show();
	$("#messages-refresh").hide();
	$("#messages-cancel").show();

	retrieveCount = onlyNew?0:SHOW_MESSAGES_COUNT;
	globalStatus.requests["showMessages"] = iwkRequest ("getMessages", "Fetching messages", {
	    account: accountId,
	    folder: folderId,
	    newestUid: globalStatus.newestUid,
	    oldestUid: globalStatus.oldestUid,
	    count: retrieveCount
	}).done(function (msg) {
	    if (msg.newMessages.length > 0) {
		globalStatus.newestUid = msg.newMessages[0].uid;
	    }
	    if (globalStatus.newestUid == null && msg.messages.length > 0) {
		globalStatus.newestUid = msg.messages[0].uid;
	    }
	    if (msg.messages.length > 0) {
		globalStatus.oldestUid = msg.messages[msg.messages.length - 1].uid;
	    }
	    msg.newMessages.reverse();
	    for (i in msg.newMessages) {
		dumpMessageInMessagesList (msg.newMessages[i], true, "#page-messages #messages-list");
	    }
	    for (i in msg.messages) {
		dumpMessageInMessagesList (msg.messages[i], false, "#page-messages #messages-list");
	    }
	    $("#page-messages #messages-list").listview('refresh');
	}).always(function(jqXHR, textStatus, errorThrown) {
	    if ('showMessages' in globalStatus.requests)
		delete globalStatus.requests["showMessages"];
	    $("#messages-list-get-more-list").show();
	    $("#messages-list-getting-more-list").hide();
	    $("#messages-refresh").show();
	    $("#messages-cancel").hide();
	});
    } catch (e) {
	console.log(e.message);
    }
}

function fetchMoreMessages ()
{
    showMessages (globalStatus.currentAccount, globalStatus.currentFolder, false);
}

function fetchNewMessages ()
{
    showMessages (globalStatus.currentAccount, globalStatus.currentFolder, true);
}

function refreshAccounts ()
{
    iwkRequest ("getAccounts", "Updating accounts", {
    }).done(function (msg) {
	fillComposerFrom (msg.result);
	fillAccountsList (msg.result);
	syncFolders();
    });
}

function syncFolders ()
{
    iwkRequest("syncFolders", "Updating accounts and folders", {
    }).done(function (msg) {
	globalSetFolders (msg.result);
	fillAccountsListCounts ();
	fillFoldersList(globalStatus.currentAccount);
    });
}

function composerSend (data)
{
    iwkRequest ("composerSend", "Adding to outbox", {
	formData: data
    }).done(function (msg) {
	if (!msg.is_ok)
	    showError (msg.error);
	else
	    syncFolders();
    });
}

$(function () {
    $("#composer-send").click(function () {
	$("#form-composer").submit();
    });

    $("#form-composer").submit(function () {
	composerSend($(this).serialize());
	return false;
    });

    $("#accounts-compose").click(function () {
	clearForm($('#form-composer'));
	return true;
    });

    refreshAccounts();
});
<commit_msg>Small fix for first retrieval.<commit_after>/***************************************************************************
 * mainView.js : UI control implementation for main-view.html
 ***************************************************************************/

/*
 * Authors:
 *  Jose Dapena Paz <jdapena@igalia.com>
 *
 * Copyright (c) 2012, Igalia, S.L.
 * All rights reserved.
 *
 * See license.js
 */

function fillComposerFrom (accounts)
{
    $("#composer-from-choice").html("");
    for (i in accounts) {
	dumpAccountOptionInComposerFrom (accounts[i], "#composer-from-choice");
    }
}

function fillAccountsList (accounts)
{
    clearAccountsList ();

    for (i in accounts) {
	dumpAccountInAccountsList (accounts[i], "#page-accounts #accounts-list");
    }
    dumpAddAccountInAccountsList ("#page-accounts #accounts-list");

    $("#page-accounts #accounts-list").listview('refresh');
}

function fillAccountsListCounts ()
{
    $("#page-accounts #accounts-list").listview();

    for (i in globalStatus.folders) {
	account = globalStatus.folders[i];
	accountId = account.accountId;
	inbox = account.folders["INBOX"];
	if (!inbox)
	    inbox = account.folders["inbox"];
	accountItemId = "account-item-"+accountId;
	if (inbox && inbox.unreadCount && inbox.unreadCount > 0) {
	    $("#page-accounts #account-item-"+accountId+" .account-count").text(inbox.unreadCount);
	    $("#page-accounts #account-item-"+accountId+" .account-count").show();
	} else {
	    $("#page-accounts #account-item-"+accountId+" .account-count").hide();
	}
    }
    $("#page-accounts #accounts-list").listview('refresh');
}

function fillFoldersList(accountId)
{
    for (i in globalStatus.folders) {
	account = globalStatus.folders[i];
	if (account.accountId == accountId) {
	    $("#page-folders-title").text(account.accountName + " folders");

	    $("#page-folders #folders-list").html("");
	    for (fullName in account.folders) {

		folder = account.folders[fullName];
		dumpFolderInFolderList (accountId, fullName, folder,
					"#page-folders #folders-list");

		if (fullName == "INBOX") {
		    accountItemId = "account-item-"+accountId;
		    $("#page-accounts #"+accountItemId+" .countSpan:first").text(folder.unreadCount);
		    if (folder.unreadCount > 0)
			$("#page-accounts #"+accountItemId+" .countSpan:first").show();
		    else
			$("#page-accounts #"+accountItemId+" .countSpan:first").hide();
		}
	    }
	    $("#page-folders #folders-list").listview('refresh');

	    break;
	}
    }
}

function fillMessageViewHeader (message)
{
    dumpMessageAsViewHeader (message, "#page-message");
}

function fillMessageViewBody (message)
{
    globalStatus.messageStructure = message;
    dumpDataWrapper (message, "#page-message #iframe-container");
}

function clearMessageViewBody ()
{
    $("#page-message #iframe-container").text("");
}

function clearAccountsList ()
{
    $("#accounts-list").html("");
}

function abortShowMessages ()
{
    if ('showMessages' in globalStatus.requests) {
	globalStatus.requests["showMessages"].abort();
    }
}

function markMessageAsRead(uid)
{
    iwkRequest ("flagMessage", "Marking message as read", {
	account: globalStatus.currentAccount,
	folder: globalStatus.currentFolder,
	message: uid,
	setFlags: "seen"
    }).done(function (msg) {
	$("#message-item-"+uid).removeClass("iwk-unread-item");
	$("#message-item-"+uid).addClass("iwk-read-item");
	$("#message-view-mark-as-read").hide();
	$("#message-view-mark-as-unread").show()
    });
}

function markMessageAsUnread(uid)
{
    iwkRequest ("flagMessage", "Marking message as unread", {
	account: globalStatus.currentAccount,
	folder: globalStatus.currentFolder,
	message: uid,
	unsetFlags: "seen"
    }).done(function (msg) {
	$("#message-item-"+uid).removeClass("iwk-read-item");
	$("#message-item-"+uid).addClass("iwk-unread-item");
	$("#message-view-mark-as-unread").hide();
	$("#message-view-mark-as-read").show();
    });
}

function markAsRead ()
{
    markMessageAsRead (globalStatus.currentMessage);
}

function markAsUnread ()
{
    markMessageAsUnread (globalStatus.currentMessage);
}

function reply ()
{
    bodies = dataWrapperGetBodies (globalStatus.messageStructure)
    $.mobile.changePage("#composer");

    // We should somehow concatenate the bodies. Now we simply use the first body we find.
    if (bodies.length > 0) {
	$.ajax({
	    url: bodies[0].uri,
	    dataType: 'jsonp',
	}).done(function(result) {
	    body = "\n-- Original message --\n"+result.data;
	    $("#composer-body").text(body);
	});
    }
}

function showMessage(message)
{
    globalStatus.messageStructure = null;
    clearMessageViewBody ();
    fillMessageViewHeader (message);
    if ('getMessage' in globalStatus.requests) {
	globalStatus.requests['getMessage'].abort();
    }

    globalStatus.requests["getMessage"] = iwkRequest ("getMessage", "Getting message", {
	account: globalStatus.currentAccount,
	folder: globalStatus.currentFolder,
	message: message.uid
    }).done(function (msg) {
	fillMessageViewBody (msg.result);
	markMessageAsRead (message.uid);
    }).always(function(jqXHR, textStatus, errorThrown) {
	if ('getMessage' in globalStatus.requests)
	    delete globalStatus.requests["getMessage"];
    });
}

function showNextMessage()
{
    current = null;
    $("#messages-list .iwk-message-item-link").each (function (index) {
	if (current != null) {
	    globalStatus.currentMessage = this.messageUid;
	    showMessage (this.message);
	    return false;
	}
	if (this.messageUid == globalStatus.currentMessage)
	    current = this;
    });
    $("#message-view-next-button").removeClass("ui-btn-active");
    return true;
}

function showPreviousMessage ()
{
    previous = null;
    $("#messages-list .iwk-message-item-link").each (function (index) {
	if (this.messageUid == globalStatus.currentMessage) {
	    if (previous) {
		globalStatus.currentMessage = previous.messageUid;
		showMessage (previous.message);
	    }
	    return false;
	}
	previous = this;
    });
    $("#message-view-previous-button").removeClass("ui-btn-active");
    return true;
}

function showMessages(accountId, folderId, onlyNew)
{
    try {
	if ('showMessages' in globalStatus.requests) {
	    globalStatus.requests["showMessages"].abort();
	}

	$("#messages-list-get-more-list").hide();
	$("#messages-list-getting-more-list").show();
	$("#messages-refresh").hide();
	$("#messages-cancel").show();

	retrieveCount = onlyNew?0:SHOW_MESSAGES_COUNT;
	globalStatus.requests["showMessages"] = iwkRequest ("getMessages", "Fetching messages", {
	    account: accountId,
	    folder: folderId,
	    newestUid: globalStatus.newestUid,
	    oldestUid: globalStatus.oldestUid,
	    count: retrieveCount
	}).done(function (msg) {
	    if (msg.newMessages.length > 0) {
		globalStatus.newestUid = msg.newMessages[0].uid;
	    }
	    if (globalStatus.newestUid == null && msg.messages.length > 0) {
		globalStatus.newestUid = msg.messages[0].uid;
	    }
	    if (msg.messages.length > 0) {
		globalStatus.oldestUid = msg.messages[msg.messages.length - 1].uid;
	    }
	    msg.newMessages.reverse();
	    for (i in msg.newMessages) {
		dumpMessageInMessagesList (msg.newMessages[i], true, "#page-messages #messages-list");
	    }
	    for (i in msg.messages) {
		dumpMessageInMessagesList (msg.messages[i], false, "#page-messages #messages-list");
	    }
	    $("#page-messages #messages-list").listview('refresh');
	}).always(function(jqXHR, textStatus, errorThrown) {
	    if ('showMessages' in globalStatus.requests)
		delete globalStatus.requests["showMessages"];
	    $("#messages-list-get-more-list").show();
	    $("#messages-list-getting-more-list").hide();
	    $("#messages-refresh").show();
	    $("#messages-cancel").hide();
	});
    } catch (e) {
	console.log(e.message);
    }
}

function fetchMoreMessages ()
{
    showMessages (globalStatus.currentAccount, globalStatus.currentFolder, false);
}

function fetchNewMessages ()
{
    showMessages (globalStatus.currentAccount, globalStatus.currentFolder, true);
}

function refreshAccounts ()
{
    iwkRequest ("getAccounts", "Updating accounts", {
    }).done(function (msg) {
	fillComposerFrom (msg.result);
	fillAccountsList (msg.result);
	syncFolders();
    });
}

function syncFolders ()
{
    iwkRequest("syncFolders", "Updating accounts and folders", {
    }).done(function (msg) {
	globalSetFolders (msg.result);
	fillAccountsListCounts ();
	fillFoldersList(globalStatus.currentAccount);
    });
}

function composerSend (data)
{
    iwkRequest ("composerSend", "Adding to outbox", {
	formData: data
    }).done(function (msg) {
	if (!msg.is_ok)
	    showError (msg.error);
	else
	    syncFolders();
    });
}

$(function () {
    $("#composer-send").click(function () {
	$("#form-composer").submit();
    });

    $("#form-composer").submit(function () {
	composerSend($(this).serialize());
	return false;
    });

    $("#accounts-compose").click(function () {
	clearForm($('#form-composer'));
	return true;
    });

    refreshAccounts();
});
<|endoftext|>


<commit_before><commit_msg>Removing the implementation of a seperate class for binary tree nodes<commit_after><|endoftext|>


<commit_before>// @flow
// An infinite scrolling chat list. Using react-virtualized which doens't really handle this case out of the box
// We control which set of messages we render in our state object
// We load that in in our constructor, after you stop scrolling or if we get an update and we're not currently scrolling

import LoadingMore from './messages/loading-more'
import React, {Component} from 'react'
import _ from 'lodash'
import messageFactory from './messages'
import {AutoSizer, CellMeasurer, List, defaultCellMeasurerCellSizeCache} from 'react-virtualized'
import {Box} from '../../common-adapters'
import {globalStyles} from '../../styles'

import type {Message} from '../../constants/chat'
import type {Props} from './'

type State = {
  isLockedToBottom: boolean,
  isScrolling: boolean,
  messages: List<Message>,
  scrollTop: number,
}

class ConversationList extends Component<void, Props, State> {
  _cellCache: any;
  _cellMeasurer: any;
  _list: any;
  state: State;

  constructor (props: Props) {
    super(props)

    this.state = {
      isLockedToBottom: true,
      isScrolling: false,
      messages: props.messages,
      scrollTop: 0,
    }

    this._cellCache = new CellSizeCache(this._indexToID)
    this._toRemeasure = []
  }

  _indexToID = index => {
    // loader message
    if (index === 0) {
      return 0
    } else {
      // minus one because loader message is there
      const messageIndex = index - 1
      const message = this.state.messages.get(messageIndex)
      // We want a stable key -- messages have an outboxID but no messageID,
      // then later gain a messageID.  So if we prefer outboxIDs to messageIDs
      // for the key, every row keeps its key.
      const id = message && (message.outboxID || message.messageID)
      if (id == null) {
        console.warn('id is null for index:', messageIndex)
      }
      return id
    }
  }

  componentWillUpdate (nextProps: Props, nextState: State) {
    // If a message has moved from pending to sent, tell the List to discard
    // heights for it and everything after it, so that they're re-rendered.
    if (this._toRemeasure.length) {
      this._toRemeasure.forEach(item => {
        this._list.recomputeRowHeights(item)
      })
      this._toRemeasure = []
    }
  }

  componentDidUpdate (prevProps: Props, prevState: State) {
    if (!this.state.isLockedToBottom && this.state.messages !== prevState.messages && prevState.messages.count() > 1) {
      // Figure out how many new items we have
      const prependedCount = this.state.messages.indexOf(prevState.messages.first())
      if (prependedCount !== -1) {
        // Measure the new items so we can adjust our scrollTop so your position doesn't jump
        const scrollTop = this.state.scrollTop + _.range(0, prependedCount)
          .map(index => this._cellMeasurer.getRowHeight({index: index + 1})) // +1 since 0 is the loading message
          .reduce((total, height) => total + height, 0)

        // Disabling eslint as we normally don't want to call setState in a componentDidUpdate in case you infinitely re-render
        this.setState({scrollTop}) // eslint-disable-line react/no-did-update-set-state
      }
    }
  }

  componentWillReceiveProps (nextProps: Props) {
    // If we're not scrolling let's update our internal messages
    if (!this.state.isScrolling) {
      this._invalidateChangedMessages()
      this.setState({
        messages: nextProps.messages,
      })
    }
  }

  _invalidateChangedMessages () {
    this.state.messages.forEach((item, index) => {
      if (item.messageID !== this.props.messages.get(index, {}).messageID) {
        this._toRemeasure.push(index + 1)
      }
    })
  }

  _onScrollSettled = _.debounce(() => {
    // If we've stopped scrolling let's update our internal messages
    this._invalidateChangedMessages()
    this.setState({
      isScrolling: false,
      ...(this.state.messages !== this.props.messages ? {messages: this.props.messages} : null),
    })
  }, 1000)

  _onScroll = _.throttle(({clientHeight, scrollHeight, scrollTop}) => {
    // Do nothing if we haven't really loaded anything
    if (!clientHeight) {
      return
    }

    // At the top, load more messages. Action handles loading state and if there's actually any more
    if (scrollTop === 0) {
      this.props.loadMoreMessages()
    }

    const isLockedToBottom = scrollTop + clientHeight === scrollHeight
    this.setState({
      isLockedToBottom,
      isScrolling: true,
      scrollTop,
    })

    // This is debounced so it resets the call
    this._onScrollSettled()
  }, 100)

  _rowRenderer = ({index, key, style, isScrolling}: {index: number, key: string, style: Object, isScrolling: boolean}) => {
    if (index === 0) {
      return <LoadingMore style={style} key={key || index} hasMoreItems={this.props.moreToLoad} />
    }

    const message = this.state.messages.get(index - 1)
    return messageFactory(message, index, key, style, isScrolling)
  }

  render () {
    const countWithLoading = this.state.messages.count() + 1 // Loading row on top always
    let scrollToIndex = this.state.isLockedToBottom ? countWithLoading - 1 : undefined
    let scrollTop = scrollToIndex ? undefined : this.state.scrollTop

    return (
      <Box style={{...globalStyles.flexBoxColumn, flex: 1}}>
        <AutoSizer>
          {({height, width}) => (
            <CellMeasurer
              cellRenderer={({rowIndex, ...rest}) => this._rowRenderer({index: rowIndex, ...rest})}
              columnCount={1}
              ref={r => { this._cellMeasurer = r }}
              cellSizeCache={this._cellCache}
              rowCount={countWithLoading} >
              {({getRowHeight}) => (
                <List
                  height={height}
                  ref={r => { this._list = r }}
                  width={width}
                  onScroll={this._onScroll}
                  scrollTop={scrollTop}
                  scrollToIndex={scrollToIndex}
                  rowCount={countWithLoading}
                  rowHeight={getRowHeight}
                  rowRenderer={this._rowRenderer} />
              )}
            </CellMeasurer>
          )}
        </AutoSizer>
      </Box>
    )
  }
}

class CellSizeCache extends defaultCellMeasurerCellSizeCache {
  _indexToID: (index: number) => number;

  constructor (indexToID) {
    super({uniformColumnWidth: true})
    this._indexToID = indexToID
  }

  getRowHeight (index) {
    return super.getRowHeight(this._indexToID(index))
  }

  setRowHeight (index, height) {
    super.setRowHeight(this._indexToID(index), height)
  }

  hasRowHeight (index) {
    return super.hasRowHeight(this._indexToID(index))
  }

  clearRowHeight (index) {
    return super.clearRowHeight(this._indexToID(index))
  }
}

export default ConversationList
<commit_msg>fix flow<commit_after>// @flow
// An infinite scrolling chat list. Using react-virtualized which doens't really handle this case out of the box
// We control which set of messages we render in our state object
// We load that in in our constructor, after you stop scrolling or if we get an update and we're not currently scrolling

import LoadingMore from './messages/loading-more'
import React, {Component} from 'react'
import _ from 'lodash'
import messageFactory from './messages'
import {AutoSizer, CellMeasurer, List, defaultCellMeasurerCellSizeCache} from 'react-virtualized'
import {Box} from '../../common-adapters'
import {globalStyles} from '../../styles'

import type {Message} from '../../constants/chat'
import type {Props} from './'

type State = {
  isLockedToBottom: boolean,
  isScrolling: boolean,
  messages: List<Message>,
  scrollTop: number,
}

class ConversationList extends Component<void, Props, State> {
  _cellCache: any;
  _cellMeasurer: any;
  _list: any;
  state: State;
  _toRemeasure: List;

  constructor (props: Props) {
    super(props)

    this.state = {
      isLockedToBottom: true,
      isScrolling: false,
      messages: props.messages,
      scrollTop: 0,
    }

    this._cellCache = new CellSizeCache(this._indexToID)
    this._toRemeasure = []
  }

  _indexToID = index => {
    // loader message
    if (index === 0) {
      return 0
    } else {
      // minus one because loader message is there
      const messageIndex = index - 1
      const message = this.state.messages.get(messageIndex)
      // We want a stable key -- messages have an outboxID but no messageID,
      // then later gain a messageID.  So if we prefer outboxIDs to messageIDs
      // for the key, every row keeps its key.
      const id = message && (message.outboxID || message.messageID)
      if (id == null) {
        console.warn('id is null for index:', messageIndex)
      }
      return id
    }
  }

  componentWillUpdate (nextProps: Props, nextState: State) {
    // If a message has moved from pending to sent, tell the List to discard
    // heights for it and everything after it, so that they're re-rendered.
    if (this._toRemeasure.length) {
      this._toRemeasure.forEach(item => {
        this._list.recomputeRowHeights(item)
      })
      this._toRemeasure = []
    }
  }

  componentDidUpdate (prevProps: Props, prevState: State) {
    if (!this.state.isLockedToBottom && this.state.messages !== prevState.messages && prevState.messages.count() > 1) {
      // Figure out how many new items we have
      const prependedCount = this.state.messages.indexOf(prevState.messages.first())
      if (prependedCount !== -1) {
        // Measure the new items so we can adjust our scrollTop so your position doesn't jump
        const scrollTop = this.state.scrollTop + _.range(0, prependedCount)
          .map(index => this._cellMeasurer.getRowHeight({index: index + 1})) // +1 since 0 is the loading message
          .reduce((total, height) => total + height, 0)

        // Disabling eslint as we normally don't want to call setState in a componentDidUpdate in case you infinitely re-render
        this.setState({scrollTop}) // eslint-disable-line react/no-did-update-set-state
      }
    }
  }

  componentWillReceiveProps (nextProps: Props) {
    // If we're not scrolling let's update our internal messages
    if (!this.state.isScrolling) {
      this._invalidateChangedMessages()
      this.setState({
        messages: nextProps.messages,
      })
    }
  }

  _invalidateChangedMessages () {
    this.state.messages.forEach((item, index) => {
      if (item.messageID !== this.props.messages.get(index, {}).messageID) {
        this._toRemeasure.push(index + 1)
      }
    })
  }

  _onScrollSettled = _.debounce(() => {
    // If we've stopped scrolling let's update our internal messages
    this._invalidateChangedMessages()
    this.setState({
      isScrolling: false,
      ...(this.state.messages !== this.props.messages ? {messages: this.props.messages} : null),
    })
  }, 1000)

  _onScroll = _.throttle(({clientHeight, scrollHeight, scrollTop}) => {
    // Do nothing if we haven't really loaded anything
    if (!clientHeight) {
      return
    }

    // At the top, load more messages. Action handles loading state and if there's actually any more
    if (scrollTop === 0) {
      this.props.loadMoreMessages()
    }

    const isLockedToBottom = scrollTop + clientHeight === scrollHeight
    this.setState({
      isLockedToBottom,
      isScrolling: true,
      scrollTop,
    })

    // This is debounced so it resets the call
    this._onScrollSettled()
  }, 100)

  _rowRenderer = ({index, key, style, isScrolling}: {index: number, key: string, style: Object, isScrolling: boolean}) => {
    if (index === 0) {
      return <LoadingMore style={style} key={key || index} hasMoreItems={this.props.moreToLoad} />
    }

    const message = this.state.messages.get(index - 1)
    return messageFactory(message, index, key, style, isScrolling)
  }

  render () {
    const countWithLoading = this.state.messages.count() + 1 // Loading row on top always
    let scrollToIndex = this.state.isLockedToBottom ? countWithLoading - 1 : undefined
    let scrollTop = scrollToIndex ? undefined : this.state.scrollTop

    return (
      <Box style={{...globalStyles.flexBoxColumn, flex: 1}}>
        <AutoSizer>
          {({height, width}) => (
            <CellMeasurer
              cellRenderer={({rowIndex, ...rest}) => this._rowRenderer({index: rowIndex, ...rest})}
              columnCount={1}
              ref={r => { this._cellMeasurer = r }}
              cellSizeCache={this._cellCache}
              rowCount={countWithLoading} >
              {({getRowHeight}) => (
                <List
                  height={height}
                  ref={r => { this._list = r }}
                  width={width}
                  onScroll={this._onScroll}
                  scrollTop={scrollTop}
                  scrollToIndex={scrollToIndex}
                  rowCount={countWithLoading}
                  rowHeight={getRowHeight}
                  rowRenderer={this._rowRenderer} />
              )}
            </CellMeasurer>
          )}
        </AutoSizer>
      </Box>
    )
  }
}

class CellSizeCache extends defaultCellMeasurerCellSizeCache {
  _indexToID: (index: number) => number;

  constructor (indexToID) {
    super({uniformColumnWidth: true})
    this._indexToID = indexToID
  }

  getRowHeight (index) {
    return super.getRowHeight(this._indexToID(index))
  }

  setRowHeight (index, height) {
    super.setRowHeight(this._indexToID(index), height)
  }

  hasRowHeight (index) {
    return super.hasRowHeight(this._indexToID(index))
  }

  clearRowHeight (index) {
    return super.clearRowHeight(this._indexToID(index))
  }
}

export default ConversationList
<|endoftext|>


<commit_before>'use strict';

//import path from 'path';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import webpackConfig from './webpack.config.js';
//import del from 'del';
import { assign } from 'lodash';
import gulp from 'gulp';
import gutil from 'gulp-util';
import manifest from 'gulp-manifest';
import shell from 'gulp-shell';
//import minimist from 'minimist';
//import asar from  'asar';

gulp.task('webpack:build', (callback) => {
  // modify some webpack config options
  var myConfig = Object.create(webpackConfig);
  myConfig.plugins = myConfig.plugins.concat(
    new webpack.DefinePlugin({
      'process.env': {
        // This has effect on the react lib size
        'NODE_ENV': JSON.stringify('production')
      }
    }),
    new webpack.optimize.UglifyJsPlugin()
  );

  // run webpack
  webpack(myConfig, (err, stats) => {
    if (err) {
      throw new gutil.PluginError('webpack:build', err);
    }
    gutil.log('[webpack:build]', stats.toString({
      colors: true
    }));
    callback();
  });
});

gulp.task('webpack-dev-server', () =>{
  // modify some webpack config options
  assign(webpackConfig, {
    plugins: [
      new webpack.ResolverPlugin([
        new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('package.json', ['main']),
        new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('bower.json', ['main'])
      ]),
      new webpack.optimize.DedupePlugin(),
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoErrorsPlugin()
    ]
  });
  var myConfig = Object.create(webpackConfig);

  // Start a webpack-dev-server
  new WebpackDevServer(webpack(myConfig), {
    publicPath: '/assets/',
    contentBase: './dist',
    hot: true,
    historyApiFallback: true,
    stats: {
      colors: true
    }
  }).listen(3000, 'localhost', function(err) {
      if (err) {
        throw new gutil.PluginError('webpack-dev-server', err);
      }
      gutil.log('[webpack-dev-server]', 'http://localhost:3000');
    });
});

gulp.task('emoji', () => {
  gulp.src(['./node_modules/emojify.js/dist/images/basic/*'])
    .pipe(gulp.dest('./dist/assets/img/emoji'));
});

gulp.task('push', () => {
  gulp.src(['./push/*'])
    .pipe(gulp.dest('./dist/'));
});

gulp.task('assets', () => {
  gulp.src(['src/assets/**/*'])
    .pipe(gulp.dest('./dist/assets/'));
});

gulp.task('html', () => {
  gulp.src(['src/index.html'])
    .pipe(gulp.dest('./dist/'));
});

gulp.task('lib:build', shell.task(['cd ../ && ./gradlew :core-js:buildPackage']));
gulp.task('lib', ['lib:build'], () => {
  const stream =
    gulp.src('../core-js/build/package/*')
      .pipe(gulp.dest('./dist/actor/'));

  return stream;
});

gulp.task('manifest:prod', ['static', 'webpack:build'], () => {
  gulp.src(['./dist/**/*'])
    .pipe(manifest({
      hash: true,
      network: ['http://*', 'https://*', '*'],
      filename: 'app.appcache'
    }))
    .pipe(gulp.dest('./dist/'));
});

gulp.task('electron:prepare', ['build'], () => {
  gulp.src(['dist/**/*'])
    .pipe(gulp.dest('./electron_dist/app'));
});

gulp.task('electron:app', () => {
  gulp.src(['electron/**/*'])
    .pipe(gulp.dest('./electron_dist/app'));
});

gulp.task('electron', ['electron:prepare', 'electron:app'], shell.task(['asar pack electron_dist/app electron_dist/app.asar']));

gulp.task('static', ['html', 'assets', 'lib', 'push', 'emoji']);

gulp.task('dev', ['static', 'webpack-dev-server']);

gulp.task('build', ['static', 'webpack:build', 'manifest:prod']);

gulp.task('build:gwt', ['static', 'webpack:build']);

gulp.task('dist', ['build', 'electron']);
<commit_msg>chore(web): exclude .map files from appcache;<commit_after>'use strict';

//import path from 'path';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import webpackConfig from './webpack.config.js';
//import del from 'del';
import { assign } from 'lodash';
import gulp from 'gulp';
import gutil from 'gulp-util';
import manifest from 'gulp-manifest';
import shell from 'gulp-shell';
//import minimist from 'minimist';
//import asar from  'asar';

gulp.task('webpack:build', (callback) => {
  // modify some webpack config options
  var myConfig = Object.create(webpackConfig);
  myConfig.plugins = myConfig.plugins.concat(
    new webpack.DefinePlugin({
      'process.env': {
        // This has effect on the react lib size
        'NODE_ENV': JSON.stringify('production')
      }
    }),
    new webpack.optimize.UglifyJsPlugin()
  );

  // run webpack
  webpack(myConfig, (err, stats) => {
    if (err) {
      throw new gutil.PluginError('webpack:build', err);
    }
    gutil.log('[webpack:build]', stats.toString({
      colors: true
    }));
    callback();
  });
});

gulp.task('webpack-dev-server', () =>{
  // modify some webpack config options
  assign(webpackConfig, {
    plugins: [
      new webpack.ResolverPlugin([
        new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('package.json', ['main']),
        new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('bower.json', ['main'])
      ]),
      new webpack.optimize.DedupePlugin(),
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoErrorsPlugin()
    ]
  });
  var myConfig = Object.create(webpackConfig);

  // Start a webpack-dev-server
  new WebpackDevServer(webpack(myConfig), {
    publicPath: '/assets/',
    contentBase: './dist',
    hot: true,
    historyApiFallback: true,
    stats: {
      colors: true
    }
  }).listen(3000, 'localhost', function(err) {
      if (err) {
        throw new gutil.PluginError('webpack-dev-server', err);
      }
      gutil.log('[webpack-dev-server]', 'http://localhost:3000');
    });
});

gulp.task('emoji', () => {
  gulp.src(['./node_modules/emojify.js/dist/images/basic/*'])
    .pipe(gulp.dest('./dist/assets/img/emoji'));
});

gulp.task('push', () => {
  gulp.src(['./push/*'])
    .pipe(gulp.dest('./dist/'));
});

gulp.task('assets', () => {
  gulp.src(['src/assets/**/*'])
    .pipe(gulp.dest('./dist/assets/'));
});

gulp.task('html', () => {
  gulp.src(['src/index.html'])
    .pipe(gulp.dest('./dist/'));
});

gulp.task('lib:build', shell.task(['cd ../ && ./gradlew :core-js:buildPackage']));
gulp.task('lib', ['lib:build'], () => {
  const stream =
    gulp.src('../core-js/build/package/*')
      .pipe(gulp.dest('./dist/actor/'));

  return stream;
});

gulp.task('manifest:prod', ['static', 'webpack:build'], () => {
  gulp.src(['./dist/**/*'])
    .pipe(manifest({
      hash: true,
      network: ['http://*', 'https://*', '*'],
      filename: 'app.appcache',
      exclude: ['assets/styles.js.map', 'assets/app.js.map']
    }))
    .pipe(gulp.dest('./dist/'));
});

gulp.task('electron:prepare', ['build'], () => {
  gulp.src(['dist/**/*'])
    .pipe(gulp.dest('./electron_dist/app'));
});

gulp.task('electron:app', () => {
  gulp.src(['electron/**/*'])
    .pipe(gulp.dest('./electron_dist/app'));
});

gulp.task('electron', ['electron:prepare', 'electron:app'], shell.task(['asar pack electron_dist/app electron_dist/app.asar']));

gulp.task('static', ['html', 'assets', 'lib', 'push', 'emoji']);

gulp.task('dev', ['static', 'webpack-dev-server']);

gulp.task('build', ['static', 'webpack:build', 'manifest:prod']);

gulp.task('build:gwt', ['static', 'webpack:build']);

gulp.task('dist', ['build', 'electron']);
<|endoftext|>


<commit_before>/**
 * Child Class
 *
 * Defines a visualization child.
 *
 * Options:
 *  height - Integer
 *  width - Integer
 *  flotr - A set of flotr options
 *
 */
(function () {

var D = Flotr.DOM,
  flotrDefaultOptions = {
    grid : {
      outlineWidth : 0,
      labelMargin : 0
    },
    lines : {
      lineWidth : 1
    },
    xaxis : {
      margin : false,
      noTicks : 0,
      showLabels  : false
    },
    yaxis : {
      margin : false,
      noTicks : 0,
      showLabels  : false
    },
    shadowSize : false
};

function Child(options) {
  this.options = options || {};
  this.container = D.node('

');
  this.flotr = null;
  this._flotrDefaultOptions();
}

Child.prototype = {

  getFlotr : function () { return this.flotr; },
  getData : function () { return this.options.data; },

  render : function (element) {

    var o = this.options;

    if (!element) throw 'No element to render within.';
    if (!o.width) throw 'No width.';
    if (!o.height) throw 'No height.';

    D.insert(element, this.container);
    D.setStyles(this.container, {width : o.width+'px', height : o.height+'px'});

    this.draw(o.data, o.flotr);
  },

  draw : function (data, flotr) {

    var o = this.options,
      data = data || o.data,
      container = this.container;

    if (flotr) {
      this._flotrDefaultOptions(flotr);
      _.extend(o.flotr, flotr);
    }
    flotr = o.flotr;
    o.data = data;

    data = this._processData();

    if (!flotr) throw 'No graph submitted.'

    this.flotr = Flotr.draw(container, [data], flotr);
  },

  _processData : function () {

    var o = this.options.flotr,
      fData = [],
      data = this.getData(),
      length = data.length,
      min = o.xaxis.min,
      max = o.xaxis.max;

    // Bound Data
    if (min && max) {
      for (i = 0; i < length; i++) {
        if (data[i][0] >= min) break;
      }
      for (i--; i < length; i++) {
        if (data[i][0] > max) break;
        fData.push(data[i]);
      }
    } else {
      fData = data;
    }

    data = fData;
    fData = [];
    length = data.length;

    // Subsample Data
    // TODO simple subsampling, fine for homogeneous data.  Implement complex strategies.
    var width = this.options.width,
      unit;

    if (length > width * 3) {
      unit = Math.round(length / width);
      for(i = 0; i < width*3-1; i++) {
        if (i*unit >= length)
          break;
        fData.push(data[i*unit]);
      }
      fData.push(data[length-1]);
    } else {
      fData = data;
    }

    return fData;
  },

  _flotrDefaultOptions : function (options) {

    var o = options || this.options.flotr,
      defaults = flotrDefaultOptions,
      i;

    for (i in defaults) {
      if (_.isUndefined(o[i])) {
        o[i] = defaults[i];
      } else {
        _.defaults(o[i], defaults[i]);
      }
    }
  }
};

Humble.Vis.Child = Child;

})();
<commit_msg>Fixed boundary offset error.<commit_after>/**
 * Child Class
 *
 * Defines a visualization child.
 *
 * Options:
 *  height - Integer
 *  width - Integer
 *  flotr - A set of flotr options
 *
 */
(function () {

var D = Flotr.DOM,
  flotrDefaultOptions = {
    grid : {
      outlineWidth : 0,
      labelMargin : 0
    },
    lines : {
      lineWidth : 1
    },
    xaxis : {
      margin : false,
      noTicks : 0,
      showLabels  : false
    },
    yaxis : {
      margin : false,
      noTicks : 0,
      showLabels  : false
    },
    shadowSize : false
};

function Child(options) {
  this.options = options || {};
  this.container = D.node('

');
  this.flotr = null;
  this._flotrDefaultOptions();
}

Child.prototype = {

  getFlotr : function () { return this.flotr; },
  getData : function () { return this.options.data; },

  render : function (element) {

    var o = this.options;

    if (!element) throw 'No element to render within.';
    if (!o.width) throw 'No width.';
    if (!o.height) throw 'No height.';

    D.insert(element, this.container);
    D.setStyles(this.container, {width : o.width+'px', height : o.height+'px'});

    this.draw(o.data, o.flotr);
  },

  draw : function (data, flotr) {

    var o = this.options,
      data = data || o.data,
      container = this.container;

    if (flotr) {
      this._flotrDefaultOptions(flotr);
      _.extend(o.flotr, flotr);
    }
    flotr = o.flotr;
    o.data = data;

    data = this._processData();

    if (!flotr) throw 'No graph submitted.'

    this.flotr = Flotr.draw(container, [data], flotr);
  },

  _processData : function () {

    var o = this.options.flotr,
      fData = [],
      data = this.getData(),
      length = data.length,
      min = o.xaxis.min,
      max = o.xaxis.max;

    // Bound Data
    if (min && max) {
      for (i = 0; i < length; i++) {
        if (data[i][0] >= min) break;
      }
      for (i; i < length; i++) {
        if (data[i][0] > max) break;
        fData.push(data[i]);
      }
    } else {
      fData = data;
    }

    data = fData;
    fData = [];
    length = data.length;

    // Subsample Data
    // TODO simple subsampling, fine for homogeneous data.  Implement complex strategies.
    var width = this.options.width,
      unit;

    if (length > width * 3) {
      unit = Math.round(length / width);
      for(i = 0; i < width*3-1; i++) {
        if (i*unit >= length)
          break;
        fData.push(data[i*unit]);
      }
      fData.push(data[length-1]);
    } else {
      fData = data;
    }

    return fData;
  },

  _flotrDefaultOptions : function (options) {

    var o = options || this.options.flotr,
      defaults = flotrDefaultOptions,
      i;

    for (i in defaults) {
      if (_.isUndefined(o[i])) {
        o[i] = defaults[i];
      } else {
        _.defaults(o[i], defaults[i]);
      }
    }
  }
};

Humble.Vis.Child = Child;

})();
<|endoftext|>


<commit_before>// base.js
// 141211
// - initial version

// Disable enter to submit form
// http://stackoverflow.com/a/587575
function checkEnter(e){
	e = e || event;
	var txtArea = /textarea/i.test((e.target || e.srcElement).tagName);
	return txtArea || (e.keyCode || e.which || e.charCode || 0) !== 13;
}

var $overlay,
	$tooltip,
	$menu,
	csrftoken = $.cookie('csrftoken'),
	browser = {
		can : {
			placeholder: function() { return 'placeholder' in document.createElement('input'); }
		}
	},
	taggingJS_options = {
		'case-sensitive': true,
		'forbidden-chars': [','],
		'forbidden-chars-text': '이하의 문자는 사용할 수 없습니다:\n',
		'forbidden-words-text': '이하의 단어는 사용할 수 없습니다:\n',
		'no-duplicate-text': '이하의 태그는 이미 선택되어 있습니다:\n',
		'tag-char': '',
		'no-spacebar': true,
		'tag-on-blur': false
	},
	autocomplete_options = {
		'serviceUrl': '/x/t',
		'type': 'POST',
		'minChars': 2,
		'lookupLimit': 5,
		'preserveInput': true,
		'formatResult': function(suggestion, currentValue) {
			return '' + $.Autocomplete.formatResult(suggestion, currentValue) + '' + suggestion.data + '';
		},
		'onSelect': function(suggestion) { this.focus(); },
		'zIndex': 1050
	}

$(function() {

	$window = $(window);
	$document = $(document);
	$body = $('body');
	$overlay = $('#overlay');
	$tooltip = $('#tooltip');
	$menu = $('.dropdown.container');

	$document
		.one('touchstart', function() {
			$('html').removeClass('no-touch');

			$document.off('mouseenter mouseleave', '.th');
		})

		.on('keypress', '.type-zone', checkEnter)

		.on('click', '.dropdown.container .handle', function(e) {
			var $container = $(this).closest('.container');
			if(!$container.hasClass('open')) { $container.addClass('open');
			} else { $container.removeClass('open'); }
		})
		.on('mouseleave', '.dropdown.container', function() { if($(this).hasClass('open')) $(this).removeClass('open'); })
		.on('click', '.dropdown.container .handle[href=#]', function(e) { e.preventDefault(); })
		.on('mousedown', '.dropdown.container .close', function(e) {
			e.preventDefault();
			var $container = $(this).closest('.container');
			$container.removeClass('open');
		})

		.on('click', 'a.checkbox', function(e) {
			if($(this).attr('href') != '#') return true;

			e.preventDefault();

			var $checkbox = $(this).prev('input[type=checkbox]');

			$(this).toggleClass('checked');
			$checkbox.prop('checked', !$checkbox.prop('checked'));
		})

		.on('mouseenter', '.th', showTooltip)
		.on('mouseleave scroll', '.th', hideTooltip)
		.on('touchstart', '.th:not(.th-active)', showTooltip)
		.on('touchstart', '.th.th-active', hideTooltip)
		.on('mouseenter touchstart', '*[title]', function(e) {
			if($(this).closest('.note-editor').length) return false;
			$(this)
				.data('title', $(this).attr('title'))
				.removeAttr('title')
				.addClass('th');
			showTooltip(e);
		});

	$('.article img').each(function() {
		if($(this).parents('a').length > 0) { return false; } else {
			var a = $('');

			a
				.addClass('imagecontainer')
				.attr('href', $(this).attr('src'))
				.attr('target', '_blank');

			$(this).wrap(a);
		}
	});

	$('input[type=checkbox]').each(function() {
		var $label;

		$label = $(this).closest('label').length ? $(this).closest('label') : $label;
		$label = $(this).next('label').length ? $(this).next('label') : $label;
		$label = $(this).prev('label').length ? $(this).prev('label') : $label;

		var $a = $('')
			.attr('href', '#')
			.addClass('checkbox')
			.html('')

		if($(this).prop('checked')) $a.addClass('checked');

		$a
			.insertAfter($label)
			.find('.label').append('' + $.trim($label.text()) + '');
		$(this).insertAfter($label);

		$(this).hide();
		$label.remove();
	});

	$('.post.item .label.meta.comments')
		.on('mouseenter', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', $item.attr('href') + '#comments');
		})
		.on('mouseleave', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', $item.attr('href').replace('#comments', ''));
		});

	$('.post.item .label.meta.author.user')
		.on('mouseenter', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', $(this).data('user-id-url'));
		})
		.on('mouseleave', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', '/' + $item.data('post-id') + '/');
		});

	$('.section.article.form form')
		.on('submit', function() {
			$('#tagbox').tagging('add');
			$tags.val($tagbox.tagging('getTags').join(','));
		})
		.find('.category')
			.on('click', 'a.option', function(e) {
				e.preventDefault();
				$('.section.article.form .category option').filter('[value='+$(this).data('value')+']').prop('selected', true);
				$('.section.article.form .category .text').text($(this).text());
				$(this).closest('.dropdown.container').removeClass('open');
			})
			.each(function() {
				var option, $handle = $(''), $dropdown = $('
'), $ul = $('
');

				option = $(this).find('select option');

				$('')
					.html(''+$(this).data('text')+'')
					.addClass('text')
					.appendTo($handle);

				$('')
					.addClass('icon')
					.appendTo($handle);

				$handle
					.attr('href', '#')
					.addClass('handle label meta')
					.appendTo($(this));

				$('')
					.addClass('close')
					.attr('href', '#')
					.appendTo($dropdown);

				$('
')
					.addClass('tip top')
					.html('')
					.appendTo($dropdown);

				option.each(function() {
					var $li = $('
'), $a = $(''), value = $(this).attr('value');

					if(value == '') return true;

					if($(this).prop('selected')) $(this).closest('.dropdown.container').find('.handle .text span').text($(this).text());

					$('')
						.text($(this).text())
						.appendTo($a);

					$a
						.attr('href', '#')
						.addClass('option')
						.data('value', value)
						.appendTo($li);

					switch($(this).text()) {
						case '번역':
							$li.addClass('scanlation');
							break;
						case '자막':
							$li.addClass('subtitles');
							break;
						case '정보':
							$li.addClass('news');
							break;
					}

					$li.appendTo($ul);
				});

				$ul.appendTo($dropdown);

				$dropdown
					.addClass('dropdown menu')
					.appendTo($(this));

				$(this).find('select').hide();
			});

	if(!browser.can.placeholder()) {
		$('#form_login .field input').each(function() {
			$('')
				.attr('for', $(this).attr('id'))
				.append(''+$(this).attr('placeholder')+'')
				.insertAfter($(this).addClass('noplaceholder'));
		});
	}

	$('#form_login')
		.on('load focusin focusout', '.field input.noplaceholder', function() {
			if(this.value == '') { $(this).next('label').show();
			} else { $(this).next('label').hide(); }
		})
		.on('keypress', '.field input.noplaceholder', function() {
			$(this).next('label').hide();
		});

	$('.vote a').on('click', function(e) {
		e.preventDefault();
		vote('p', $(this).parent('.vote').data('target-id'), $(this));
	});

	$('.section.board')
		.on('click', '.search.button .label.meta.search', function(e) {
			e.preventDefault();

			var $nav = $(this).closest('.nav.bottom');

			$nav
				.addClass('open')
				.removeClass('close');

		})
		.on('click', '.search.button .label.meta.delete', function(e) {
			e.preventDefault();

			var $nav = $(this).closest('.nav.bottom');

			$nav
				.removeClass('open')
				.addClass('close');
		});

	$('.tabs')
		.on('click', '.tab.header a:not(.current)', function(e) {
			e.preventDefault();

			var $container = $(this).closest('.tabs'),
				target = $(this).attr('href').replace('#', '.');

			$container.find('.section').hide();
			$container.find(target).show();

			$container.find('.tab.header a').removeClass('current');
			$(this).addClass('current');

		})
});

function csrfSafeMethod(method) { return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); }

$.ajaxSetup({
	beforeSend: function(xhr, settings) {
		if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
			xhr.setRequestHeader("X-CSRFToken", csrftoken);
		}
	}
});

function showTooltip(e) {
	e.preventDefault();

	var $target = $(e.target).closest('.th');
	var position = $target.offset();

	var targetHeight = $target.outerHeight();
	var targetWidth = $target.outerWidth();

	var offsetY = 0;
	var offsetX = 0;

	var top;
	var left;

	var height;
	var width;

	var margin = 10;

	$tooltip.find('.container p').html($target.data('title').replace(/\\n/gi, '
'));
	$tooltip.find('.tip').removeAttr('style');

	top = position.top + targetHeight;
	left = position.left + targetWidth/2;

	height = $tooltip.height();
	width = $tooltip.width();

	if(top + height + margin > $window.height() + $window.scrollTop()) offsetY = -1*(targetHeight + height);

	offsetX = $window.width() + $window.scrollLeft() - left;

	if(offsetX - (width/2 + margin) < 0) {
		offsetX = offsetX - (width/2 + margin);
		if(width/2 + offsetX < 0 ) return false;
	} else if(offsetX + (width/2 + margin) > 0) {
		offsetX = offsetX + width/2 + margin - $window.width();
		if(offsetX < 0) offsetX = 0;
		if(width/2 - offsetX < 0 ) return false;
	} else { offsetX = 0; }

	$('.th-active').removeClass('th-active');
	$target.addClass('th-active');

	$tooltip
		.css({
			'top': top + offsetY,
			'left': left - width/2 + offsetX
		})
		.show()
		.stop()
		.animate({'opacity': 1}, 100)
		.find('.tip')
			.hide()
			.css('margin-left', -2*offsetX)
			.filter((offsetY >= 0) ? '.top' : '.bottom').show();
}

function hideTooltip() {
	$('.th-active').removeClass('th-active');

	$tooltip
		.stop()
		.animate({'opacity': 0}, 100, function() { $tooltip.hide(); });
}

function vote(type, target, button) {
	var vote, avote, work,
		$sibling_voted = button.siblings('.voted'),
		databox = {
			type: type,
			target: target,
			vote: null
		};

	work = button.hasClass('voted') ? '-' : '+';
	vote = button.hasClass('upvote') ? '+' : '-';
	avote = (vote == '+') ? '-' : '+';

	if(work == '+' && $sibling_voted.length) {
		$ajax_vote({type: type, target: target, vote: '-' + avote}, $sibling_voted)
			.done(function(data, status, xhr) {
				$ajax_vote({type: type, target: target, vote: work + vote})
					.done(function(data) { vote_callback(data, work, button); });

				vote_callback(data, '-', $sibling_voted);
			});
	} else { $ajax_vote({type: type, target: target, vote: work + vote}).done(function(data) { vote_callback(data, work, button); }); };
}

function vote_callback(data, work, button) {
	if(work == '+') { button.addClass('voted');
	} else { button.removeClass('voted'); }

	var $score = button.closest('.item').find('.meta.score');

	$score
		.attr('title', '+'+data.current.upvote+' / -'+data.current.downvote)
		.find('.text span').text(data.current.total);
}

function $ajax_vote(databox) {
	return $.ajax({
			type: 'POST',
			url: VOTE_AJAX_ENDPOINT,
			data: databox
		})
			.fail(function(xhr, status, error) {
				var response = xhr.responseJSON.status;
				switch(response) {
					case 'notauthenticated':
						alert('로그인한 사용자만 사용 가능한 기능입니다.');
						break;

					case 'alreadyhave':
						alert('이미 추천한 게시글 또는 댓글입니다.');
						break;

					default:
						alert('알 수 없는 문제가 발생하였습니다.');
						console.log(databox);
						console.log(xhr);
						console.log(status);
						console.log(error);
				}
			});
}
<commit_msg>Adjust custom JS tooltip behavior for better mobile UX<commit_after>// base.js
// 141211
// - initial version

// Disable enter to submit form
// http://stackoverflow.com/a/587575
function checkEnter(e){
	e = e || event;
	var txtArea = /textarea/i.test((e.target || e.srcElement).tagName);
	return txtArea || (e.keyCode || e.which || e.charCode || 0) !== 13;
}

var $overlay,
	$tooltip,
	$menu,
	csrftoken = $.cookie('csrftoken'),
	browser = {
		can : {
			placeholder: function() { return 'placeholder' in document.createElement('input'); }
		}
	},
	taggingJS_options = {
		'case-sensitive': true,
		'forbidden-chars': [','],
		'forbidden-chars-text': '이하의 문자는 사용할 수 없습니다:\n',
		'forbidden-words-text': '이하의 단어는 사용할 수 없습니다:\n',
		'no-duplicate-text': '이하의 태그는 이미 선택되어 있습니다:\n',
		'tag-char': '',
		'no-spacebar': true,
		'tag-on-blur': false
	},
	autocomplete_options = {
		'serviceUrl': '/x/t',
		'type': 'POST',
		'minChars': 2,
		'lookupLimit': 5,
		'preserveInput': true,
		'formatResult': function(suggestion, currentValue) {
			return '' + $.Autocomplete.formatResult(suggestion, currentValue) + '' + suggestion.data + '';
		},
		'onSelect': function(suggestion) { this.focus(); },
		'zIndex': 1050
	}

$(function() {

	$window = $(window);
	$document = $(document);
	$body = $('body');
	$overlay = $('#overlay');
	$tooltip = $('#tooltip');
	$menu = $('.dropdown.container');

	$document
		.one('touchstart', function() {
			$('html').removeClass('no-touch');

			$document.off('mouseenter mouseleave', '.th');
		})

		.on('keypress', '.type-zone', checkEnter)

		.on('click', '.dropdown.container .handle', function(e) {
			var $container = $(this).closest('.container');
			if(!$container.hasClass('open')) { $container.addClass('open');
			} else { $container.removeClass('open'); }
		})
		.on('mouseleave', '.dropdown.container', function() { if($(this).hasClass('open')) $(this).removeClass('open'); })
		.on('click', '.dropdown.container .handle[href=#]', function(e) { e.preventDefault(); })
		.on('mousedown', '.dropdown.container .close', function(e) {
			e.preventDefault();
			var $container = $(this).closest('.container');
			$container.removeClass('open');
		})

		.on('click', 'a.checkbox', function(e) {
			if($(this).attr('href') != '#') return true;

			e.preventDefault();

			var $checkbox = $(this).prev('input[type=checkbox]');

			$(this).toggleClass('checked');
			$checkbox.prop('checked', !$checkbox.prop('checked'));
		})

		.on('mouseenter', '.th', showTooltip)
		.on('mouseleave scroll', '.th', hideTooltip)
		.on('touchstart', '.th:not(.th-active)', showTooltip)
		.on('touchstart', '.th.th-active', hideTooltip)
		.on('mouseenter touchstart', '*[title]', function(e) {
			if($(this).closest('.note-editor').length) return false;
			$(this)
				.data('title', $(this).attr('title'))
				.removeAttr('title')
				.addClass('th');
			showTooltip(e);
		});

	$('.article img').each(function() {
		if($(this).parents('a').length > 0) { return false; } else {
			var a = $('');

			a
				.addClass('imagecontainer')
				.attr('href', $(this).attr('src'))
				.attr('target', '_blank');

			$(this).wrap(a);
		}
	});

	$('input[type=checkbox]').each(function() {
		var $label;

		$label = $(this).closest('label').length ? $(this).closest('label') : $label;
		$label = $(this).next('label').length ? $(this).next('label') : $label;
		$label = $(this).prev('label').length ? $(this).prev('label') : $label;

		var $a = $('')
			.attr('href', '#')
			.addClass('checkbox')
			.html('')

		if($(this).prop('checked')) $a.addClass('checked');

		$a
			.insertAfter($label)
			.find('.label').append('' + $.trim($label.text()) + '');
		$(this).insertAfter($label);

		$(this).hide();
		$label.remove();
	});

	$('.post.item .label.meta.comments')
		.on('mouseenter', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', $item.attr('href') + '#comments');
		})
		.on('mouseleave', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', $item.attr('href').replace('#comments', ''));
		});

	$('.post.item .label.meta.author.user')
		.on('mouseenter', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', $(this).data('user-id-url'));
		})
		.on('mouseleave', function() {
			var $item = $(this).closest('a.post.item');

			$item.attr('href', '/' + $item.data('post-id') + '/');
		});

	$('.section.article.form form')
		.on('submit', function() {
			$('#tagbox').tagging('add');
			$tags.val($tagbox.tagging('getTags').join(','));
		})
		.find('.category')
			.on('click', 'a.option', function(e) {
				e.preventDefault();
				$('.section.article.form .category option').filter('[value='+$(this).data('value')+']').prop('selected', true);
				$('.section.article.form .category .text').text($(this).text());
				$(this).closest('.dropdown.container').removeClass('open');
			})
			.each(function() {
				var option, $handle = $(''), $dropdown = $('
'), $ul = $('
');

				option = $(this).find('select option');

				$('')
					.html(''+$(this).data('text')+'')
					.addClass('text')
					.appendTo($handle);

				$('')
					.addClass('icon')
					.appendTo($handle);

				$handle
					.attr('href', '#')
					.addClass('handle label meta')
					.appendTo($(this));

				$('')
					.addClass('close')
					.attr('href', '#')
					.appendTo($dropdown);

				$('
')
					.addClass('tip top')
					.html('')
					.appendTo($dropdown);

				option.each(function() {
					var $li = $('
'), $a = $(''), value = $(this).attr('value');

					if(value == '') return true;

					if($(this).prop('selected')) $(this).closest('.dropdown.container').find('.handle .text span').text($(this).text());

					$('')
						.text($(this).text())
						.appendTo($a);

					$a
						.attr('href', '#')
						.addClass('option')
						.data('value', value)
						.appendTo($li);

					switch($(this).text()) {
						case '번역':
							$li.addClass('scanlation');
							break;
						case '자막':
							$li.addClass('subtitles');
							break;
						case '정보':
							$li.addClass('news');
							break;
					}

					$li.appendTo($ul);
				});

				$ul.appendTo($dropdown);

				$dropdown
					.addClass('dropdown menu')
					.appendTo($(this));

				$(this).find('select').hide();
			});

	if(!browser.can.placeholder()) {
		$('#form_login .field input').each(function() {
			$('')
				.attr('for', $(this).attr('id'))
				.append(''+$(this).attr('placeholder')+'')
				.insertAfter($(this).addClass('noplaceholder'));
		});
	}

	$('#form_login')
		.on('load focusin focusout', '.field input.noplaceholder', function() {
			if(this.value == '') { $(this).next('label').show();
			} else { $(this).next('label').hide(); }
		})
		.on('keypress', '.field input.noplaceholder', function() {
			$(this).next('label').hide();
		});

	$('.vote a').on('click', function(e) {
		e.preventDefault();
		vote('p', $(this).parent('.vote').data('target-id'), $(this));
	});

	$('.section.board')
		.on('click', '.search.button .label.meta.search', function(e) {
			e.preventDefault();

			var $nav = $(this).closest('.nav.bottom');

			$nav
				.addClass('open')
				.removeClass('close');

		})
		.on('click', '.search.button .label.meta.delete', function(e) {
			e.preventDefault();

			var $nav = $(this).closest('.nav.bottom');

			$nav
				.removeClass('open')
				.addClass('close');
		});

	$('.tabs')
		.on('click', '.tab.header a:not(.current)', function(e) {
			e.preventDefault();

			var $container = $(this).closest('.tabs'),
				target = $(this).attr('href').replace('#', '.');

			$container.find('.section').hide();
			$container.find(target).show();

			$container.find('.tab.header a').removeClass('current');
			$(this).addClass('current');

		})
});

function csrfSafeMethod(method) { return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); }

$.ajaxSetup({
	beforeSend: function(xhr, settings) {
		if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
			xhr.setRequestHeader("X-CSRFToken", csrftoken);
		}
	}
});

function showTooltip(e) {
	var $target = $(e.target).closest('.th');
	var position = $target.offset();

	var targetHeight = $target.outerHeight();
	var targetWidth = $target.outerWidth();

	var offsetY = 0;
	var offsetX = 0;

	var top;
	var left;

	var height;
	var width;

	var margin = 10;

	$tooltip.find('.container p').html($target.data('title').replace(/\\n/gi, '
'));
	$tooltip.find('.tip').removeAttr('style');

	top = position.top + targetHeight;
	left = position.left + targetWidth/2;

	height = $tooltip.height();
	width = $tooltip.width();

	if(top + height + margin > $window.height() + $window.scrollTop()) offsetY = -1*(targetHeight + height);

	offsetX = $window.width() + $window.scrollLeft() - left;

	if(offsetX - (width/2 + margin) < 0) {
		offsetX = offsetX - (width/2 + margin);
		if(width/2 + offsetX < 0 ) return false;
	} else if(offsetX + (width/2 + margin) > 0) {
		offsetX = offsetX + width/2 + margin - $window.width();
		if(offsetX < 0) offsetX = 0;
		if(width/2 - offsetX < 0 ) return false;
	} else { offsetX = 0; }

	$('.th-active').removeClass('th-active');
	$target.addClass('th-active');

	$tooltip
		.css({
			'top': top + offsetY,
			'left': left - width/2 + offsetX
		})
		.show()
		.stop()
		.animate({'opacity': 1}, 100)
		.find('.tip')
			.hide()
			.css('margin-left', -2*offsetX)
			.filter((offsetY >= 0) ? '.top' : '.bottom').show();
}

function hideTooltip() {
	$('.th-active').removeClass('th-active');

	$tooltip
		.stop()
		.animate({'opacity': 0}, 100, function() { $tooltip.hide(); });
}

function vote(type, target, button) {
	var vote, avote, work,
		$sibling_voted = button.siblings('.voted'),
		databox = {
			type: type,
			target: target,
			vote: null
		};

	work = button.hasClass('voted') ? '-' : '+';
	vote = button.hasClass('upvote') ? '+' : '-';
	avote = (vote == '+') ? '-' : '+';

	if(work == '+' && $sibling_voted.length) {
		$ajax_vote({type: type, target: target, vote: '-' + avote}, $sibling_voted)
			.done(function(data, status, xhr) {
				$ajax_vote({type: type, target: target, vote: work + vote})
					.done(function(data) { vote_callback(data, work, button); });

				vote_callback(data, '-', $sibling_voted);
			});
	} else { $ajax_vote({type: type, target: target, vote: work + vote}).done(function(data) { vote_callback(data, work, button); }); };
}

function vote_callback(data, work, button) {
	if(work == '+') { button.addClass('voted');
	} else { button.removeClass('voted'); }

	var $score = button.closest('.item').find('.meta.score');

	$score
		.attr('title', '+'+data.current.upvote+' / -'+data.current.downvote)
		.find('.text span').text(data.current.total);
}

function $ajax_vote(databox) {
	return $.ajax({
			type: 'POST',
			url: VOTE_AJAX_ENDPOINT,
			data: databox
		})
			.fail(function(xhr, status, error) {
				var response = xhr.responseJSON.status;
				switch(response) {
					case 'notauthenticated':
						alert('로그인한 사용자만 사용 가능한 기능입니다.');
						break;

					case 'alreadyhave':
						alert('이미 추천한 게시글 또는 댓글입니다.');
						break;

					default:
						alert('알 수 없는 문제가 발생하였습니다.');
						console.log(databox);
						console.log(xhr);
						console.log(status);
						console.log(error);
				}
			});
}
<|endoftext|>


<commit_before>
// author: Cesar A. Longoria II
// copyright (under MIT?): 2014-2015

/**
	key: stores these in a table, so a key makes later removal convenient.

	mousedown: event on mouse down.

	drag: function(e,dragData)
		- e: mousemove event
		- dragData
				. [key1,key2,...]: arrays of all offsets between element and document.
				. timing: array of times mousemove was called, in epoch millis.
				. length: function returning current length of offset data arrays.
				. diff: function(a,b,index) returning subtraction of offset array data
				    at index between nodes stored under dragData as keys a and b.
				. first: function(a,b) performing diff with index = 0.
				. last: function(a,b) performing diff with index = dragData.length-1.

	mouseup: event on mouse up.

	relatedNodes: nodes whichs' offsets from document to track and store under
	  their given key names in the dragData object passed to the drag function.

	params: eh... extraneous options and parameters...
		- selection: setting params.selection to true will allow the default
		    mousedown behavior.  Otherwise, the actual mousedown listener will
		    always end with { e.preventDefault(); return true; }.
*/

Element.prototype.addDragListener =
	function(key,mousedown,drag,mouseup,relatedNodes,params)
{
	if (!this._dragEvents)
		this._dragEvents = {};

	var startFunc;
	var dragFunc;
	var endFunc;

	var offsetArrays = {};
	var timingArray = [];

	for (var i in relatedNodes)
		offsetArrays[i] = [];

	var dragging = false;

	var self = this;

	var trueOffset = function(node)
	{
		var n = node;
		var off = {x:0,y:0};
		while (n.offsetParent)
		{
			off.x += parseFloat(n.offsetLeft);
			off.y += parseFloat(n.offsetTop);
			n = n.offsetParent;
		}
		return off;
	};

	this.dragging = function(){return dragging};

	this.addEventListener('mousedown',startFunc=function(e)
	{
		dragging = true;

		offsetArrays = {mouse:[]};
		timingArray = [];

		// NOTE: Can't seem to decide which key variable name to use...
		for (var k in relatedNodes)
			offsetArrays[k] = [];

		var pushCurrent = function(e)
		{
			for (var p in relatedNodes)
				offsetArrays[p].push(trueOffset(relatedNodes[p]));

			offsetArrays.mouse.push({
				x:parseFloat(e.pageX) + parseFloat(document.body.scrollLeft),
				y:parseFloat(e.pageY) + parseFloat(document.body.scrollTop)
			});

			timingArray.push(new Date().getTime());
		};

		document.addEventListener('mousemove',dragFunc=function(e)
		{
			pushCurrent(e);

			var dragData = {
				length:function(){return offsetArrays.mouse.length},
				diff:function(a,b,index)
				{
					return {
						x:this[a][index].x-this[b][index].x,
						y:this[a][index].y-this[b][index].y
					};
				},
				first:function(a,b){return this.diff(a,b,0)},
				last:function(a,b){return this.diff(a,b,this.length()-1)}
			};

			// NOTE: Oh boy.  Full copy of all arrays *every mousemove*.  This needs
			//   to be made into a read-only Array accessor of sorts.  I'll leave it
			//   at this, for now, though.
			for (var p in offsetArrays)
				dragData[p] = offsetArrays[p].slice(0);
			dragData.timing = timingArray.slice(0);

			drag(e,dragData);
		});

		document.addEventListener('mouseup',endFunc=function endFunc(e)
		{
			mouseup(e);

			dragging = false;
			document.removeEventListener('mousemove',dragFunc);
			document.removeEventListener('mouseup',endFunc);
		});

		self._dragEvents[key].dragFunc = dragFunc;
		self._dragEvents[key].endFunc = endFunc;

		// Run the mousedown function.
		pushCurrent(e);
		mousedown(e);

		// Prevent highlight selection, by default.
		if (!params || !params.selection)
		{
			e.preventDefault();
			return false;
		}
	});

	this._dragEvents[key] = {startFunc:startFunc};
};

Element.prototype.removeDragListener = function(key)
{
	var a = this._dragEvents[key];
	this.removeEventListener('mousedown',a.startFunc);
	document.removeEventListener('mousemove',a.dragFunc);
	document.removeEventListener('mouseup',a.endFunc);
}

<commit_msg>Minor comment indentation fix.<commit_after>
// author: Cesar A. Longoria II
// copyright (under MIT?): 2014-2015

/**
	key: stores these in a table, so a key makes later removal convenient.

	mousedown: event on mouse down.

	drag: function(e,dragData)
		- e: mousemove event
		- dragData
			  . [key1,key2,...]: arrays of all offsets between element and document.
			  . timing: array of times mousemove was called, in epoch millis.
			  . length: function returning current length of offset data arrays.
			  . diff: function(a,b,index) returning subtraction of offset array data
			      at index between nodes stored under dragData as keys a and b.
			  . first: function(a,b) performing diff with index = 0.
			  . last: function(a,b) performing diff with index = dragData.length-1.

	mouseup: event on mouse up.

	relatedNodes: nodes whichs' offsets from document to track and store under
	  their given key names in the dragData object passed to the drag function.

	params: eh... extraneous options and parameters...
		- selection: setting params.selection to true will allow the default
		    mousedown behavior.  Otherwise, the actual mousedown listener will
		    always end with { e.preventDefault(); return true; }.
*/

Element.prototype.addDragListener =
	function(key,mousedown,drag,mouseup,relatedNodes,params)
{
	if (!this._dragEvents)
		this._dragEvents = {};

	var startFunc;
	var dragFunc;
	var endFunc;

	var offsetArrays = {};
	var timingArray = [];

	for (var i in relatedNodes)
		offsetArrays[i] = [];

	var dragging = false;

	var self = this;

	var trueOffset = function(node)
	{
		var n = node;
		var off = {x:0,y:0};
		while (n.offsetParent)
		{
			off.x += parseFloat(n.offsetLeft);
			off.y += parseFloat(n.offsetTop);
			n = n.offsetParent;
		}
		return off;
	};

	this.dragging = function(){return dragging};

	this.addEventListener('mousedown',startFunc=function(e)
	{
		dragging = true;

		offsetArrays = {mouse:[]};
		timingArray = [];

		// NOTE: Can't seem to decide which key variable name to use...
		for (var k in relatedNodes)
			offsetArrays[k] = [];

		var pushCurrent = function(e)
		{
			for (var p in relatedNodes)
				offsetArrays[p].push(trueOffset(relatedNodes[p]));

			offsetArrays.mouse.push({
				x:parseFloat(e.pageX) + parseFloat(document.body.scrollLeft),
				y:parseFloat(e.pageY) + parseFloat(document.body.scrollTop)
			});

			timingArray.push(new Date().getTime());
		};

		document.addEventListener('mousemove',dragFunc=function(e)
		{
			pushCurrent(e);

			var dragData = {
				length:function(){return offsetArrays.mouse.length},
				diff:function(a,b,index)
				{
					return {
						x:this[a][index].x-this[b][index].x,
						y:this[a][index].y-this[b][index].y
					};
				},
				first:function(a,b){return this.diff(a,b,0)},
				last:function(a,b){return this.diff(a,b,this.length()-1)}
			};

			// NOTE: Oh boy.  Full copy of all arrays *every mousemove*.  This needs
			//   to be made into a read-only Array accessor of sorts.  I'll leave it
			//   at this, for now, though.
			for (var p in offsetArrays)
				dragData[p] = offsetArrays[p].slice(0);
			dragData.timing = timingArray.slice(0);

			drag(e,dragData);
		});

		document.addEventListener('mouseup',endFunc=function endFunc(e)
		{
			mouseup(e);

			dragging = false;
			document.removeEventListener('mousemove',dragFunc);
			document.removeEventListener('mouseup',endFunc);
		});

		self._dragEvents[key].dragFunc = dragFunc;
		self._dragEvents[key].endFunc = endFunc;

		// Run the mousedown function.
		pushCurrent(e);
		mousedown(e);

		// Prevent highlight selection, by default.
		if (!params || !params.selection)
		{
			e.preventDefault();
			return false;
		}
	});

	this._dragEvents[key] = {startFunc:startFunc};
};

Element.prototype.removeDragListener = function(key)
{
	var a = this._dragEvents[key];
	this.removeEventListener('mousedown',a.startFunc);
	document.removeEventListener('mousemove',a.dragFunc);
	document.removeEventListener('mouseup',a.endFunc);
}

<|endoftext|>