<commit_before>var Slowparse = (function() {
  function ParseError(parseInfo) {
    this.name = "ParseError";
    this.message = parseInfo.type;
    this.parseInfo = parseInfo;
  }

  ParseError.prototype = Error.prototype;

  function Stream(text) {
    this.text = text;
    this.pos = 0;
    this.tokenStart = 0;
  }

  Stream.prototype = {
    peek: function() {
      return this.text[this.pos];
    },
    next: function() {
      if (!this.end())
        return this.text[this.pos++];
    },
    end: function() {
      return (this.pos == this.text.length);
    },
    eat: function(matcher) {
      if (this.peek().match(matcher))
        return this.next();
    },
    eatWhile: function(matcher) {
      var wereAnyEaten = false;
      while (!this.end()) {
        if (this.eat(matcher))
          wereAnyEaten = true;
        else
          return wereAnyEaten;
      }
    },
    eatSpace: function() {
      return this.eatWhile(/[\s\n]/);
    },
    makeToken: function() {
      if (this.pos == this.tokenStart)
        return null;
      var token = {
        value: this.text.slice(this.tokenStart, this.pos),
        interval: {
          start: this.tokenStart,
          end: this.pos
        }
      };
      this.tokenStart = this.pos;
      return token;
    },
  };

  function parseHTML(html, domBuilder) {
    var stream = new Stream(html);
    var error = null;

    var helpers = {
      buildTextNode: function() {
        var token = stream.makeToken();
        if (token) {
          domBuilder.text(token.value, token.interval);
        }
      },
    };

    var modes = {
      text: function() {
        while (!stream.end()) {
          if (stream.peek() == '<') {
            helpers.buildTextNode();
            modes.startTag();
          } else
            stream.next();
        }

        helpers.buildTextNode();

        if (domBuilder.currentNode != domBuilder.fragment)
          throw new ParseError({
            type: "UNCLOSED_TAG",
            node: domBuilder.currentNode,
            position: stream.pos
          });
      },
      startTag: function() {
        if (stream.next() != '<')
          throw new Error('assertion failed, expected to be on "<"');

        stream.eatWhile(/[A-Za-z\/]/);
        var token = stream.makeToken();
        var tagName = token.value.slice(1);
        if (tagName[0] == '/') {
          domBuilder.currentNode.parseInfo.closeTag = {
            start: token.interval.start
          };
          if (tagName.slice(1).toLowerCase() !=
              domBuilder.currentNode.nodeName.toLowerCase())
            throw new Error("TODO: parse err for unmatching close tag");
          modes.endCloseTag();
        } else {
          if (!(tagName && tagName.match(/^[A-Za-z]+$/)))
            throw new ParseError({
              type: "INVALID_TAG_NAME",
              value: tagName,
              position: token.interval.start + 1
            });
          domBuilder.pushElement(tagName, {
            openTag: {
              start: token.interval.start
            }
          });

          if (!stream.end())
            modes.endOpenTag();
        }
      },
      quotedAttributeValue: function() {
        stream.eatSpace();
        stream.makeToken();
        if (stream.next() != '"')
          throw new Error("TODO: unquoted attributes are unimplemented");
        stream.eatWhile(/[^"]/);
        if (stream.next() != '"')
          throw new Error("TODO: parse error for unterminated attr value");
      },
      endCloseTag: function() {
        stream.eatSpace();
        if (stream.next() != '>') {
          if (stream.end())
            throw new Error("TODO: parse error for unterminated close tag");
          else
            throw new Error("TODO: parse error for garbage in close tag");
        }
        var end = stream.makeToken().interval.end;
        domBuilder.currentNode.parseInfo.closeTag.end = end;
        domBuilder.popElement();
      },
      attribute: function() {
        var nameTok = stream.makeToken();
        stream.eatSpace();
        if (stream.peek() == '=') {
          stream.next();
          modes.quotedAttributeValue();
          var valueTok = stream.makeToken();
          domBuilder.attribute(nameTok.value, valueTok.value.slice(1, -1), {
            name: nameTok.interval,
            value: valueTok.interval
          });
        } else
          throw new Error("TODO: boolean attributes are unimplemented");
      },
      endOpenTag: function() {
        while (!stream.end()) {
          if (stream.eatWhile(/[A-Za-z]/)) {
            modes.attribute();
          } else if (stream.eatSpace()) {
            stream.makeToken();
          } else if (stream.peek() == '>') {
            stream.next();
            var end = stream.makeToken().interval.end;
            domBuilder.currentNode.parseInfo.openTag.end = end;
            return;
          } else if (stream.end()) {
            throw new Error("TODO: parse err for unterminated open tag");
          } else
            throw new Error("TODO: don't know what to do with " +
                            stream.peek());
        }
      }
    };

    try {
      modes.text();
    } catch (e) {
      if (e.parseInfo) {
        error = e.parseInfo;
      } else
        throw e;
    }

    return {
      document: domBuilder.fragment,
      error: error
    };
  }

  function DOMBuilder(document) {
    this.document = document;
    this.fragment = document.createDocumentFragment();
    this.currentNode = this.fragment;
  }

  DOMBuilder.prototype = {
    pushElement: function(tagName, parseInfo) {
      var node = this.document.createElement(tagName);
      node.parseInfo = parseInfo;
      this.currentNode.appendChild(node);
      this.currentNode = node;
    },
    popElement: function() {
      this.currentNode = this.currentNode.parentNode;
    },
    attribute: function(name, value, parseInfo) {
      var attrNode = this.document.createAttribute(name);
      attrNode.parseInfo = parseInfo;
      attrNode.nodeValue = value;
      this.currentNode.attributes.setNamedItem(attrNode);
    },
    text: function(text, parseInfo) {
      var textNode = this.document.createTextNode(text);
      textNode.parseInfo = parseInfo;
      this.currentNode.appendChild(textNode);
    }
  };

  var Slowparse = {
    HTML: function(document, html) {
      return parseHTML(html, new DOMBuilder(document));
    }
  };

  return Slowparse;
})();
<commit_msg>TODO rewordings<commit_after>var Slowparse = (function() {
  function ParseError(parseInfo) {
    this.name = "ParseError";
    this.message = parseInfo.type;
    this.parseInfo = parseInfo;
  }

  ParseError.prototype = Error.prototype;

  function Stream(text) {
    this.text = text;
    this.pos = 0;
    this.tokenStart = 0;
  }

  Stream.prototype = {
    peek: function() {
      return this.text[this.pos];
    },
    next: function() {
      if (!this.end())
        return this.text[this.pos++];
    },
    end: function() {
      return (this.pos == this.text.length);
    },
    eat: function(matcher) {
      if (this.peek().match(matcher))
        return this.next();
    },
    eatWhile: function(matcher) {
      var wereAnyEaten = false;
      while (!this.end()) {
        if (this.eat(matcher))
          wereAnyEaten = true;
        else
          return wereAnyEaten;
      }
    },
    eatSpace: function() {
      return this.eatWhile(/[\s\n]/);
    },
    makeToken: function() {
      if (this.pos == this.tokenStart)
        return null;
      var token = {
        value: this.text.slice(this.tokenStart, this.pos),
        interval: {
          start: this.tokenStart,
          end: this.pos
        }
      };
      this.tokenStart = this.pos;
      return token;
    },
  };

  function parseHTML(html, domBuilder) {
    var stream = new Stream(html);
    var error = null;

    var helpers = {
      buildTextNode: function() {
        var token = stream.makeToken();
        if (token) {
          domBuilder.text(token.value, token.interval);
        }
      },
    };

    var modes = {
      text: function() {
        while (!stream.end()) {
          if (stream.peek() == '<') {
            helpers.buildTextNode();
            modes.startTag();
          } else
            stream.next();
        }

        helpers.buildTextNode();

        if (domBuilder.currentNode != domBuilder.fragment)
          throw new ParseError({
            type: "UNCLOSED_TAG",
            node: domBuilder.currentNode,
            position: stream.pos
          });
      },
      startTag: function() {
        if (stream.next() != '<')
          throw new Error('assertion failed, expected to be on "<"');

        stream.eatWhile(/[A-Za-z\/]/);
        var token = stream.makeToken();
        var tagName = token.value.slice(1);
        if (tagName[0] == '/') {
          domBuilder.currentNode.parseInfo.closeTag = {
            start: token.interval.start
          };
          if (tagName.slice(1).toLowerCase() !=
              domBuilder.currentNode.nodeName.toLowerCase())
            throw new Error("TODO: parse error for unmatching close tag");
          modes.endCloseTag();
        } else {
          if (!(tagName && tagName.match(/^[A-Za-z]+$/)))
            throw new ParseError({
              type: "INVALID_TAG_NAME",
              value: tagName,
              position: token.interval.start + 1
            });
          domBuilder.pushElement(tagName, {
            openTag: {
              start: token.interval.start
            }
          });

          if (!stream.end())
            modes.endOpenTag();
        }
      },
      quotedAttributeValue: function() {
        stream.eatSpace();
        stream.makeToken();
        if (stream.next() != '"')
          throw new Error("TODO: unquoted attributes are unimplemented");
        stream.eatWhile(/[^"]/);
        if (stream.next() != '"')
          throw new Error("TODO: parse error for unterminated attr value");
      },
      endCloseTag: function() {
        stream.eatSpace();
        if (stream.next() != '>') {
          if (stream.end())
            throw new Error("TODO: parse error for unterminated close tag");
          else
            throw new Error("TODO: parse error for garbage in close tag");
        }
        var end = stream.makeToken().interval.end;
        domBuilder.currentNode.parseInfo.closeTag.end = end;
        domBuilder.popElement();
      },
      attribute: function() {
        var nameTok = stream.makeToken();
        stream.eatSpace();
        if (stream.peek() == '=') {
          stream.next();
          modes.quotedAttributeValue();
          var valueTok = stream.makeToken();
          domBuilder.attribute(nameTok.value, valueTok.value.slice(1, -1), {
            name: nameTok.interval,
            value: valueTok.interval
          });
        } else
          throw new Error("TODO: boolean attributes are unimplemented");
      },
      endOpenTag: function() {
        while (!stream.end()) {
          if (stream.eatWhile(/[A-Za-z]/)) {
            modes.attribute();
          } else if (stream.eatSpace()) {
            stream.makeToken();
          } else if (stream.peek() == '>') {
            stream.next();
            var end = stream.makeToken().interval.end;
            domBuilder.currentNode.parseInfo.openTag.end = end;
            return;
          } else if (stream.end()) {
            throw new Error("TODO: parse error for unterminated open tag");
          } else
            throw new Error("TODO: parse error for unexpected garbage: " +
                            stream.peek());
        }
      }
    };

    try {
      modes.text();
    } catch (e) {
      if (e.parseInfo) {
        error = e.parseInfo;
      } else
        throw e;
    }

    return {
      document: domBuilder.fragment,
      error: error
    };
  }

  function DOMBuilder(document) {
    this.document = document;
    this.fragment = document.createDocumentFragment();
    this.currentNode = this.fragment;
  }

  DOMBuilder.prototype = {
    pushElement: function(tagName, parseInfo) {
      var node = this.document.createElement(tagName);
      node.parseInfo = parseInfo;
      this.currentNode.appendChild(node);
      this.currentNode = node;
    },
    popElement: function() {
      this.currentNode = this.currentNode.parentNode;
    },
    attribute: function(name, value, parseInfo) {
      var attrNode = this.document.createAttribute(name);
      attrNode.parseInfo = parseInfo;
      attrNode.nodeValue = value;
      this.currentNode.attributes.setNamedItem(attrNode);
    },
    text: function(text, parseInfo) {
      var textNode = this.document.createTextNode(text);
      textNode.parseInfo = parseInfo;
      this.currentNode.appendChild(textNode);
    }
  };

  var Slowparse = {
    HTML: function(document, html) {
      return parseHTML(html, new DOMBuilder(document));
    }
  };

  return Slowparse;
})();
<|endoftext|>


<commit_before>dojo.provide("dojox.grid._ViewManager");

dojo.declare('dojox.grid._ViewManager', null, {
	// summary:
	//		A collection of grid views. Owned by grid and used internally for managing grid views.
	// description:
	//		Grid creates views automatically based on grid's layout structure.
	//		Users should typically not need to access individual views or the views collection directly.
	constructor: function(inGrid){
		this.grid = inGrid;
	},

	defaultWidth: 200,

	views: [],

	// operations
	resize: function(){
		this.onEach("resize");
	},

	render: function(){
		this.onEach("render");
	},

	// views
	addView: function(inView){
		inView.idx = this.views.length;
		this.views.push(inView);
	},

	destroyViews: function(){
		for(var i=0, v; v=this.views[i]; i++){
			v.destroy();
		}
		this.views = [];
	},

	getContentNodes: function(){
		var nodes = [];
		for(var i=0, v; v=this.views[i]; i++){
			nodes.push(v.contentNode);
		}
		return nodes;
	},

	forEach: function(inCallback){
		for(var i=0, v; v=this.views[i]; i++){
			inCallback(v, i);
		}
	},

	onEach: function(inMethod, inArgs){
		inArgs = inArgs || [];
		for(var i=0, v; v=this.views[i]; i++){
			if(inMethod in v){
				v[inMethod].apply(v, inArgs);
			}
		}
	},

	// layout
	normalizeHeaderNodeHeight: function(){
		var rowNodes = [];
		for(var i=0, v; (v=this.views[i]); i++){
			if(v.headerContentNode.firstChild){
				rowNodes.push(v.headerContentNode);
			}
		}
		this.normalizeRowNodeHeights(rowNodes);
	},

	normalizeRowNodeHeights: function(inRowNodes){
		var h = 0;
		var currHeights = [];
		if(this.grid.rowHeight){
			h = this.grid.rowHeight;
		}else{
			if(inRowNodes.length <= 1){
				// no need to normalize if we are the only one...
				return;
			}
			for(var i=0, n; (n=inRowNodes[i]); i++){
				// We only care about the height - so don't use marginBox.  This
				// depends on the container not having any margin (which it shouldn't)
				// Also - we only look up the height if the cell doesn't have the
				// dojoxGridNonNormalizedCell class (like for row selectors)
				if(!dojo.hasClass(n, "dojoxGridNonNormalizedCell")){
					currHeights[i] = n.firstChild.offsetHeight;
					h =  Math.max(h, currHeights[i]);
				}
			}
			h = (h >= 0 ? h : 0);

			//Work around odd FF3 rendering bug: #8864.
			//A one px increase fixes FireFox 3's rounding bug for fractional font sizes.
			if(dojo.isFF>=3 && h){h++;}
		}
		for(i=0; (n=inRowNodes[i]); i++){
			if(currHeights[i] != h){
				n.firstChild.style.height = h + "px";
			}
		}
	},

	resetHeaderNodeHeight: function(){
		for(var i=0, v, n; (v=this.views[i]); i++){
			n = v.headerContentNode.firstChild;
			if(n){
				n.style.height = "";
			}
		}
	},

	renormalizeRow: function(inRowIndex){
		var rowNodes = [];
		for(var i=0, v, n; (v=this.views[i])&&(n=v.getRowNode(inRowIndex)); i++){
			n.firstChild.style.height = '';
			rowNodes.push(n);
		}
		this.normalizeRowNodeHeights(rowNodes);
	},

	getViewWidth: function(inIndex){
		return this.views[inIndex].getWidth() || this.defaultWidth;
	},

	// must be called after view widths are properly set or height can be miscalculated
	// if there are flex columns
	measureHeader: function(){
		// need to reset view header heights so they are properly measured.
		this.resetHeaderNodeHeight();
		this.forEach(function(inView){
			inView.headerContentNode.style.height = '';
		});
		var h = 0;
		// calculate maximum view header height
		this.forEach(function(inView){
			h = Math.max(inView.headerNode.offsetHeight, h);
		});
		return h;
	},

	measureContent: function(){
		var h = 0;
		this.forEach(function(inView){
			h = Math.max(inView.domNode.offsetHeight, h);
		});
		return h;
	},

	findClient: function(inAutoWidth){
		// try to use user defined client
		var c = this.grid.elasticView || -1;
		// attempt to find implicit client
		if(c < 0){
			for(var i=1, v; (v=this.views[i]); i++){
				if(v.viewWidth){
					for(i=1; (v=this.views[i]); i++){
						if(!v.viewWidth){
							c = i;
							break;
						}
					}
					break;
				}
			}
		}
		// client is in the middle by default
		if(c < 0){
			c = Math.floor(this.views.length / 2);
		}
		return c;
	},

	arrange: function(l, w){
		var i, v, vw, len = this.views.length;
		// find the client
		var c = (w <= 0 ? len : this.findClient());
		// layout views
		var setPosition = function(v, l){
			var ds = v.domNode.style;
			var hs = v.headerNode.style;

			if(!dojo._isBodyLtr()){
				ds.right = l + 'px';
				// fixed rtl, the scrollbar is on the right side in FF
				if (dojo.isMoz) {
					hs.right = l + v.getScrollbarWidth() + 'px';
					hs.width = parseInt(hs.width, 10) - v.getScrollbarWidth() + 'px';
				}else{
					hs.right = l + 'px';
				}
			}else{
				ds.left = l + 'px';
				hs.left = l + 'px';
			}
			ds.top = 0 + 'px';
			hs.top = 0;
		};
		// for views left of the client
		//BiDi TODO: The left and right should not appear in BIDI environment. Should be replaced with
		//leading and tailing concept.
		for(i=0; (v=this.views[i])&&(i<c); i++){
			// get width
			vw = this.getViewWidth(i);
			// process boxes
			v.setSize(vw, 0);
			setPosition(v, l);
			if(v.headerContentNode && v.headerContentNode.firstChild){
				vw = v.getColumnsWidth()+v.getScrollbarWidth();
			}else{
				vw = v.domNode.offsetWidth;
			}
			// update position
			l += vw;
		}
		// next view (is the client, i++ == c)
		i++;
		// start from the right edge
		var r = w;
		// for views right of the client (iterated from the right)
		for(var j=len-1; (v=this.views[j])&&(i<=j); j--){
			// get width
			vw = this.getViewWidth(j);
			// set size
			v.setSize(vw, 0);
			// measure in pixels
			vw = v.domNode.offsetWidth;
			// update position
			r -= vw;
			// set position
			setPosition(v, r);
		}
		if(c<len){
			v = this.views[c];
			// position the client box between left and right boxes
			vw = Math.max(1, r-l);
			// set size
			v.setSize(vw + 'px', 0);
			setPosition(v, l);
		}
		return l;
	},

	// rendering
	renderRow: function(inRowIndex, inNodes, skipRenorm){
		var rowNodes = [];
		for(var i=0, v, n, rowNode; (v=this.views[i])&&(n=inNodes[i]); i++){
			rowNode = v.renderRow(inRowIndex);
			n.appendChild(rowNode);
			rowNodes.push(rowNode);
		}
		if(!skipRenorm){
			this.normalizeRowNodeHeights(rowNodes);
		}
	},

	rowRemoved: function(inRowIndex){
		this.onEach("rowRemoved", [ inRowIndex ]);
	},

	// updating
	updateRow: function(inRowIndex, skipRenorm){
		for(var i=0, v; v=this.views[i]; i++){
			v.updateRow(inRowIndex);
		}
		if(!skipRenorm){
			this.renormalizeRow(inRowIndex);
		}
	},

	updateRowStyles: function(inRowIndex){
		this.onEach("updateRowStyles", [ inRowIndex ]);
	},

	// scrolling
	setScrollTop: function(inTop){
		var top = inTop;
		for(var i=0, v; v=this.views[i]; i++){
			top = v.setScrollTop(inTop);
			// Work around IE not firing scroll events that cause header offset
			// issues to occur.
			if(dojo.isIE && v.headerNode && v.scrollboxNode){
				v.headerNode.scrollLeft = v.scrollboxNode.scrollLeft;
			}
		}
		return top;
		//this.onEach("setScrollTop", [ inTop ]);
	},

	getFirstScrollingView: function(){
		// summary: Returns the first grid view with a scroll bar
		for(var i=0, v; (v=this.views[i]); i++){
			if(v.hasHScrollbar() || v.hasVScrollbar()){
				return v;
			}
		}
		return null;
	}

});
<commit_msg>Remove FF2 support.  Refs #9183, #8864<commit_after>dojo.provide("dojox.grid._ViewManager");

dojo.declare('dojox.grid._ViewManager', null, {
	// summary:
	//		A collection of grid views. Owned by grid and used internally for managing grid views.
	// description:
	//		Grid creates views automatically based on grid's layout structure.
	//		Users should typically not need to access individual views or the views collection directly.
	constructor: function(inGrid){
		this.grid = inGrid;
	},

	defaultWidth: 200,

	views: [],

	// operations
	resize: function(){
		this.onEach("resize");
	},

	render: function(){
		this.onEach("render");
	},

	// views
	addView: function(inView){
		inView.idx = this.views.length;
		this.views.push(inView);
	},

	destroyViews: function(){
		for(var i=0, v; v=this.views[i]; i++){
			v.destroy();
		}
		this.views = [];
	},

	getContentNodes: function(){
		var nodes = [];
		for(var i=0, v; v=this.views[i]; i++){
			nodes.push(v.contentNode);
		}
		return nodes;
	},

	forEach: function(inCallback){
		for(var i=0, v; v=this.views[i]; i++){
			inCallback(v, i);
		}
	},

	onEach: function(inMethod, inArgs){
		inArgs = inArgs || [];
		for(var i=0, v; v=this.views[i]; i++){
			if(inMethod in v){
				v[inMethod].apply(v, inArgs);
			}
		}
	},

	// layout
	normalizeHeaderNodeHeight: function(){
		var rowNodes = [];
		for(var i=0, v; (v=this.views[i]); i++){
			if(v.headerContentNode.firstChild){
				rowNodes.push(v.headerContentNode);
			}
		}
		this.normalizeRowNodeHeights(rowNodes);
	},

	normalizeRowNodeHeights: function(inRowNodes){
		var h = 0;
		var currHeights = [];
		if(this.grid.rowHeight){
			h = this.grid.rowHeight;
		}else{
			if(inRowNodes.length <= 1){
				// no need to normalize if we are the only one...
				return;
			}
			for(var i=0, n; (n=inRowNodes[i]); i++){
				// We only care about the height - so don't use marginBox.  This
				// depends on the container not having any margin (which it shouldn't)
				// Also - we only look up the height if the cell doesn't have the
				// dojoxGridNonNormalizedCell class (like for row selectors)
				if(!dojo.hasClass(n, "dojoxGridNonNormalizedCell")){
					currHeights[i] = n.firstChild.offsetHeight;
					h =  Math.max(h, currHeights[i]);
				}
			}
			h = (h >= 0 ? h : 0);

			//Work around odd FF3 rendering bug: #8864.
			//A one px increase fixes FireFox 3's rounding bug for fractional font sizes.
			if(dojo.isMoz && h){h++;}
		}
		for(i=0; (n=inRowNodes[i]); i++){
			if(currHeights[i] != h){
				n.firstChild.style.height = h + "px";
			}
		}
	},

	resetHeaderNodeHeight: function(){
		for(var i=0, v, n; (v=this.views[i]); i++){
			n = v.headerContentNode.firstChild;
			if(n){
				n.style.height = "";
			}
		}
	},

	renormalizeRow: function(inRowIndex){
		var rowNodes = [];
		for(var i=0, v, n; (v=this.views[i])&&(n=v.getRowNode(inRowIndex)); i++){
			n.firstChild.style.height = '';
			rowNodes.push(n);
		}
		this.normalizeRowNodeHeights(rowNodes);
	},

	getViewWidth: function(inIndex){
		return this.views[inIndex].getWidth() || this.defaultWidth;
	},

	// must be called after view widths are properly set or height can be miscalculated
	// if there are flex columns
	measureHeader: function(){
		// need to reset view header heights so they are properly measured.
		this.resetHeaderNodeHeight();
		this.forEach(function(inView){
			inView.headerContentNode.style.height = '';
		});
		var h = 0;
		// calculate maximum view header height
		this.forEach(function(inView){
			h = Math.max(inView.headerNode.offsetHeight, h);
		});
		return h;
	},

	measureContent: function(){
		var h = 0;
		this.forEach(function(inView){
			h = Math.max(inView.domNode.offsetHeight, h);
		});
		return h;
	},

	findClient: function(inAutoWidth){
		// try to use user defined client
		var c = this.grid.elasticView || -1;
		// attempt to find implicit client
		if(c < 0){
			for(var i=1, v; (v=this.views[i]); i++){
				if(v.viewWidth){
					for(i=1; (v=this.views[i]); i++){
						if(!v.viewWidth){
							c = i;
							break;
						}
					}
					break;
				}
			}
		}
		// client is in the middle by default
		if(c < 0){
			c = Math.floor(this.views.length / 2);
		}
		return c;
	},

	arrange: function(l, w){
		var i, v, vw, len = this.views.length;
		// find the client
		var c = (w <= 0 ? len : this.findClient());
		// layout views
		var setPosition = function(v, l){
			var ds = v.domNode.style;
			var hs = v.headerNode.style;

			if(!dojo._isBodyLtr()){
				ds.right = l + 'px';
				// fixed rtl, the scrollbar is on the right side in FF
				if (dojo.isMoz) {
					hs.right = l + v.getScrollbarWidth() + 'px';
					hs.width = parseInt(hs.width, 10) - v.getScrollbarWidth() + 'px';
				}else{
					hs.right = l + 'px';
				}
			}else{
				ds.left = l + 'px';
				hs.left = l + 'px';
			}
			ds.top = 0 + 'px';
			hs.top = 0;
		};
		// for views left of the client
		//BiDi TODO: The left and right should not appear in BIDI environment. Should be replaced with
		//leading and tailing concept.
		for(i=0; (v=this.views[i])&&(i<c); i++){
			// get width
			vw = this.getViewWidth(i);
			// process boxes
			v.setSize(vw, 0);
			setPosition(v, l);
			if(v.headerContentNode && v.headerContentNode.firstChild){
				vw = v.getColumnsWidth()+v.getScrollbarWidth();
			}else{
				vw = v.domNode.offsetWidth;
			}
			// update position
			l += vw;
		}
		// next view (is the client, i++ == c)
		i++;
		// start from the right edge
		var r = w;
		// for views right of the client (iterated from the right)
		for(var j=len-1; (v=this.views[j])&&(i<=j); j--){
			// get width
			vw = this.getViewWidth(j);
			// set size
			v.setSize(vw, 0);
			// measure in pixels
			vw = v.domNode.offsetWidth;
			// update position
			r -= vw;
			// set position
			setPosition(v, r);
		}
		if(c<len){
			v = this.views[c];
			// position the client box between left and right boxes
			vw = Math.max(1, r-l);
			// set size
			v.setSize(vw + 'px', 0);
			setPosition(v, l);
		}
		return l;
	},

	// rendering
	renderRow: function(inRowIndex, inNodes, skipRenorm){
		var rowNodes = [];
		for(var i=0, v, n, rowNode; (v=this.views[i])&&(n=inNodes[i]); i++){
			rowNode = v.renderRow(inRowIndex);
			n.appendChild(rowNode);
			rowNodes.push(rowNode);
		}
		if(!skipRenorm){
			this.normalizeRowNodeHeights(rowNodes);
		}
	},

	rowRemoved: function(inRowIndex){
		this.onEach("rowRemoved", [ inRowIndex ]);
	},

	// updating
	updateRow: function(inRowIndex, skipRenorm){
		for(var i=0, v; v=this.views[i]; i++){
			v.updateRow(inRowIndex);
		}
		if(!skipRenorm){
			this.renormalizeRow(inRowIndex);
		}
	},

	updateRowStyles: function(inRowIndex){
		this.onEach("updateRowStyles", [ inRowIndex ]);
	},

	// scrolling
	setScrollTop: function(inTop){
		var top = inTop;
		for(var i=0, v; v=this.views[i]; i++){
			top = v.setScrollTop(inTop);
			// Work around IE not firing scroll events that cause header offset
			// issues to occur.
			if(dojo.isIE && v.headerNode && v.scrollboxNode){
				v.headerNode.scrollLeft = v.scrollboxNode.scrollLeft;
			}
		}
		return top;
		//this.onEach("setScrollTop", [ inTop ]);
	},

	getFirstScrollingView: function(){
		// summary: Returns the first grid view with a scroll bar
		for(var i=0, v; (v=this.views[i]); i++){
			if(v.hasHScrollbar() || v.hasVScrollbar()){
				return v;
			}
		}
		return null;
	}

});
<|endoftext|>


<commit_before>/* vim: ts=4:sw=4:expandtab
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {
  'use strict';

   window.Whisper = window.Whisper || {};

  var Conversation = Whisper.Conversation = Backbone.Model.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    defaults: function() {
      var timestamp = new Date().getTime();
      return {
        unreadCount : 0,
        timestamp   : timestamp,
        active_at   : timestamp
      };
    },

    initialize: function() {
        this.messageCollection = new Whisper.MessageCollection();
    },

    validate: function(attributes, options) {
      var required = ['type', 'timestamp'];
      var missing = _.filter(required, function(attr) { return !attributes[attr]; });
      if (missing.length) { return "Conversation must have " + missing; }
    },

    sendMessage: function(body, attachments) {
        var now = Date.now();
        var message = this.messageCollection.add({
            body           : body,
            conversationId : this.id,
            type           : 'outgoing',
            attachments    : attachments,
            sent_at        : now,
            received_at    : now
        });
        message.save();

        this.save({
            unreadCount : 0,
            active_at   : now,
            timestamp   : now,
            lastMessage : body
        });

        var sendFunc;
        if (this.get('type') == 'private') {
            sendFunc = textsecure.messaging.sendMessageToNumber;
        }
        else {
            sendFunc = textsecure.messaging.sendMessageToGroup;
        }
        sendFunc(this.get('id'), body, attachments, now).catch(function(errors) {
            var keyErrors = [];
            _.each(errors, function(e) {
                if (e.error.name === 'OutgoingIdentityKeyError') {
                    e.error.args.push(message.id);
                    keyErrors.push(e.error);
                }
            });
            if (keyErrors.length) {
                message.save({ errors : keyErrors }).then(function() {
                    extension.trigger('message', message); // notify frontend listeners
                });
            } else {
                throw errors;
            }
        });
    },

    receiveMessage: function(decrypted) {
        var conversation = this;
        var timestamp = decrypted.pushMessage.timestamp.toNumber();
        var m = this.messageCollection.add({
            body           : decrypted.message.body,
            timestamp      : timestamp,
            conversationId : this.id,
            attachments    : decrypted.message.attachments,
            type           : 'incoming',
            sender         : decrypted.pushMessage.source
        });

        if (timestamp > this.get('timestamp')) {
          this.set('timestamp', timestamp);
        }
        this.save({unreadCount: this.get('unreadCount') + 1, active: true});

        return new Promise(function (resolve) { m.save().then(resolve(m)) });
    },

    fetchMessages: function(options) {
        return this.messageCollection.fetchConversation(this.id, options);
    },

    archive: function() {
        this.unset('active_at');
    },

    destroyMessages: function() {
        var models = this.messageCollection.models;
        this.messageCollection.reset([]);
        _.each(models, function(message) { message.destroy(); });
        this.archive();
        return this.save();
    },

    getTitle: function() {
        return this.get('name') || this.get('members') || this.id;
    }
  });

  Whisper.ConversationCollection = Backbone.Collection.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    model: Conversation,

    comparator: function(m) {
      return -m.get('timestamp');
    },

    createGroup: function(recipients, name, avatar) {
      var attributes = {};
      attributes = {
        name      : name,
        members   : recipients,
        type      : 'group',
        avatar    : avatar
      };
      var conversation = this.add(attributes, {merge: true});
      return textsecure.messaging.createGroup(recipients, name, avatar).then(function(groupId) {
        conversation.save({
          id      : getString(groupId),
          groupId : getString(groupId)
        });
        return conversation;
      });
    },

    findOrCreateForRecipient: function(recipient) {
      var attributes = {};
      attributes = {
        id        : recipient,
        name      : recipient,
        type      : 'private',
      };
      var conversation = this.add(attributes, {merge: true});
      conversation.save();
      return conversation;
    },

    addIncomingMessage: function(decrypted) {
      var attributes = {};
      if (decrypted.message.group) {
        attributes = {
          id         : decrypted.message.group.id,
          groupId    : decrypted.message.group.id,
          name       : decrypted.message.group.name || 'New group',
          type       : 'group',
        };
      } else {
        attributes = {
          id         : decrypted.pushMessage.source,
          name       : decrypted.pushMessage.source,
          type       : 'private'
        };
      }
      var conversation = this.add(attributes, {merge: true});
      return conversation.receiveMessage(decrypted);
    },

    destroyAll: function () {
        return Promise.all(this.models.map(function(m) {
            return new Promise(function(resolve, reject) {
                m.destroy().then(resolve).fail(reject);
            });
        }));
    },

    fetchGroups: function(number) {
        return this.fetch({
            index: {
                name: 'group',
                only: number
            }
        });
    },

    fetchActive: function(options) {
        return this.fetch(_.extend(options, {
            index: {
                name: 'inbox', // 'inbox' index on active_at
                order: 'desc'  // ORDER timestamp DESC
            }
            // TODO pagination/infinite scroll
            // limit: 10, offset: page*10,
        }));
    }
  });
})();
<commit_msg>Format phone numbers before saving conversations<commit_after>/* vim: ts=4:sw=4:expandtab
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {
  'use strict';

   window.Whisper = window.Whisper || {};

  var Conversation = Whisper.Conversation = Backbone.Model.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    defaults: function() {
      var timestamp = new Date().getTime();
      return {
        unreadCount : 0,
        timestamp   : timestamp,
        active_at   : timestamp
      };
    },

    initialize: function() {
        this.messageCollection = new Whisper.MessageCollection();
    },

    validate: function(attributes, options) {
        var required = ['type', 'timestamp'];
        var missing = _.filter(required, function(attr) { return !attributes[attr]; });
        if (missing.length) { return "Conversation must have " + missing; }

        if (this.get('type') === 'private') {
            var number = libphonenumber.util.verifyNumber(id);

            this.set({
                id: number,
                international_number: libphonenumber.format(number, libphonenumber.PhoneNumberFormat.INTERNATIONAL),
                national_number: libphonenumber.format(number, libphonenumber.PhoneNumberFormat.NATIONAL)
            });

        }
    },

    sendMessage: function(body, attachments) {
        var now = Date.now();
        var message = this.messageCollection.add({
            body           : body,
            conversationId : this.id,
            type           : 'outgoing',
            attachments    : attachments,
            sent_at        : now,
            received_at    : now
        });
        message.save();

        this.save({
            unreadCount : 0,
            active_at   : now,
            timestamp   : now,
            lastMessage : body
        });

        var sendFunc;
        if (this.get('type') == 'private') {
            sendFunc = textsecure.messaging.sendMessageToNumber;
        }
        else {
            sendFunc = textsecure.messaging.sendMessageToGroup;
        }
        sendFunc(this.get('id'), body, attachments, now).catch(function(errors) {
            var keyErrors = [];
            _.each(errors, function(e) {
                if (e.error.name === 'OutgoingIdentityKeyError') {
                    e.error.args.push(message.id);
                    keyErrors.push(e.error);
                }
            });
            if (keyErrors.length) {
                message.save({ errors : keyErrors }).then(function() {
                    extension.trigger('message', message); // notify frontend listeners
                });
            } else {
                throw errors;
            }
        });
    },

    receiveMessage: function(decrypted) {
        var conversation = this;
        var timestamp = decrypted.pushMessage.timestamp.toNumber();
        var m = this.messageCollection.add({
            body           : decrypted.message.body,
            timestamp      : timestamp,
            conversationId : this.id,
            attachments    : decrypted.message.attachments,
            type           : 'incoming',
            sender         : decrypted.pushMessage.source
        });

        if (timestamp > this.get('timestamp')) {
          this.set('timestamp', timestamp);
        }
        this.save({unreadCount: this.get('unreadCount') + 1, active: true});

        return new Promise(function (resolve) { m.save().then(resolve(m)) });
    },

    fetchMessages: function(options) {
        return this.messageCollection.fetchConversation(this.id, options);
    },

    archive: function() {
        this.unset('active_at');
    },

    destroyMessages: function() {
        var models = this.messageCollection.models;
        this.messageCollection.reset([]);
        _.each(models, function(message) { message.destroy(); });
        this.archive();
        return this.save();
    },

    getTitle: function() {
        return this.get('name') || this.get('members') || this.id;
    }
  });

  Whisper.ConversationCollection = Backbone.Collection.extend({
    database: Whisper.Database,
    storeName: 'conversations',
    model: Conversation,

    comparator: function(m) {
      return -m.get('timestamp');
    },

    createGroup: function(recipients, name, avatar) {
      var attributes = {};
      attributes = {
        name      : name,
        members   : recipients,
        type      : 'group',
        avatar    : avatar
      };
      var conversation = this.add(attributes, {merge: true});
      return textsecure.messaging.createGroup(recipients, name, avatar).then(function(groupId) {
        conversation.save({
          id      : getString(groupId),
          groupId : getString(groupId)
        });
        return conversation;
      });
    },

    findOrCreateForRecipient: function(recipient) {
      var attributes = {};
      attributes = {
        id        : recipient,
        name      : recipient,
        type      : 'private',
      };
      var conversation = this.add(attributes, {merge: true});
      conversation.save();
      return conversation;
    },

    addIncomingMessage: function(decrypted) {
      var attributes = {};
      if (decrypted.message.group) {
        attributes = {
          id         : decrypted.message.group.id,
          groupId    : decrypted.message.group.id,
          name       : decrypted.message.group.name || 'New group',
          type       : 'group',
        };
      } else {
        attributes = {
          id         : decrypted.pushMessage.source,
          name       : decrypted.pushMessage.source,
          type       : 'private'
        };
      }
      var conversation = this.add(attributes, {merge: true});
      return conversation.receiveMessage(decrypted);
    },

    destroyAll: function () {
        return Promise.all(this.models.map(function(m) {
            return new Promise(function(resolve, reject) {
                m.destroy().then(resolve).fail(reject);
            });
        }));
    },

    fetchGroups: function(number) {
        return this.fetch({
            index: {
                name: 'group',
                only: number
            }
        });
    },

    fetchActive: function(options) {
        return this.fetch(_.extend(options, {
            index: {
                name: 'inbox', // 'inbox' index on active_at
                order: 'desc'  // ORDER timestamp DESC
            }
            // TODO pagination/infinite scroll
            // limit: 10, offset: page*10,
        }));
    }
  });
})();
<|endoftext|>


<commit_before>/*global describe it before after  =*/

require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) {
    var expect = chai.expect;

    architect.resolveConfig([
        {
            packagePath : "plugins/c9.core/c9",
            workspaceId : "ubuntu/ip-10-35-77-180",
            startdate   : new Date(),
            debug       : true,
            hosted      : true,
            local       : false,
            davPrefix   : "/"
        },

        "plugins/c9.core/ext",
        "plugins/c9.core/http",
        "plugins/c9.core/util",
        "plugins/c9.ide.ui/lib_apf",
        {
            packagePath: "plugins/c9.core/settings",
            testing: true
        },
        {
            packagePath  : "plugins/c9.ide.ui/ui",
            staticPrefix : "plugins/c9.ide.ui"
        },
        "plugins/c9.ide.editors/document",
        "plugins/c9.ide.editors/undomanager",
        {
            packagePath: "plugins/c9.ide.editors/editors",
            defaultEditor: "ace"
        },
        "plugins/c9.ide.editors/editor",
        "plugins/c9.ide.editors/tabmanager",
        "plugins/c9.ide.editors/pane",
        "plugins/c9.ide.editors/tab",
        "plugins/c9.ide.ace/ace",
        {
            packagePath  : "plugins/c9.ide.ace.statusbar/statusbar",
            staticPrefix : "plugins/c9.ide.layout.classic"
        },
        "plugins/c9.ide.keys/commands",
        "plugins/c9.fs/proc",
        "plugins/c9.vfs.client/vfs_client",
        "plugins/c9.vfs.client/endpoint",
        "plugins/c9.ide.auth/auth",
        "plugins/c9.fs/fs",

        // Mock plugins
        {
            consumes : ["apf", "ui", "Plugin"],
            provides : [
                "commands", "menus", "layout", "watcher",
                "save", "preferences", "anims", "gotoline", "clipboard"
            ],
            setup    : expect.html.mocked
        },
        {
            consumes : ["tabManager", "ace"],
            provides : [],
            setup    : main
        }
    ], function (err, config) {
        if (err) throw err;
        var app = architect.createApp(config);
        app.on("service", function(name, plugin){ plugin.name = name; });
    });

    function main(options, imports, register) {
        var tabs    = imports.tabManager;
        var ace     = imports.ace;

        function getTabHtml(tab){
            return tab.pane.aml.getPage("editor::" + tab.editorType).$ext
        }

        expect.html.setConstructor(function(tab){
            if (typeof tab == "object")
                return tab.$ext;
        });

        describe('statusbar', function() {
            before(function(done){
                apf.config.setProperty("allow-select", false);
                apf.config.setProperty("allow-blur", false);
                tabs.getPanes()[0].focus();

                bar.$ext.style.background = "rgba(220, 220, 220, 0.93)";
                bar.$ext.style.position = "fixed";
                bar.$ext.style.left = "20px";
                bar.$ext.style.right = "20px";
                bar.$ext.style.bottom = "20px";
                bar.$ext.style.height = "33%";

                document.body.style.marginBottom = "33%";
                done();
            });

            describe("open", function(){
                this.timeout(10000);

                it('should open a pane with just an editor', function(done) {
                    tabs.openFile("/file.txt", function(err, tab){
                        expect(tabs.getTabs()).length(1);

                        var sb  = tab.document.getSession().statusBar;
                        var bar = sb.getElement("bar");
                        expect.html(bar, "rowcol").text("1:1");

                        tab.document.editor.ace.selectAll();
                        setTimeout(function(){
                            expect.html(bar, "rowcol sel").text("2:1");
                            expect.html(bar, "sel").text("23 Bytes");

                            done();
                        }, 100);
                    });
                });
                it('should handle multiple documents in the same pane', function(done) {
                    tabs.openFile("/listing.json", function(err, tab){
                        expect(tabs.getTabs()).length(2);

                        tab.activate();

                        setTimeout(function(){
                            var sb = tab.document.getSession().statusBar;
                            expect.html(sb.getElement("bar"), "caption").text("1:1");

                            done();
                        }, 100);
                    });
                });
            });
            describe("split(), pane.unload()", function(){
                it('should split a pane horizontally, making the existing pane the left one', function(done) {
                    var pane = tabs.focussedTab.pane;
                    var righttab = pane.hsplit(true);
                    tabs.focussedTab.attachTo(righttab);

                    setTimeout(function(){
                        expect.html(pane.aml, "pane").text("2:1");
                        expect.html(righttab.aml, "righttab").text("1:1");

                        done();
                    }, 100);
                });
//                it('should remove the left pane from a horizontal split', function(done) {
//                    var pane  = tabs.getPanes()[0];
//                    var tab = tabs.getPanes()[1].getTab();
//                    pane.unload();
//                    expect(tabs.getPanes()).length(1);
//                    expect(tabs.getTabs()).length(2);
//                    tabs.focusTab(tab);
//                    done();
//                });
            });
//            describe("Change Theme", function(){
//                this.timeout(10000);
//
//                it('should change a theme', function(done) {
//                    var editor = tabs.focussedTab.editor;
//                    ace.on("themeInit", function setTheme(){
//                        ace.off("theme.init", setTheme);
//                        expect.html(getTabHtml(tabs.focussedTab).childNodes[1]).className("ace-monokai");
//                        editor.setOption("theme", "ace/theme/textmate");
//                        done();
//                    });
//                    editor.setOption("theme", "ace/theme/monokai");
//                });
//            });

            // @todo test split api and menu

           if (!onload.remain){
               after(function(done){
                   tabs.unload();

                   document.body.style.marginBottom = "";
                   done();
               });
           }
        });

        onload && onload();
    }
});<commit_msg>Fixed statusbar tests<commit_after>/*global describe it before after  =*/

require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) {
    var expect = chai.expect;

    architect.resolveConfig([
        {
            packagePath : "plugins/c9.core/c9",
            workspaceId : "ubuntu/ip-10-35-77-180",
            startdate   : new Date(),
            debug       : true,
            hosted      : true,
            local       : false,
            davPrefix   : "/"
        },

        "plugins/c9.core/ext",
        "plugins/c9.core/http",
        "plugins/c9.core/util",
        "plugins/c9.ide.ui/lib_apf",
        "plugins/c9.ide.ui/menus",
        {
            packagePath: "plugins/c9.core/settings",
            testing: true
        },
        {
            packagePath  : "plugins/c9.ide.ui/ui",
            staticPrefix : "plugins/c9.ide.ui"
        },
        "plugins/c9.ide.editors/document",
        "plugins/c9.ide.editors/undomanager",
        {
            packagePath: "plugins/c9.ide.editors/editors",
            defaultEditor: "ace"
        },
        "plugins/c9.ide.editors/editor",
        "plugins/c9.ide.editors/tabmanager",
        "plugins/c9.ide.editors/pane",
        "plugins/c9.ide.editors/tab",
        "plugins/c9.ide.ace/ace",
        {
            packagePath  : "plugins/c9.ide.ace.statusbar/statusbar",
            staticPrefix : "plugins/c9.ide.layout.classic"
        },
        "plugins/c9.ide.keys/commands",
        "plugins/c9.fs/proc",
        "plugins/c9.vfs.client/vfs_client",
        "plugins/c9.vfs.client/endpoint",
        "plugins/c9.ide.auth/auth",
        "plugins/c9.fs/fs",

        // Mock plugins
        {
            consumes : ["apf", "ui", "Plugin"],
            provides : [
                "commands", "menus", "layout", "watcher",
                "save", "preferences", "anims", "gotoline", "clipboard",
                "dialog.alert", "auth.bootstrap", "ace.gotoline"
            ],
            setup    : expect.html.mocked
        },
        {
            consumes : ["tabManager", "ace"],
            provides : [],
            setup    : main
        }
    ], function (err, config) {
        if (err) throw err;
        var app = architect.createApp(config);
        app.on("service", function(name, plugin){ plugin.name = name; });
    });

    function main(options, imports, register) {
        var tabs    = imports.tabManager;
        var ace     = imports.ace;

        function getTabHtml(tab){
            return tab.pane.aml.getPage("editor::" + tab.editorType).$ext
        }

        expect.html.setConstructor(function(tab){
            if (typeof tab == "object")
                return tab.$ext;
        });

        describe('statusbar', function() {
            before(function(done){
                apf.config.setProperty("allow-select", false);
                apf.config.setProperty("allow-blur", false);
                tabs.getPanes()[0].focus();

                bar.$ext.style.background = "rgba(220, 220, 220, 0.93)";
                bar.$ext.style.position = "fixed";
                bar.$ext.style.left = "20px";
                bar.$ext.style.right = "20px";
                bar.$ext.style.bottom = "20px";
                bar.$ext.style.height = "33%";

                document.body.style.marginBottom = "33%";
                done();
            });

            describe("open", function(){
                this.timeout(10000);

                it('should open a pane with just an editor', function(done) {
                    tabs.openFile("/file.txt", function(err, tab){
                        expect(tabs.getTabs()).length(1);

                        var sb  = tab.document.getSession().statusBar;
                        var bar = sb.getElement("bar");
                        expect.html(bar, "rowcol").text("1:1");

                        tab.document.editor.ace.selectAll();
                        setTimeout(function(){
                            expect.html(bar, "rowcol sel").text("2:1");
                            expect.html(bar, "sel").text("23 Bytes");

                            done();
                        }, 100);
                    });
                });
                it('should handle multiple documents in the same pane', function(done) {
                    tabs.openFile("/listing.json", function(err, tab){
                        expect(tabs.getTabs()).length(2);

                        tab.activate();

                        setTimeout(function(){
                            var sb = tab.document.getSession().statusBar;
                            expect.html(sb.getElement("bar"), "caption").text("1:1");

                            done();
                        }, 100);
                    });
                });
            });
            describe("split(), pane.unload()", function(){
                it('should split a pane horizontally, making the existing pane the left one', function(done) {
                    var pane = tabs.focussedTab.pane;
                    var righttab = pane.hsplit(true);
                    tabs.focussedTab.attachTo(righttab);

                    setTimeout(function(){
                        expect.html(pane.aml, "pane").text("2:1");
                        expect.html(righttab.aml, "righttab").text("1:1");

                        done();
                    }, 100);
                });
//                it('should remove the left pane from a horizontal split', function(done) {
//                    var pane  = tabs.getPanes()[0];
//                    var tab = tabs.getPanes()[1].getTab();
//                    pane.unload();
//                    expect(tabs.getPanes()).length(1);
//                    expect(tabs.getTabs()).length(2);
//                    tabs.focusTab(tab);
//                    done();
//                });
            });
//            describe("Change Theme", function(){
//                this.timeout(10000);
//
//                it('should change a theme', function(done) {
//                    var editor = tabs.focussedTab.editor;
//                    ace.on("themeInit", function setTheme(){
//                        ace.off("theme.init", setTheme);
//                        expect.html(getTabHtml(tabs.focussedTab).childNodes[1]).className("ace-monokai");
//                        editor.setOption("theme", "ace/theme/textmate");
//                        done();
//                    });
//                    editor.setOption("theme", "ace/theme/monokai");
//                });
//            });

            // @todo test split api and menu

           if (!onload.remain){
               after(function(done){
                   tabs.unload();

                   document.body.style.marginBottom = "";
                   done();
               });
           }
        });

        onload && onload();
    }
});<|endoftext|>


<commit_before>var gulp = require('gulp');
var inquirer = require('inquirer');
var runSequence = require('run-sequence');
var shell = require('shelljs');
var request = require('request');
var fs = require('fs');
var chalk = require('chalk');
var moment = require('moment');
var s = require('underscore.string');

function printError(library) {
	console.log(chalk.red("Looks like you didn't install " + library + ". Make sure to install all prerequisites, as detailed in " + chalk.underline.red('https://github.com/BostonGlobe/slush-globegraphic#prerequisites.')));
}

function getGraphicName() {
	return s.slugify(shell.pwd().split('/').slice(-1)[0]) + '_' + moment().format('YYYY-M-D');
}

function initGitRepo() {
	shell.exec('git init');
	shell.exec('git ignore node_modules');
	shell.exec('git add .');
	shell.exec('git commit -m "first commit"');
}

function pushGitRepo() {
	shell.exec('git push -u origin master');
}

gulp.task('check-for-prerequisites', function(done) {

	['hub'].forEach(function(value) {
		if (!shell.which(value)) {
			printError(value);
			shell.exit(1);
		}
	});
	done();
});

gulp.task('download-globe-graphic-template', function(done) {

	request('https://github.com/russellgoldenberg/globe-graphic-template/archive/master.zip')
		.pipe(fs.createWriteStream('master.zip'))
		.on('close', function () {
			shell.exec('unzip -q master.zip');
			shell.exec('mv globe-graphic-template-master/* .');
			shell.exec('rm -rf master.zip globe-graphic-template-master src/css/main.css LICENSE README.md');
			shell.sed('-i', "'src/index.html'", "'dist/dev/index.html'", 'embed.html');
			done();
		});

});

gulp.task('copy-templates-directory', function(done) {

	gulp.src(__dirname + '/templates/**')
		.pipe(gulp.dest('./'))
		.on('finish', function() {
			console.log('This will take about ten seconds. Take two deep breaths.');
			shell.exec('mkdir src/html');
			shell.exec('mv src/index.html src/html/index.hbs');
			shell.exec('unzip -q node_modules.zip');
			shell.exec('rm -rf node_modules.zip');
			shell.sed('-i', '||YEAR||', new Date().getFullYear(), 'LICENSE');
			done();
		});

});

gulp.task('add-to-git-repo', function(done) {

	inquirer.prompt([
		{
			type: 'list',
			name: 'git',
			message: 'Add ' + getGraphicName() + ' to git repository?',
			choices: [
				'None',
				'GitHub',
				'Bitbucket'
			]
		}
	],
	function(answers) {

		switch(answers.git) {

			case 'None':
			break;
			case 'GitHub':
				initGitRepo();
				shell.exec('hub create BostonGlobe/' + getGraphicName() + ' -p');
				pushGitRepo();
			break;
			case 'Bitbucket':
				initGitRepo();
			break;
		}

		done();
	});

});

gulp.task('default', function(done) {

	runSequence(
		'check-for-prerequisites',
		'download-globe-graphic-template',
		'copy-templates-directory',
		'add-to-git-repo',
		done
	);

});<commit_msg>comment out bitbucket for now<commit_after>var gulp = require('gulp');
var inquirer = require('inquirer');
var runSequence = require('run-sequence');
var shell = require('shelljs');
var request = require('request');
var fs = require('fs');
var chalk = require('chalk');
var moment = require('moment');
var s = require('underscore.string');

function printError(library) {
	console.log(chalk.red("Looks like you didn't install " + library + ". Make sure to install all prerequisites, as detailed in " + chalk.underline.red('https://github.com/BostonGlobe/slush-globegraphic#prerequisites.')));
}

function getGraphicName() {
	return s.slugify(shell.pwd().split('/').slice(-1)[0]) + '_' + moment().format('YYYY-M-D');
}

function initGitRepo() {
	shell.exec('git init');
	shell.exec('git ignore node_modules');
	shell.exec('git add .');
	shell.exec('git commit -m "first commit"');
}

function pushGitRepo() {
	shell.exec('git push -u origin master');
}

gulp.task('check-for-prerequisites', function(done) {

	['hub'].forEach(function(value) {
		if (!shell.which(value)) {
			printError(value);
			shell.exit(1);
		}
	});
	done();
});

gulp.task('download-globe-graphic-template', function(done) {

	request('https://github.com/russellgoldenberg/globe-graphic-template/archive/master.zip')
		.pipe(fs.createWriteStream('master.zip'))
		.on('close', function () {
			shell.exec('unzip -q master.zip');
			shell.exec('mv globe-graphic-template-master/* .');
			shell.exec('rm -rf master.zip globe-graphic-template-master src/css/main.css LICENSE README.md');
			shell.sed('-i', "'src/index.html'", "'dist/dev/index.html'", 'embed.html');
			done();
		});

});

gulp.task('copy-templates-directory', function(done) {

	gulp.src(__dirname + '/templates/**')
		.pipe(gulp.dest('./'))
		.on('finish', function() {
			console.log('This will take about ten seconds. Take two deep breaths.');
			shell.exec('mkdir src/html');
			shell.exec('mv src/index.html src/html/index.hbs');
			shell.exec('unzip -q node_modules.zip');
			shell.exec('rm -rf node_modules.zip');
			shell.sed('-i', '||YEAR||', new Date().getFullYear(), 'LICENSE');
			done();
		});

});

gulp.task('add-to-git-repo', function(done) {

	inquirer.prompt([
		{
			type: 'list',
			name: 'git',
			message: 'Add ' + getGraphicName() + ' to git repository?',
			choices: [
				'None',
				'GitHub'
			]
		}
	],
	function(answers) {

		switch(answers.git) {

			case 'None':
			break;
			case 'GitHub':
				initGitRepo();
				shell.exec('hub create BostonGlobe/' + getGraphicName() + ' -p');
				pushGitRepo();
			break;
		}

		done();
	});

});

gulp.task('default', function(done) {

	runSequence(
		'check-for-prerequisites',
		'download-globe-graphic-template',
		'copy-templates-directory',
		'add-to-git-repo',
		done
	);

});<|endoftext|>


<commit_before>import React from "react";
import Link from "component/link";
import { FormRow } from "component/form";
import Modal from "../component/modal.js";
import lbry from "../lbry.js";

class ReportPage extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      submitting: false,
      modal: null,
    };
  }

  submitMessage() {
    if (this._messageArea.value) {
      this.setState({
        submitting: true,
      });
      lbry.report_bug({ message: this._messageArea.value }).then(() => {
        this.setState({
          submitting: false,
          modal: "submitted",
        });
      });
      this._messageArea.value = "";
    }
  }

  closeModal() {
    this.setState({
      modal: null,
    });
  }

  render() {
    return (

{__("Report an Issue")}

              {__(
                "Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!"
              )}

              <FormRow
                type="textarea"
                ref={t => (this._messageArea = t)}
                rows="10"
                name="message"
                placeholder={__("Description of your issue")}
              />

              <button
                onClick={event => {
                  this.submitMessage(event);
                }}
                className={
                  "button-block button-primary " +
                  (this.state.submitting ? "disabled" : "")
                }
              >
                {this.state.submitting
                  ? __("Submitting...")
                  : __("Submit Report")}
              </button>

{__("Developer?")}

            {__("You can also")}
            {" "}
            <Link
              href="https://github.com/lbryio/lbry/issues"
              label={__("submit an issue on GitHub")}
            />.

        <Modal
          isOpen={this.state.modal == "submitted"}
          contentLabel={__("Bug report submitted")}
          onConfirmed={event => {
            this.closeModal(event);
          }}
        >
          {__(
            "Your bug report has been submitted! Thank you for your feedback."
          )}
        </Modal>

    );
  }
}

export default ReportPage;
<commit_msg>fix error reporting<commit_after>import React from "react";
import Link from "component/link";
import { FormRow } from "component/form";
import { doShowSnackBar } from "actions/app";
import lbry from "../lbry.js";

class ReportPage extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      submitting: false,
      message: "",
    };
  }

  submitMessage() {
    const message = this.state.message;
    if (message) {
      this.setState({
        submitting: true,
      });
      lbry.report_bug({ message }).then(() => {
        this.setState({
          submitting: false,
        });

        // Display global notice
        const action = doShowSnackBar({
          message: __("Message received! Thanks for helping."),
          isError: false,
        });
        window.app.store.dispatch(action);
      });

      this.setState({ message: "" });
    }
  }

  onMessageChange(event) {
    this.setState({
      message: event.target.value,
    });
  }

  render() {
    return (

{__("Report an Issue")}

              {__(
                "Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!"
              )}

              <FormRow
                type="textarea"
                rows="10"
                name="message"
                value={this.state.message}
                onChange={event => {
                  this.onMessageChange(event);
                }}
                placeholder={__("Description of your issue")}
              />

              <button
                onClick={event => {
                  this.submitMessage(event);
                }}
                className={
                  "button-block button-primary " +
                  (this.state.submitting ? "disabled" : "")
                }
              >
                {this.state.submitting
                  ? __("Submitting...")
                  : __("Submit Report")}
              </button>

{__("Developer?")}

            {__("You can also")}
            {" "}
            <Link
              href="https://github.com/lbryio/lbry/issues"
              label={__("submit an issue on GitHub")}
            />.

    );
  }
}

export default ReportPage;
<|endoftext|>


<commit_before>/**
Simple username/password field detector and auto-filler.

When page is loaded, we try to find any input fields with specific name
attributes. If we find something useful, we dispatch an IPC event
'password-autofill' to signal that we want to check if there is auto-fill data
available.

When we receive back an IPC event 'password-autofill-match' with auto-fill
data, we do one of two things:

- If there's a single credentials match, we fill the input fields with that
  data.

- If there's more than one match, we add a focus event listener on the
  username/email fields that will display a small overlay div with available
  options. When user selects one of the options, we fill the input fields with
  credentials data from the selection.

This code doesn't work with JS-based forms. We don't listen to all DOM changes,
we expect the login form to be present in the HTML code at page load. We can
add a MutationObserver to the document, or DOMNodeInserted listener, but I
wanted to keep it lightweight and not impact browser performace too much.
*/

// "carbon:password"
const keyIcon = '<svg width="22px" height="22px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="vertical-align: -0.125em;-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M21 2a9 9 0 0 0-9 9a8.87 8.87 0 0 0 .39 2.61L2 24v6h6l10.39-10.39A9 9 0 0 0 30 11.74a8.77 8.77 0 0 0-1.65-6A9 9 0 0 0 21 2zm0 16a7 7 0 0 1-2-.3l-1.15-.35l-.85.85l-3.18 3.18L12.41 20L11 21.41l1.38 1.38l-1.59 1.59L9.41 23L8 24.41l1.38 1.38L7.17 28H4v-3.17L13.8 15l.85-.85l-.29-.95a7.14 7.14 0 0 1 3.4-8.44a7 7 0 0 1 10.24 6a6.69 6.69 0 0 1-1.09 4A7 7 0 0 1 21 18z" fill="currentColor"/><circle cx="22" cy="10" r="2" fill="currentColor"/></svg>'

// Ref to added unlock button.
var currentUnlockButton = null
var currentAutocompleteList = null

// Creates an unlock button element.
//
// - input: Input element to 'attach' unlock button to.
function createUnlockButton (input) {
  var inputRect = input.getBoundingClientRect()

  // Container.
  var unlockDiv = document.createElement('div')

  // Style.
  unlockDiv.style.width = '20px'
  unlockDiv.style.height = '20px'
  unlockDiv.style.zIndex = 999999999999999

  // Position.
  unlockDiv.style.position = 'absolute'
  unlockDiv.style.left = (window.scrollX + (inputRect.left + inputRect.width - 20 - 10)) + 'px'
  unlockDiv.style.top = (window.scrollY + (inputRect.top + (inputRect.height - 20) / 2.0)) + 'px'

  // Button.
  var button = document.createElement('div')

  // Button style.
  button.style.width = '20px'
  button.style.height = '20px'
  button.style.opacity = 0.7
  button.style.color = window.getComputedStyle(input).color
  button.style.transition = '0.1s color'
  button.innerHTML = keyIcon

  // Button hover.
  button.addEventListener('mouseenter', (event) => {
    button.style.opacity = 1.0
  })
  button.addEventListener('mouseleave', (event) => {
    button.style.opacity = 0.7
  })

  // Click event.
  button.addEventListener('mousedown', (event) => {
    event.preventDefault()
    checkInputs()
  })

  unlockDiv.appendChild(button)

  return unlockDiv
}

// Tries to find if an element has a specific attribute value that contains at
// least one of the values from 'matches' array.
function checkAttribute (element, attribute, matches) {
  const value = element.getAttribute(attribute)
  if (value == null) { return false }
  return matches.some(match => value.toLowerCase().includes(match))
}

// Gets all input fields on a page that contain at least one of the provided
// strings in their name attribute.
function getInputs (names, types) {
  const allFields = document.getElementsByTagName('input')

  const matchedFields = []
  for (const field of allFields) {
    // checkAttribute won't work here because type can be a property but not an attribute
    if (!types.includes(field.type)) {
      continue
    }

    // We expect the field to have either 'name', 'formcontrolname' or 'id' attribute
    // that we can use to identify it as a login form input field.
    if (checkAttribute(field, 'name', names) ||
        checkAttribute(field, 'formcontrolname', names) ||
        checkAttribute(field, 'id', names)) {
      matchedFields.push(field)
    }
  }

  return matchedFields
}

// Shortcut to get username fields from a page.
function getUsernameFields () {
  return getInputs(['user', 'name', 'mail', 'login', 'auth', 'identifier'], ['text', 'email'])
}

// Shortcut to get password fields from a page.
function getPasswordFields () {
  return getInputs(['pass'], ['password'])
}

// Removes credentials list overlay.
function removeAutocompleteList () {
  if (currentAutocompleteList && currentAutocompleteList.parentNode) {
    currentAutocompleteList.parentNode.removeChild(currentAutocompleteList)
  }
}

// Populates username/password fields with provided credentials.
function fillCredentials (credentials) {
  const { username, password } = credentials
  const inputEvents = ['keydown', 'keypress', 'keyup', 'input', 'change']

  for (const field of getUsernameFields()) {
    field.value = username
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }

  for (const field of getPasswordFields()) {
    field.value = password
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }
}

// Setup a focus/click listener on the username input fields.
//
// When those events happen, we add a small overlay with a list of matching
// credentials. Clicking on an item in a list populates the input fields with
// selected username/password pair.
//
// - element: input field to add a listener to
// - credentials: an array of { username, password } objects
function addFocusListener (element, credentials) {
  const inputRect = element.getBoundingClientRect()
  // Creates an options list container.
  function buildContainer () {
    const suggestionsDiv = document.createElement('div')
    suggestionsDiv.style = 'position: absolute; border: 1px solid #d4d4d4; z-index: 999999; border-bottom: none; background: #FFFFFF; transform: scale(0); opacity: 0; transform-origin: top left; transition: 0.15s; color: #000000;'
    suggestionsDiv.style.top = (inputRect.y + inputRect.height) + 'px'
    suggestionsDiv.style.left = (inputRect.x) + 'px'
    suggestionsDiv.id = 'password-autocomplete-list'
    requestAnimationFrame(function () {
      suggestionsDiv.style.opacity = '1'
      suggestionsDiv.style.transform = 'scale(1)'
    })
    return suggestionsDiv
  }

  // Adds an option row to the list container.
  function addOption (parent, username) {
    const suggestionItem = document.createElement('div')
    suggestionItem.innerHTML = username
    suggestionItem.style = 'padding: 10px; cursor: pointer; background-color: #fff; border-bottom: 1px solid #d4d4d4;'

    // Hover.
    suggestionItem.addEventListener('mouseenter', (event) => {
      suggestionItem.style.backgroundColor = '#e4e4e4'
    })
    suggestionItem.addEventListener('mouseleave', (event) => {
      suggestionItem.style.backgroundColor = '#fff'
    })

    // When user clicks on the suggestion, we populate the form inputs with selected credentials.
    suggestionItem.addEventListener('click', function (e) {
      const selectedCredentials = credentials.filter(el => { return el.username === username })[0]
      fillCredentials(selectedCredentials)
      removeAutocompleteList()
      element.focus()
    })

    parent.appendChild(suggestionItem)
  }

  // Creates autocomplete list and adds it below the activated field.
  function showAutocompleteList (e) {
    removeAutocompleteList()
    const container = buildContainer()
    for (const cred of credentials) {
      addOption(container, cred.username)
    }
    document.body.appendChild(container)
    currentAutocompleteList = container
  }

  element.addEventListener('focus', showAutocompleteList)
  element.addEventListener('click', showAutocompleteList)

  // Hide options overlay when user clicks out of the input field.
  document.addEventListener('click', function (e) {
    if (e.target !== element) {
      removeAutocompleteList()
    }
  })

  // Show the autocomplete list right away if field is already focused.
  // Userful for login pages which auto-focus the input field on page load.
  if (element === document.activeElement) {
    showAutocompleteList()
  }
}

function checkInputs () {
  if (getUsernameFields().length > 0 || getPasswordFields().length > 0) {
    ipc.send('password-autofill', document.location.hostname)
  }
}

function addUnlockButton (target) {
  if (getUsernameFields().includes(target) || getPasswordFields().includes(target)) {
    const unlockButton = createUnlockButton(target)
    document.body.appendChild(unlockButton)

    currentUnlockButton = unlockButton
  }
}

function checkInitialFocus () {
  if (getUsernameFields().includes(document.activeElement) || getPasswordFields().includes(document.activeElement)) {
    addUnlockButton(document.activeElement)
  }
}

function handleFocus (event) {
  addUnlockButton(event.target)
}

function handleBlur (event) {
  if (currentUnlockButton !== null && currentUnlockButton.parentElement != null) {
    currentUnlockButton.parentElement.removeChild(currentUnlockButton)
    currentUnlockButton = null
  }
}

// Handle credentials fetched from the backend. Credentials are expected to be
// an array of { username, password, manager } objects.
ipc.on('password-autofill-match', (event, data) => {
  if (data.hostname !== window.location.hostname) {
    throw new Error('password origin must match current page origin')
  }

  if (data.credentials.length === 0) {
    if (currentUnlockButton && currentUnlockButton.children.length > 0) {
      currentUnlockButton.children[0].style.color = 'rgb(180, 0, 0)'
    }
  } else if (data.credentials.length === 1) {
    fillCredentials(data.credentials[0])
  } else {
    const firstField = getUsernameFields().filter(field => field.type !== 'hidden')[0]
    addFocusListener(firstField, data.credentials)
    firstField.focus()
  }
})

// Trigger autofill check from keyboard shortcut.
ipc.on('password-autofill-shortcut', (event) => {
  checkInputs(true)
})

// Autofill enabled event handler. Initializes focus listeners for input fields.
ipc.on('password-autofill-enabled', (event) => {
  checkInitialFocus()

  // Add default focus event listeners.
  window.addEventListener('blur', handleBlur, true)
  window.addEventListener('focus', handleFocus, true)
})

// Check if password autofill is configured.
window.addEventListener('load', function (event) {
  ipc.send('password-autofill-check')
})

// send passwords back to the main process so they can be saved to storage

window.addEventListener('submit', function () {
  var usernameValues = getUsernameFields().map(f => f.value)
  var passwordValues = getPasswordFields().map(f => f.value)

  if (usernameValues.length > 0 || passwordValues.length > 0) {
    ipc.send('password-form-filled', [window.location.hostname, usernameValues, passwordValues])
  }
})
<commit_msg>don't show password save prompt if no credentials were detected<commit_after>/**
Simple username/password field detector and auto-filler.

When page is loaded, we try to find any input fields with specific name
attributes. If we find something useful, we dispatch an IPC event
'password-autofill' to signal that we want to check if there is auto-fill data
available.

When we receive back an IPC event 'password-autofill-match' with auto-fill
data, we do one of two things:

- If there's a single credentials match, we fill the input fields with that
  data.

- If there's more than one match, we add a focus event listener on the
  username/email fields that will display a small overlay div with available
  options. When user selects one of the options, we fill the input fields with
  credentials data from the selection.

This code doesn't work with JS-based forms. We don't listen to all DOM changes,
we expect the login form to be present in the HTML code at page load. We can
add a MutationObserver to the document, or DOMNodeInserted listener, but I
wanted to keep it lightweight and not impact browser performace too much.
*/

// "carbon:password"
const keyIcon = '<svg width="22px" height="22px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="vertical-align: -0.125em;-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M21 2a9 9 0 0 0-9 9a8.87 8.87 0 0 0 .39 2.61L2 24v6h6l10.39-10.39A9 9 0 0 0 30 11.74a8.77 8.77 0 0 0-1.65-6A9 9 0 0 0 21 2zm0 16a7 7 0 0 1-2-.3l-1.15-.35l-.85.85l-3.18 3.18L12.41 20L11 21.41l1.38 1.38l-1.59 1.59L9.41 23L8 24.41l1.38 1.38L7.17 28H4v-3.17L13.8 15l.85-.85l-.29-.95a7.14 7.14 0 0 1 3.4-8.44a7 7 0 0 1 10.24 6a6.69 6.69 0 0 1-1.09 4A7 7 0 0 1 21 18z" fill="currentColor"/><circle cx="22" cy="10" r="2" fill="currentColor"/></svg>'

// Ref to added unlock button.
var currentUnlockButton = null
var currentAutocompleteList = null

// Creates an unlock button element.
//
// - input: Input element to 'attach' unlock button to.
function createUnlockButton (input) {
  var inputRect = input.getBoundingClientRect()

  // Container.
  var unlockDiv = document.createElement('div')

  // Style.
  unlockDiv.style.width = '20px'
  unlockDiv.style.height = '20px'
  unlockDiv.style.zIndex = 999999999999999

  // Position.
  unlockDiv.style.position = 'absolute'
  unlockDiv.style.left = (window.scrollX + (inputRect.left + inputRect.width - 20 - 10)) + 'px'
  unlockDiv.style.top = (window.scrollY + (inputRect.top + (inputRect.height - 20) / 2.0)) + 'px'

  // Button.
  var button = document.createElement('div')

  // Button style.
  button.style.width = '20px'
  button.style.height = '20px'
  button.style.opacity = 0.7
  button.style.color = window.getComputedStyle(input).color
  button.style.transition = '0.1s color'
  button.innerHTML = keyIcon

  // Button hover.
  button.addEventListener('mouseenter', (event) => {
    button.style.opacity = 1.0
  })
  button.addEventListener('mouseleave', (event) => {
    button.style.opacity = 0.7
  })

  // Click event.
  button.addEventListener('mousedown', (event) => {
    event.preventDefault()
    checkInputs()
  })

  unlockDiv.appendChild(button)

  return unlockDiv
}

// Tries to find if an element has a specific attribute value that contains at
// least one of the values from 'matches' array.
function checkAttribute (element, attribute, matches) {
  const value = element.getAttribute(attribute)
  if (value == null) { return false }
  return matches.some(match => value.toLowerCase().includes(match))
}

// Gets all input fields on a page that contain at least one of the provided
// strings in their name attribute.
function getInputs (names, types) {
  const allFields = document.getElementsByTagName('input')

  const matchedFields = []
  for (const field of allFields) {
    // checkAttribute won't work here because type can be a property but not an attribute
    if (!types.includes(field.type)) {
      continue
    }

    // We expect the field to have either 'name', 'formcontrolname' or 'id' attribute
    // that we can use to identify it as a login form input field.
    if (checkAttribute(field, 'name', names) ||
        checkAttribute(field, 'formcontrolname', names) ||
        checkAttribute(field, 'id', names)) {
      matchedFields.push(field)
    }
  }

  return matchedFields
}

// Shortcut to get username fields from a page.
function getUsernameFields () {
  return getInputs(['user', 'name', 'mail', 'login', 'auth', 'identifier'], ['text', 'email'])
}

// Shortcut to get password fields from a page.
function getPasswordFields () {
  return getInputs(['pass'], ['password'])
}

// Removes credentials list overlay.
function removeAutocompleteList () {
  if (currentAutocompleteList && currentAutocompleteList.parentNode) {
    currentAutocompleteList.parentNode.removeChild(currentAutocompleteList)
  }
}

// Populates username/password fields with provided credentials.
function fillCredentials (credentials) {
  const { username, password } = credentials
  const inputEvents = ['keydown', 'keypress', 'keyup', 'input', 'change']

  for (const field of getUsernameFields()) {
    field.value = username
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }

  for (const field of getPasswordFields()) {
    field.value = password
    for (const event of inputEvents) {
      field.dispatchEvent(new Event(event, { bubbles: true }))
    }
  }
}

// Setup a focus/click listener on the username input fields.
//
// When those events happen, we add a small overlay with a list of matching
// credentials. Clicking on an item in a list populates the input fields with
// selected username/password pair.
//
// - element: input field to add a listener to
// - credentials: an array of { username, password } objects
function addFocusListener (element, credentials) {
  const inputRect = element.getBoundingClientRect()
  // Creates an options list container.
  function buildContainer () {
    const suggestionsDiv = document.createElement('div')
    suggestionsDiv.style = 'position: absolute; border: 1px solid #d4d4d4; z-index: 999999; border-bottom: none; background: #FFFFFF; transform: scale(0); opacity: 0; transform-origin: top left; transition: 0.15s; color: #000000;'
    suggestionsDiv.style.top = (inputRect.y + inputRect.height) + 'px'
    suggestionsDiv.style.left = (inputRect.x) + 'px'
    suggestionsDiv.id = 'password-autocomplete-list'
    requestAnimationFrame(function () {
      suggestionsDiv.style.opacity = '1'
      suggestionsDiv.style.transform = 'scale(1)'
    })
    return suggestionsDiv
  }

  // Adds an option row to the list container.
  function addOption (parent, username) {
    const suggestionItem = document.createElement('div')
    suggestionItem.innerHTML = username
    suggestionItem.style = 'padding: 10px; cursor: pointer; background-color: #fff; border-bottom: 1px solid #d4d4d4;'

    // Hover.
    suggestionItem.addEventListener('mouseenter', (event) => {
      suggestionItem.style.backgroundColor = '#e4e4e4'
    })
    suggestionItem.addEventListener('mouseleave', (event) => {
      suggestionItem.style.backgroundColor = '#fff'
    })

    // When user clicks on the suggestion, we populate the form inputs with selected credentials.
    suggestionItem.addEventListener('click', function (e) {
      const selectedCredentials = credentials.filter(el => { return el.username === username })[0]
      fillCredentials(selectedCredentials)
      removeAutocompleteList()
      element.focus()
    })

    parent.appendChild(suggestionItem)
  }

  // Creates autocomplete list and adds it below the activated field.
  function showAutocompleteList (e) {
    removeAutocompleteList()
    const container = buildContainer()
    for (const cred of credentials) {
      addOption(container, cred.username)
    }
    document.body.appendChild(container)
    currentAutocompleteList = container
  }

  element.addEventListener('focus', showAutocompleteList)
  element.addEventListener('click', showAutocompleteList)

  // Hide options overlay when user clicks out of the input field.
  document.addEventListener('click', function (e) {
    if (e.target !== element) {
      removeAutocompleteList()
    }
  })

  // Show the autocomplete list right away if field is already focused.
  // Userful for login pages which auto-focus the input field on page load.
  if (element === document.activeElement) {
    showAutocompleteList()
  }
}

function checkInputs () {
  if (getUsernameFields().length > 0 || getPasswordFields().length > 0) {
    ipc.send('password-autofill', document.location.hostname)
  }
}

function addUnlockButton (target) {
  if (getUsernameFields().includes(target) || getPasswordFields().includes(target)) {
    const unlockButton = createUnlockButton(target)
    document.body.appendChild(unlockButton)

    currentUnlockButton = unlockButton
  }
}

function checkInitialFocus () {
  if (getUsernameFields().includes(document.activeElement) || getPasswordFields().includes(document.activeElement)) {
    addUnlockButton(document.activeElement)
  }
}

function handleFocus (event) {
  addUnlockButton(event.target)
}

function handleBlur (event) {
  if (currentUnlockButton !== null && currentUnlockButton.parentElement != null) {
    currentUnlockButton.parentElement.removeChild(currentUnlockButton)
    currentUnlockButton = null
  }
}

// Handle credentials fetched from the backend. Credentials are expected to be
// an array of { username, password, manager } objects.
ipc.on('password-autofill-match', (event, data) => {
  if (data.hostname !== window.location.hostname) {
    throw new Error('password origin must match current page origin')
  }

  if (data.credentials.length === 0) {
    if (currentUnlockButton && currentUnlockButton.children.length > 0) {
      currentUnlockButton.children[0].style.color = 'rgb(180, 0, 0)'
    }
  } else if (data.credentials.length === 1) {
    fillCredentials(data.credentials[0])
  } else {
    const firstField = getUsernameFields().filter(field => field.type !== 'hidden')[0]
    addFocusListener(firstField, data.credentials)
    firstField.focus()
  }
})

// Trigger autofill check from keyboard shortcut.
ipc.on('password-autofill-shortcut', (event) => {
  checkInputs(true)
})

// Autofill enabled event handler. Initializes focus listeners for input fields.
ipc.on('password-autofill-enabled', (event) => {
  checkInitialFocus()

  // Add default focus event listeners.
  window.addEventListener('blur', handleBlur, true)
  window.addEventListener('focus', handleFocus, true)
})

// Check if password autofill is configured.
window.addEventListener('load', function (event) {
  ipc.send('password-autofill-check')
})

// send passwords back to the main process so they can be saved to storage

window.addEventListener('submit', function () {
  var usernameValues = getUsernameFields().map(f => f.value)
  var passwordValues = getPasswordFields().map(f => f.value)

  if (usernameValues.some(v => v.length > 0) || passwordValues.some(v => v.length > 0)) {
    ipc.send('password-form-filled', [window.location.hostname, usernameValues, passwordValues])
  }
})
<|endoftext|>


<commit_before>var gulp = require('gulp'),
    fs = require('fs'),
    path = require('path'),
    inflection = require('inflection'),
    install = require('gulp-install'),
    conflict = require('gulp-conflict'),
    template = require('gulp-template'),
    rename = require("gulp-rename"),
    inquirer = require('inquirer'),
    license = require('license'),
    gitConfig = require('git-config');

gulp.task('default', function(done) {
  gitConfig(function (err, gitConfig) {
    if (err) { return done(err); }

    license.licenseTypes(function(err, licenseTypes) {
      inquirer.prompt([
        {type: 'input', name: 'name', message: 'Project name', default: gulp.args.join('-')},
        {type: 'input', name: 'description', message: 'Project description', default: 'The best project ever.'},
        {type: 'input', name: 'author_name', message: 'Project author name', default: gitConfig.user.name},
        {type: 'input', name: 'author_email', message: 'Project author name', default: gitConfig.user.email},
        {type: 'input', name: 'github_user_name', message: 'GitHub user name', default: gitConfig.github.user},
        {type: 'list', name: 'licenseType', message: 'Choose your license type', choices: licenseTypes, default: 'mit'},
        {type: 'confirm', name: 'moveon', message: 'OK?'}
      ],

      function(answers) {
        if (!answers.moveon) { return done(); }

        answers.year = new Date().getFullYear();

        license.createLicense({licenseType: answers.licenseType, year: answers.year, organization: answers.author_name}, function(err, licenseString) {
          fs.writeFileSync(__dirname + '/templates/LICENSE', licenseString);
          answers.license = answers.licenseType.toUpperCase();

          gulp.src(__dirname + '/templates/**', {dot: true})
            .pipe(template(answers))
            .pipe(rename(function (file) {
              if (file.basename.indexOf('dot') === 0) {
                file.basename = file.basename.replace('dot', '');
              } else {
                file.basename = file.basename.replace('name', answers.name);
              }
            }))
            .pipe(conflict('./'))
            .pipe(gulp.dest('./'))
            .pipe(install())
            .on('finish', done);
        });
      });
    });
  });
});

gulp.task('generate', function(done) {
  inquirer.prompt([
    {type: 'input', name: 'path', message: 'Path (dirname + basename)', default: gulp.args.join('_')},
    {type: 'list', name: 'format', message: 'Choose a script format', choices: ['class', 'module'], default: 'class'},
    {type: 'confirm', name: 'moveon', message: 'OK?'}
  ],

  function(answers) {
    if (!answers.moveon) { return done(); }

    answers.path = answers.path.replace(/^\/+|\/+$/g, '');
    answers.basename = path.basename(answers.path, '.ts');
    answers.dirname = path.dirname(answers.path);
    answers.classifyName = inflection.classify(answers.basename);

    answers.relativeRootDir = path.relative('src/' + answers.dirname, process.cwd()) || '.';

    gulp.src(__dirname + '/templates-generate/**/*-' + answers.format + '.ts')
      .pipe(template(answers))
      .pipe(rename(function (file) {
        file.dirname = path.join(file.dirname, answers.dirname);
        file.basename = file.basename
          .replace('-' + answers.format, '')
          .replace('name', answers.basename);
      }))
      .pipe(conflict('./'))
      .pipe(gulp.dest('./'))
      .pipe(install())
      .on('finish', done);
  });
});
<commit_msg>Added safety checks to prevent errors when .gitconfig isn't configured.<commit_after>var gulp = require('gulp'),
    fs = require('fs'),
    path = require('path'),
    inflection = require('inflection'),
    install = require('gulp-install'),
    conflict = require('gulp-conflict'),
    template = require('gulp-template'),
    rename = require("gulp-rename"),
    inquirer = require('inquirer'),
    license = require('license'),
    gitConfig = require('git-config');

gulp.task('default', function(done) {
  gitConfig(function (err, gitConfig) {
    if (err) { return done(err); }

    license.licenseTypes(function(err, licenseTypes) {
      // Substitute empty values in case configuration isn't specified.
      var user = gitConfig.user ? gitConfig.user : {};
      var github = gitConfig.github ? gitConfig.github : {};

      inquirer.prompt([
        {type: 'input', name: 'name', message: 'Project name', default: gulp.args.join('-')},
        {type: 'input', name: 'description', message: 'Project description', default: 'The best project ever.'},
        {type: 'input', name: 'author_name', message: 'Project author name', default: user.name},
        {type: 'input', name: 'author_email', message: 'Project author name', default: user.email},
        {type: 'input', name: 'github_user_name', message: 'GitHub user name', default: github.user},
        {type: 'list', name: 'licenseType', message: 'Choose your license type', choices: licenseTypes, default: 'mit'},
        {type: 'confirm', name: 'moveon', message: 'OK?'}
      ],

      function(answers) {
        if (!answers.moveon) { return done(); }

        answers.year = new Date().getFullYear();

        license.createLicense({licenseType: answers.licenseType, year: answers.year, organization: answers.author_name}, function(err, licenseString) {
          fs.writeFileSync(__dirname + '/templates/LICENSE', licenseString);
          answers.license = answers.licenseType.toUpperCase();

          gulp.src(__dirname + '/templates/**', {dot: true})
            .pipe(template(answers))
            .pipe(rename(function (file) {
              if (file.basename.indexOf('dot') === 0) {
                file.basename = file.basename.replace('dot', '');
              } else {
                file.basename = file.basename.replace('name', answers.name);
              }
            }))
            .pipe(conflict('./'))
            .pipe(gulp.dest('./'))
            .pipe(install())
            .on('finish', done);
        });
      });
    });
  });
});

gulp.task('generate', function(done) {
  inquirer.prompt([
    {type: 'input', name: 'path', message: 'Path (dirname + basename)', default: gulp.args.join('_')},
    {type: 'list', name: 'format', message: 'Choose a script format', choices: ['class', 'module'], default: 'class'},
    {type: 'confirm', name: 'moveon', message: 'OK?'}
  ],

  function(answers) {
    if (!answers.moveon) { return done(); }

    answers.path = answers.path.replace(/^\/+|\/+$/g, '');
    answers.basename = path.basename(answers.path, '.ts');
    answers.dirname = path.dirname(answers.path);
    answers.classifyName = inflection.classify(answers.basename);

    answers.relativeRootDir = path.relative('src/' + answers.dirname, process.cwd()) || '.';

    gulp.src(__dirname + '/templates-generate/**/*-' + answers.format + '.ts')
      .pipe(template(answers))
      .pipe(rename(function (file) {
        file.dirname = path.join(file.dirname, answers.dirname);
        file.basename = file.basename
          .replace('-' + answers.format, '')
          .replace('name', answers.basename);
      }))
      .pipe(conflict('./'))
      .pipe(gulp.dest('./'))
      .pipe(install())
      .on('finish', done);
  });
});
<|endoftext|>


<commit_before>'use strict';

const gulp = require('gulp');
const install = require('gulp-install');
const rename = require('gulp-rename');
const template = require('gulp-template');
const conflict = require('gulp-conflict');
const inquirer = require('inquirer');
const _ = require('underscore.string');

gulp.task('default', done => {
    inquirer.prompt([
        {
            type: 'input',
            name: 'componentName',
            message: 'What is the name of component?'
        },
        {
            type: 'input',
            name: 'componentVersion',
            message: 'What is the version?',
            default: '0.1.0'
        }
    ]).then(answers => {
        if (!answers.componentName) {
            return done();
        }

        let files = [
            __dirname + '/templates/**/*'
        ];

        answers.componentName = _.humanize(answers.componentName);
        answers.componentSlugName = _.slugify(answers.componentName);

        return gulp.src(files)
            .pipe(template(answers))
            .pipe(rename(file => {
                console.log(file);
                if (file.basename[0] === '_') {
                    file.basename = '.' + file.basename.slice(1);
                } else if (file.dirname[0] === '_') {
                    file.dirname = '.' + file.dirname.slice(1);
                }
            }))
            .pipe(conflict('./', {
                defaultChoice: 'd'
            }))
            .pipe(gulp.dest('./'))
            .pipe(install())
            .on('finish', function(){
                done();
            });
    });
});<commit_msg>Remove debugger<commit_after>const gulp = require('gulp');
const install = require('gulp-install');
const rename = require('gulp-rename');
const template = require('gulp-template');
const conflict = require('gulp-conflict');
const inquirer = require('inquirer');
const _ = require('underscore.string');

gulp.task('default', done => {
    inquirer.prompt([
        {
            type: 'input',
            name: 'componentName',
            message: 'What is the name of component?'
        },
        {
            type: 'input',
            name: 'componentVersion',
            message: 'What is the version?',
            default: '0.1.0'
        }
    ]).then(answers => {
        if (!answers.componentName) {
            return done();
        }

        let files = [
            __dirname + '/templates/**/*'
        ];

        answers.componentName = _.humanize(answers.componentName);
        answers.componentSlugName = _.slugify(answers.componentName);

        return gulp.src(files)
            .pipe(template(answers))
            .pipe(rename(file => {
                if (file.basename[0] === '_') {
                    file.basename = '.' + file.basename.slice(1);
                } else if (file.dirname[0] === '_') {
                    file.dirname = '.' + file.dirname.slice(1);
                }
            }))
            .pipe(conflict('./', {
                defaultChoice: 'd'
            }))
            .pipe(gulp.dest('./'))
            .pipe(install())
            .on('finish', function(){
                done();
            });
    });
});<|endoftext|>