<commit_before>/*
This module provides a direct interface with the ctpubsub backend. Here's how to use it.

	CT.pubsub.connect(host, port, uname)
	CT.pubsub.publish(channel, message)
	CT.pubsub.subscribe(channel)
	CT.pubsub.unsubscribe(channel)
	CT.pubsub.pm(user, message)
	CT.pubsub.set_cb(action, cb)
	CT.pubsub.set_reconnect(bool)
	CT.pubsub.isInitialized() (returns bool)
*/

CT.pubsub = {
	"_": {
		"ws": null,
		"args": null,
		"open": false,
		"initialized": false,
		"autohistory": false,
		"reconnect": true,
		"reconnect_interval": 250,
		"protocol": location == "http:" && "ws" || "wss",
		"log": CT.log.getLogger("CT.pubsub"),
		"queue": [],
		"channels": {},
		"cb": { // default callbacks -- override w/ set_cb()
			"snapshot": CT.log.getLogger("CT.pubsub|snapshot"),
			"pm": CT.log.getLogger("CT.pubsub|pm"),
			"message": CT.log.getLogger("CT.pubsub|message"),
			"subscribe": CT.log.getLogger("CT.pubsub|subscribe"),
			"join": CT.log.getLogger("CT.pubsub|join"),
			"leave": CT.log.getLogger("CT.pubsub|leave"),
			"open": CT.log.getLogger("CT.pubsub|open"),
			"close": CT.log.getLogger("CT.pubsub|close"),
			"wserror": CT.log.getLogger("CT.pubsub|wserror"), // websocket -- almost same as close
			"error": CT.log.getLogger("CT.pubsub|error") // non-fatal pubsub error
		},
		"process": { // pubsub events
			"channel": function(data) {
				CT.pubsub._.channels[data.channel] = data;
				CT.pubsub._.cb.subscribe(data);
				CT.pubsub._.autohistory && data.history.forEach(CT.pubsub._.process.publish);
			},
			"publish": function(data) {
				CT.pubsub._.cb.message(data);
			},
			"subscribe": function(data) {
				CT.pubsub._.cb.join(data.channel, data.user);
				CT.data.append(CT.pubsub._.channels[data.channel].presence, data.user);
			},
			"unsubscribe": function(data) {
				CT.pubsub._.cb.leave(data.channel, data.user);
				CT.data.remove(CT.pubsub._.channels[data.channel].presence, data.user);
			},
			"pm": function(data) {
				CT.pubsub._.cb.pm(data.message, data.user);
			},
			"error": function(data) {
				CT.pubsub._.cb.error(data.message);
			},
			"snapshot": function(data) {
				CT.pubsub._.cb.snapshot(data);
				data.channels.forEach(function(c) {
					var cdata = CT.pubsub._.channels[c.name] = {
						channel: c.name,
						presence: c.users
					};
					CT.pubsub._.cb.subscribe(cdata);
				});
			}
		},
		"on": { // websocket events
			"open": function() {
				CT.pubsub._.open = true;
				CT.pubsub._.reconnect_interval = 250;
				CT.pubsub._.write({
					"action": "register",
					"data": CT.pubsub._.args[2]
				});
				for (var channel in CT.pubsub._.channels)
					CT.pubsub.subscribe(channel);
				CT.pubsub._.queue.forEach(CT.pubsub._.write);
				CT.pubsub._.queue.length = 0;
				CT.pubsub._.cb.open();
			},
			"close": function() {
				CT.pubsub._.open = false;
				CT.pubsub._.cb.close();
				if (CT.pubsub._.reconnect)
					CT.pubsub._.try_reconnect()
			},
			"error": function() {
				CT.pubsub._.cb.wserror();
			},
			"message": function(msg) {
				var d = JSON.parse(atob(msg.data));
				CT.pubsub._.process[d.action](d.data);
			}
		},
		"write": function(data) {
			if (CT.pubsub._.open) {
				var dstring = JSON.stringify(data),
					b64str = btoa(dstring);
				CT.pubsub._.ws.send(b64str);
			} else
				CT.pubsub._.queue.push(data);
		},
		"try_reconnect": function() {
			var r_int = CT.pubsub._.reconnect_interval;
			CT.pubsub._.log("RECONNECT", r_int);
			setTimeout(function() {
				CT.pubsub.connect.apply(null, CT.pubsub._.args)
			}, r_int);
			CT.pubsub._.reconnect_interval = Math.min(2 * r_int, 30000);
		}
	},
	"isInitialized": function() {
		return CT.pubsub._.initialized;
	},
	"set_protocol": function(protocol) {
		CT.pubsub._.protocol = protocol;
	},
	"set_reconnect": function(bool) {
		CT.pubsub._.reconnect = bool;
	},
	"set_autohistory": function(bool) {
		CT.pubsub._.autohistory = bool;
	},
	"set_cb": function(action, cb) {
		// action: message|subscribe|join|leave|open|close|error|pm
		CT.pubsub._.cb[action] = cb;
	},
	"pm": function(user, message) {
		CT.pubsub._.write({
			"action": "pm",
			"data": {
				"user": user,
				"message": message
			}
		});
	},
	"publish": function(channel, message) {
		CT.pubsub._.write({
			"action": "publish",
			"data": {
				"channel": channel,
				"message": message
			}
		});
	},
	"subscribe": function(channel) {
		CT.pubsub._.write({
			"action": "subscribe",
			"data": channel
		});
	},
	"unsubscribe": function(channel) {
		CT.pubsub._.write({
			"action": "unsubscribe",
			"data": channel
		});
	},
	"connect": function(host, port, uname) {
		CT.pubsub._.args = arguments;
		CT.pubsub._.initialized = true;
		CT.pubsub._.ws = new WebSocket(CT.pubsub._.protocol + "://" + host + ":" + port);
		for (var action in CT.pubsub._.on)
			CT.pubsub._.ws["on" + action] = CT.pubsub._.on[action];
		window.onbeforeunload = function() {
			CT.pubsub._.write({ "action": "close" });
		};
	}
};<commit_msg>oops, location.protocol<commit_after>/*
This module provides a direct interface with the ctpubsub backend. Here's how to use it.

	CT.pubsub.connect(host, port, uname)
	CT.pubsub.publish(channel, message)
	CT.pubsub.subscribe(channel)
	CT.pubsub.unsubscribe(channel)
	CT.pubsub.pm(user, message)
	CT.pubsub.set_cb(action, cb)
	CT.pubsub.set_reconnect(bool)
	CT.pubsub.isInitialized() (returns bool)
*/

CT.pubsub = {
	"_": {
		"ws": null,
		"args": null,
		"open": false,
		"initialized": false,
		"autohistory": false,
		"reconnect": true,
		"reconnect_interval": 250,
		"protocol": location.protocol == "http:" && "ws" || "wss",
		"log": CT.log.getLogger("CT.pubsub"),
		"queue": [],
		"channels": {},
		"cb": { // default callbacks -- override w/ set_cb()
			"snapshot": CT.log.getLogger("CT.pubsub|snapshot"),
			"pm": CT.log.getLogger("CT.pubsub|pm"),
			"message": CT.log.getLogger("CT.pubsub|message"),
			"subscribe": CT.log.getLogger("CT.pubsub|subscribe"),
			"join": CT.log.getLogger("CT.pubsub|join"),
			"leave": CT.log.getLogger("CT.pubsub|leave"),
			"open": CT.log.getLogger("CT.pubsub|open"),
			"close": CT.log.getLogger("CT.pubsub|close"),
			"wserror": CT.log.getLogger("CT.pubsub|wserror"), // websocket -- almost same as close
			"error": CT.log.getLogger("CT.pubsub|error") // non-fatal pubsub error
		},
		"process": { // pubsub events
			"channel": function(data) {
				CT.pubsub._.channels[data.channel] = data;
				CT.pubsub._.cb.subscribe(data);
				CT.pubsub._.autohistory && data.history.forEach(CT.pubsub._.process.publish);
			},
			"publish": function(data) {
				CT.pubsub._.cb.message(data);
			},
			"subscribe": function(data) {
				CT.pubsub._.cb.join(data.channel, data.user);
				CT.data.append(CT.pubsub._.channels[data.channel].presence, data.user);
			},
			"unsubscribe": function(data) {
				CT.pubsub._.cb.leave(data.channel, data.user);
				CT.data.remove(CT.pubsub._.channels[data.channel].presence, data.user);
			},
			"pm": function(data) {
				CT.pubsub._.cb.pm(data.message, data.user);
			},
			"error": function(data) {
				CT.pubsub._.cb.error(data.message);
			},
			"snapshot": function(data) {
				CT.pubsub._.cb.snapshot(data);
				data.channels.forEach(function(c) {
					var cdata = CT.pubsub._.channels[c.name] = {
						channel: c.name,
						presence: c.users
					};
					CT.pubsub._.cb.subscribe(cdata);
				});
			}
		},
		"on": { // websocket events
			"open": function() {
				CT.pubsub._.open = true;
				CT.pubsub._.reconnect_interval = 250;
				CT.pubsub._.write({
					"action": "register",
					"data": CT.pubsub._.args[2]
				});
				for (var channel in CT.pubsub._.channels)
					CT.pubsub.subscribe(channel);
				CT.pubsub._.queue.forEach(CT.pubsub._.write);
				CT.pubsub._.queue.length = 0;
				CT.pubsub._.cb.open();
			},
			"close": function() {
				CT.pubsub._.open = false;
				CT.pubsub._.cb.close();
				if (CT.pubsub._.reconnect)
					CT.pubsub._.try_reconnect()
			},
			"error": function() {
				CT.pubsub._.cb.wserror();
			},
			"message": function(msg) {
				var d = JSON.parse(atob(msg.data));
				CT.pubsub._.process[d.action](d.data);
			}
		},
		"write": function(data) {
			if (CT.pubsub._.open) {
				var dstring = JSON.stringify(data),
					b64str = btoa(dstring);
				CT.pubsub._.ws.send(b64str);
			} else
				CT.pubsub._.queue.push(data);
		},
		"try_reconnect": function() {
			var r_int = CT.pubsub._.reconnect_interval;
			CT.pubsub._.log("RECONNECT", r_int);
			setTimeout(function() {
				CT.pubsub.connect.apply(null, CT.pubsub._.args)
			}, r_int);
			CT.pubsub._.reconnect_interval = Math.min(2 * r_int, 30000);
		}
	},
	"isInitialized": function() {
		return CT.pubsub._.initialized;
	},
	"set_protocol": function(protocol) {
		CT.pubsub._.protocol = protocol;
	},
	"set_reconnect": function(bool) {
		CT.pubsub._.reconnect = bool;
	},
	"set_autohistory": function(bool) {
		CT.pubsub._.autohistory = bool;
	},
	"set_cb": function(action, cb) {
		// action: message|subscribe|join|leave|open|close|error|pm
		CT.pubsub._.cb[action] = cb;
	},
	"pm": function(user, message) {
		CT.pubsub._.write({
			"action": "pm",
			"data": {
				"user": user,
				"message": message
			}
		});
	},
	"publish": function(channel, message) {
		CT.pubsub._.write({
			"action": "publish",
			"data": {
				"channel": channel,
				"message": message
			}
		});
	},
	"subscribe": function(channel) {
		CT.pubsub._.write({
			"action": "subscribe",
			"data": channel
		});
	},
	"unsubscribe": function(channel) {
		CT.pubsub._.write({
			"action": "unsubscribe",
			"data": channel
		});
	},
	"connect": function(host, port, uname) {
		CT.pubsub._.args = arguments;
		CT.pubsub._.initialized = true;
		CT.pubsub._.ws = new WebSocket(CT.pubsub._.protocol + "://" + host + ":" + port);
		for (var action in CT.pubsub._.on)
			CT.pubsub._.ws["on" + action] = CT.pubsub._.on[action];
		window.onbeforeunload = function() {
			CT.pubsub._.write({ "action": "close" });
		};
	}
};<|endoftext|>


<commit_before>Ico.SparkLine = Class.create(Ico.Base, {
  initialize: function (element, data, options) {
    this.element = element;
    this.data = data;

    this.options = {
      highlight:              false,
      stroke_width:           1,
      colour : "#000",
      width: parseInt(element.getStyle('width'), 10),
      height: parseInt(element.getStyle('height'), 10),
      acceptable_range : false
    };
    Object.extend(this.options, options || { });

    this.step = this.calculateStep();
    this.paper = new Raphael(this.element, this.options.width, this.options.height);
    if (this.options.acceptable_range) {
      this.background = this.paper.rect(0, this.options.height - this.normalise(this.options.acceptable_range[1]),
                                        this.options.width, this.options.height - this.normalise(this.options.acceptable_range[0]));
    } else {
      this.background = this.paper.rect(0, 0, this.options.width, this.options.height);
    }
    this.background.attr({fill: this.options.background_colour, stroke: 'none' });
    this.draw();
  },

  calculateStep: function () {
    return this.options.width / (this.data.length - 1);
  },

  normalise: function (value) {
    return (this.options.height / this.data.max()) * value;
  },

  draw: function () {
    var data = this.normaliseData(this.data);
    this.drawLines('', this.options.colour, data);

    if (this.options.highlight) {
      this.showHighlight(data);
    }
  },

  drawLines: function (label, colour, data) {
    var line = this.paper.path().attr({ stroke: colour, "stroke-width" : this.options.stroke_width }).moveTo(0, this.options.height - data.first()),
        x = 0;

    data.slice(1).each(function (value) {
      x = x + this.step;
      line.lineTo(x, this.options.height - value);
    }.bind(this));
  },

  showHighlight: function (data) {
    var size = 1+this.options.stroke_width/2,
        x = this.options.width - size,
        i = this.options.highlight.index || data.length - 1,
        y = data[i] + (size / 2).round(),
        colour = this.options.highlight.colour || "#f00",
        circle;

    // Find the x position if it's not the last value
    if (typeof this.options.highlight.index !== 'undefined') {
      x = this.step * this.options.highlight.index;
    }

    circle = this.paper.circle(x, this.options.height - y + size/2, size);
    circle.attr({ stroke: false, fill: colour});
  }
});

Ico.SparkBar = Class.create(Ico.SparkLine, {
  calculateStep: function () {
    return this.options.width / this.data.length;
  },
  drawLines: function (label, colour, data) {
    var lastcolor = this.options.bargraph_lastcolour,
        width = this.step > 2 ? this.step - 1 : this.step,
        x = width/2;

    data.each(function (value,index) {
      var colour2, line;

      colour2 = lastcolor && index === data.length-1 ? lastcolor : colour;
      line = this.paper.path().attr({ stroke: colour2, 'stroke-width': width });
      line.moveTo(x, this.options.height - value);
      line.lineTo(x, this.options.height);
      x = x + this.step;
    }.bind(this));
  },
  showHighlight: function (data) {
    //to be implemented
  }
});
Ico.SparkArea = Class.create(Ico.SparkLine, {
  drawLines: function (label, colour, data) {
    var fillopacity = colour,
        strokecolour = colour,
        fillopacity = 0.2;

    if(typeof colour == "object") {
      fillcolour = colour[1];
      strokecolour = colour[0];
      fillopacity = 1;
    }

    var line = this.paper.path().attr({fill: fillcolour,  stroke: fillcolour, "stroke-width" : 0, "stroke-opacity" : 0, opacity: fillopacity})
        .moveTo(0,this.options.height)
        .lineTo(0, this.options.height - data.first()),
        line2 = this.paper.path().attr({stroke: strokecolour, "stroke-width" : this.options.stroke_width }).moveTo(0, this.options.height - data.first()),
        x = 0;

    data.slice(1).each(function (value) {
      x = x + this.step;
      line.lineTo(x, this.options.height - value);
      line2.lineTo(x, this.options.height - value);
    }.bind(this));
    line.lineTo(x,this.options.height);
  }
});

<commit_msg>fix bug that incorrectly doesnt set fillcolour<commit_after>Ico.SparkLine = Class.create(Ico.Base, {
  initialize: function (element, data, options) {
    this.element = element;
    this.data = data;

    this.options = {
      highlight:              false,
      stroke_width:           1,
      colour : "#000",
      width: parseInt(element.getStyle('width'), 10),
      height: parseInt(element.getStyle('height'), 10),
      acceptable_range : false
    };
    Object.extend(this.options, options || { });

    this.step = this.calculateStep();
    this.paper = new Raphael(this.element, this.options.width, this.options.height);
    if (this.options.acceptable_range) {
      this.background = this.paper.rect(0, this.options.height - this.normalise(this.options.acceptable_range[1]),
                                        this.options.width, this.options.height - this.normalise(this.options.acceptable_range[0]));
    } else {
      this.background = this.paper.rect(0, 0, this.options.width, this.options.height);
    }
    this.background.attr({fill: this.options.background_colour, stroke: 'none' });
    this.draw();
  },

  calculateStep: function () {
    return this.options.width / (this.data.length - 1);
  },

  normalise: function (value) {
    return (this.options.height / this.data.max()) * value;
  },

  draw: function () {
    var data = this.normaliseData(this.data);
    this.drawLines('', this.options.colour, data);

    if (this.options.highlight) {
      this.showHighlight(data);
    }
  },

  drawLines: function (label, colour, data) {
    var line = this.paper.path().attr({ stroke: colour, "stroke-width" : this.options.stroke_width }).moveTo(0, this.options.height - data.first()),
        x = 0;

    data.slice(1).each(function (value) {
      x = x + this.step;
      line.lineTo(x, this.options.height - value);
    }.bind(this));
  },

  showHighlight: function (data) {
    var size = 1+this.options.stroke_width/2,
        x = this.options.width - size,
        i = this.options.highlight.index || data.length - 1,
        y = data[i] + (size / 2).round(),
        colour = this.options.highlight.colour || "#f00",
        circle;

    // Find the x position if it's not the last value
    if (typeof this.options.highlight.index !== 'undefined') {
      x = this.step * this.options.highlight.index;
    }

    circle = this.paper.circle(x, this.options.height - y + size/2, size);
    circle.attr({ stroke: false, fill: colour});
  }
});

Ico.SparkBar = Class.create(Ico.SparkLine, {
  calculateStep: function () {
    return this.options.width / this.data.length;
  },
  drawLines: function (label, colour, data) {
    var lastcolor = this.options.bargraph_lastcolour,
        width = this.step > 2 ? this.step - 1 : this.step,
        x = width/2;

    data.each(function (value,index) {
      var colour2, line;

      colour2 = lastcolor && index === data.length-1 ? lastcolor : colour;
      line = this.paper.path().attr({ stroke: colour2, 'stroke-width': width });
      line.moveTo(x, this.options.height - value);
      line.lineTo(x, this.options.height);
      x = x + this.step;
    }.bind(this));
  },
  showHighlight: function (data) {
    //to be implemented
  }
});
Ico.SparkArea = Class.create(Ico.SparkLine, {
  drawLines: function (label, colour, data) {
    var fillcolour = colour,
        strokecolour = colour,
        fillopacity = 0.2;

    if(typeof colour == "object") {
      fillcolour = colour[1];
      strokecolour = colour[0];
      fillopacity = 1;
    }

    var line = this.paper.path().attr({fill: fillcolour,  stroke: fillcolour, "stroke-width" : 0, "stroke-opacity" : 0, opacity: fillopacity})
        .moveTo(0,this.options.height)
        .lineTo(0, this.options.height - data.first()),
        line2 = this.paper.path().attr({stroke: strokecolour, "stroke-width" : this.options.stroke_width }).moveTo(0, this.options.height - data.first()),
        x = 0;

    data.slice(1).each(function (value) {
      x = x + this.step;
      line.lineTo(x, this.options.height - value);
      line2.lineTo(x, this.options.height - value);
    }.bind(this));
    line.lineTo(x,this.options.height);
  }
});

<|endoftext|>


<commit_before>var searchFunc = function(path, search_id, content_id) {
    'use strict';
    $.ajax({
        url: path,
        dataType: "xml",
        success: function( xmlResponse ) {
            // get the contents from search data
            var datas = $( "entry", xmlResponse ).map(function() {
                return {
                    title: $( "title", this ).text(),
                    content: $("content",this).text(),
                    url: $( "url" , this).text()
                };
            }).get();

            var $input = document.getElementById(search_id);
            var $resultContent = document.getElementById(content_id);

            $input.addEventListener('input', function(){
                var str='
';
                var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                $resultContent.innerHTML = "";
                if (this.value.trim().length <= 0) {
                    return;
                }
                // perform local searching
                datas.forEach(function(data) {
                    var isMatch = true;
                    var content_index = [];
                    if (!data.title) {
                        data.title = "Untitled";
                    }
                    var data_title = data.title.trim().toLowerCase();
                    var data_content = data.content.trim().replace(/<[^>]+>/g,"").toLowerCase();
                    var data_url = data.url;
                    var index_title = -1;
                    var index_content = -1;
                    var first_occur = -1;
                    // only match artiles with not empty titles and contents
                    if (data_content !== '') {
                        keywords.forEach(function(keyword, i) {
                            index_title = data_title.indexOf(keyword);
                            index_content = data_content.indexOf(keyword);

                            if( index_title < 0 && index_content < 0 ){
                                isMatch = false;
                            } else {
                                if (index_content < 0) {
                                    index_content = 0;
                                }
                                if (i == 0) {
                                    first_occur = index_content;
                                }
                                // content_index.push({index_content:index_content, keyword_len:keyword_len});
                            }
                        });
                    } else {
                        isMatch = false;
                    }
                    // show search results
                    if (isMatch) {
                        str += "
"+ data_title +"";
                        var content = data.content.trim().replace(/<[^>]+>/g,"");
                        if (first_occur >= 0) {
                            // cut out 100 characters
                            var start = first_occur - 20;
                            var end = first_occur + 80;

                            if(start < 0){
                                start = 0;
                            }

                            if(start == 0){
                                end = 100;
                            }

                            if(end > content.length){
                                end = content.length;
                            }

                            var match_content = content.substr(start, end);

                            // highlight all keywords
                            keywords.forEach(function(keyword){
                                var regS = new RegExp(keyword, "gi");
                                match_content = match_content.replace(regS, ""+keyword+"");
                            });

                            str += "
" + match_content +"...
"
                        }
                        str += "
";
                    }
                });
                str += "
";
                $resultContent.innerHTML = str;
            });
        }
    });
}
<commit_msg>Also deal with blank title.<commit_after>var searchFunc = function(path, search_id, content_id) {
    'use strict';
    $.ajax({
        url: path,
        dataType: "xml",
        success: function( xmlResponse ) {
            // get the contents from search data
            var datas = $( "entry", xmlResponse ).map(function() {
                return {
                    title: $( "title", this ).text(),
                    content: $("content",this).text(),
                    url: $( "url" , this).text()
                };
            }).get();

            var $input = document.getElementById(search_id);
            var $resultContent = document.getElementById(content_id);

            $input.addEventListener('input', function(){
                var str='
';
                var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                $resultContent.innerHTML = "";
                if (this.value.trim().length <= 0) {
                    return;
                }
                // perform local searching
                datas.forEach(function(data) {
                    var isMatch = true;
                    var content_index = [];
                    if (!data.title || data.title.trim() === '') {
                        data.title = "Untitled";
                    }
                    var data_title = data.title.trim().toLowerCase();
                    var data_content = data.content.trim().replace(/<[^>]+>/g,"").toLowerCase();
                    var data_url = data.url;
                    var index_title = -1;
                    var index_content = -1;
                    var first_occur = -1;
                    // only match artiles with not empty contents
                    if (data_content !== '') {
                        keywords.forEach(function(keyword, i) {
                            index_title = data_title.indexOf(keyword);
                            index_content = data_content.indexOf(keyword);

                            if( index_title < 0 && index_content < 0 ){
                                isMatch = false;
                            } else {
                                if (index_content < 0) {
                                    index_content = 0;
                                }
                                if (i == 0) {
                                    first_occur = index_content;
                                }
                                // content_index.push({index_content:index_content, keyword_len:keyword_len});
                            }
                        });
                    } else {
                        isMatch = false;
                    }
                    // show search results
                    if (isMatch) {
                        str += "
"+ data_title +"";
                        var content = data.content.trim().replace(/<[^>]+>/g,"");
                        if (first_occur >= 0) {
                            // cut out 100 characters
                            var start = first_occur - 20;
                            var end = first_occur + 80;

                            if(start < 0){
                                start = 0;
                            }

                            if(start == 0){
                                end = 100;
                            }

                            if(end > content.length){
                                end = content.length;
                            }

                            var match_content = content.substr(start, end);

                            // highlight all keywords
                            keywords.forEach(function(keyword){
                                var regS = new RegExp(keyword, "gi");
                                match_content = match_content.replace(regS, ""+keyword+"");
                            });

                            str += "
" + match_content +"...
"
                        }
                        str += "
";
                    }
                });
                str += "
";
                $resultContent.innerHTML = str;
            });
        }
    });
}
<|endoftext|>


<commit_before>// @flow
import {AsyncStorage} from 'react-native'
import moment from 'moment'
import {API} from '@frogpond/api'

type BaseCacheResultType<T> = {
	isExpired: boolean,
	isCached: boolean,
	value: ?T,
}

type CacheResultType<T> = Promise<BaseCacheResultType<T>>

function needsUpdate(time: Date, [count, unit]: [number, string]): boolean {
	return moment(time).isBefore(moment().subtract(count, unit))
}

function annotateCacheEntry(stored) {
	// if nothing's stored, note that it's expired and not cached
	if (stored === null || stored === undefined) {
		return {isCached: false, isExpired: true, value: null}
	}

	// migration from old storage
	if (
		!('dateCached' in stored && 'timeToCache' in stored && 'value' in stored)
	) {
		return {isCached: true, isExpired: true, value: stored}
	}

	// handle AsyncStorage entries that aren't caches, like the homescreen order
	if (!stored.timeToCache) {
		return {isCached: true, isExpired: false, value: stored.value}
	}

	const date = new Date(stored.dateCached)
	const isExpired = needsUpdate(date, stored.timeToCache)
	return {isCached: true, isExpired, value: stored.value}
}

/// MARK: Utilities

function setItem(key: string, value: any, cacheTime?: [number, string]) {
	const dataToStore = {
		dateCached: new Date().toUTCString(),
		timeToCache: cacheTime,
		value: value,
	}
	return AsyncStorage.setItem(`aao:${key}`, JSON.stringify(dataToStore))
}
function getItem(key: string): CacheResultType<any> {
	return AsyncStorage.getItem(`aao:${key}`).then(stored =>
		annotateCacheEntry(JSON.parse(stored)),
	)
}

/// MARK: Help tools

const helpToolsKey = 'help:tools'
const helpToolsCacheTime = [1, 'hour']
import {type ToolOptions} from '../views/help/types'
const {data: helpData} = require('../../docs/help.json')
export function setHelpTools(tools: Array<ToolOptions>) {
	return setItem(helpToolsKey, tools, helpToolsCacheTime)
}
export function getHelpTools(): CacheResultType<?Array<ToolOptions>> {
	return getItem(helpToolsKey)
}
function fetchHelpToolsBundled(): Promise<Array<ToolOptions>> {
	return Promise.resolve(helpData)
}
function fetchHelpToolsRemote(): Promise<{data: Array<ToolOptions>}> {
	return fetchJson(API('/tools/help'))
}
export async function fetchHelpTools(): Promise<Array<ToolOptions>> {
	const cachedValue = await getHelpTools()

	if (process.env.NODE_ENV === 'development') {
		return fetchHelpToolsBundled()
	}

	if (!cachedValue.isExpired && cachedValue.value) {
		return cachedValue.value
	}

	try {
		const request = await fetchHelpToolsRemote()
		await setHelpTools(request.data)

		return request.data
	} catch (error) {
		if (cachedValue.isCached && cachedValue.value) {
			return cachedValue.value
		}
		return fetchHelpToolsBundled()
	}
}
<commit_msg>replace caching module<commit_after>// @flow
import {AsyncStorage} from 'react-native'
import moment from 'moment'
import {reportNetworkProblem} from '@frogpond/analytics'
import delay from 'delay'

global.AsyncStorage = AsyncStorage

type _BaseCacheResult<T> = {
	isExpired: boolean,
	isCached: boolean,
	value: ?T,
	source: ?string,
}

export type CacheResult<T> = Promise<_BaseCacheResult<T>>

function needsUpdate(time: Date, [count, unit]: [number, string]): boolean {
	return moment(time).isBefore(moment().subtract(count, unit))
}

function annotateCacheEntry({ts, ttl}: {ts: string, ttl: string}) {
	// handle uncached entries and entries that aren't caches, like the homescreen order
	if (!ts || !ttl) {
		return {isCached: true, isExpired: false}
	}

	return {
		isCached: true,
		isExpired: needsUpdate(new Date(ts), JSON.parse(ttl)),
	}
}

/// MARK: Utilities

async function setItem(
	key: string,
	value: any,
	{ttl, source}: {ttl?: [number, string], source?: string},
) {
	console.log('setItem', {key, value, ttl, source})
	await AsyncStorage.multiSet([
		[`fp:${key}:data`, JSON.stringify(value)],
		[`fp:${key}:ts`, new Date().toUTCString()],
		[`fp:${key}:ttl`, JSON.stringify(ttl)],
		[`fp:${key}:source`, source],
	])
}

async function getItem(key: string): CacheResult<any> {
	console.log('getItem', key)

	let [[, value], [, ts], [, ttl], [, source]] = await AsyncStorage.multiGet([
		`fp:${key}:data`,
		`fp:${key}:ts`,
		`fp:${key}:ttl`,
		`fp:${key}:source`,
	])

	return {
		value: JSON.parse(value),
		source,
		...annotateCacheEntry({ts, ttl}),
	}
}

global._setItem = setItem
global._getItem = getItem
global._fetchAndCacheItem = fetchAndCacheItem

export async function fetchAndCacheItem(args: {
	key: string,
	url?: string,
	cbForBundledData?: () => Promise<mixed>,
	ttl?: [number, string],
	afterFetch?: (parsed: any) => any,
	force?: boolean,
	delay?: boolean,
}): CacheResult<any> {
	let {
		key,
		url,
		cbForBundledData,
		ttl,
		afterFetch,
		force: shouldForce,
		delay: shouldDelay,
	} = args

	let start = Date.now()

	console.log('fetchAndCacheItem', {key, url, cbForBundledData, ttl})

	// if (process.env.NODE_ENV === 'development' && cbForBundledData) {
	// 	let data = await cbForBundledData()
	// 	await setItem(key, data, {source: url})
	// 	return getItem(key)
	// }

	let {value, isExpired, isCached, source} = await getItem(key)

	if (!isExpired && value != null && !shouldForce) {
		return {value, isCached, isExpired, source}
	}

	if (!source && url) {
		source = url
	}

	try {
		// try fetching first
		let body = await fetchJson(url)

		if (afterFetch) {
			body = afterFetch(body)
		}

		await setItem(key, body, {source: url, ttl})

		let elapsed = Date.now() - start
		if (shouldDelay && elapsed < 500) {
			// 0.5s delay for ListViews – if we let them go at full speed, it feels broken
			await delay(500 - elapsed)
		}

		return getItem(key)
	} catch (error) {
		// fall back to the local data
		if (error.message.startsWith('Failed to fetch')) {
			reportNetworkProblem(error)
		}

		// if there's no data still, return null
		if (!isCached) {
			if (cbForBundledData) {
				let data = await cbForBundledData()
				await setItem(key, data, {source: url, ttl})
				return getItem(key)
			}

			return {value: null, isCached, isExpired, source}
		}

		// if we have data, though, return it
		return {value, isCached, isExpired, source}
	}
}
<|endoftext|>


<commit_before>import FEATURE_TOGGLE from '../../src/feature-toggle';

export default class Particle {

  /*
   *   Pass 'boxBounce' as { w: <with>, h: <height> } to make particle bounce inside a box
   *
   **/
    constructor(settings) {
        this.x = settings.x || 0;
        this.y = settings.y || 0;
        this.vx = (Math.cos(settings.direction) * settings.speed) || 0;
        this.vy = (Math.sin(settings.direction) * settings.speed) || 0;
        this.gravity = settings.gravity || 0;
        this.mass = settings.mass || 1;
        this.radius = settings.radius || settings.mass * 0.87;
        this.friction = settings.friction || 1;
        this.springs = [];
        this.gravitations = [];

        this.color = settings.color || "#000000";
        this.boxBounce = settings.boxBounce || false;
    }

    /*
     *  Updates the state of the particle
     */
    update() {
        this.handleSprings();
        this.handleGravitations();
        this.vy += this.gravity;
        this.vx *= this.friction;
        this.vy *= this.friction;
        this.x += this.vx;
        this.y += this.vy;

        if (this.boxBounce) {
          this.checkBorders(this.boxBounce.w, this.boxBounce.h);
        }
    }

    /*
     *  Gets the length of the velocity vector, which equals to the hypotenuse
     */
    getSpeed() {
        return Math.sqrt(this.vx * this.vx + this.vy * this.vy);
    }

    /*
     *  Using the actual Velocity vector's angle, sets a new length for it
     */
    setSpeed(speed) {
        let heading = this.getHeading();
        this.vx = Math.cos(heading) * speed;
        this.vy = Math.sin(heading) * speed;
    }

    /*
     *  Gets the angle direction of the velocity vector
     */
    getHeading() {
        return Math.atan2(this.vy, this.vx);
    }

    /*
     *  Changes the Velocity vector's angle and recalculate coordinates
     */
    setHeading(heading) {
        let speed = this.getSpeed();
        this.vx = Math.cos(heading) * speed;
        this.vy = Math.sin(heading) * speed;
    }

    /*
     *  Sums to the Velocity vector x and y values
     */
    accelerate(x, y) {
        this.vx += x;
        this.vy += y;
    }

    /*
    *  Bounce if the particle hits the box (i.e. screen) borders
    */
    checkBorders(width, height) {
      if (this.x + this.radius >= width) {
        this.x = width - this.radius;
        this.vx *=  -1;
      } else if (this.x - this.radius <= 0) {
        this.x = this.radius;
        this.vx *= -1;
      }

      if (this.y + this.radius >= height) {
        this.y = height - this.radius;
        this.vy *=  -1;
      } else if (this.y - this.radius <= 0) {
        this.y = this.radius;
        this.vy *= -1;
      }
    }

    /*
     *  Calculates the angle between this particle and 'p2'
     */
    angleTo(p2) {
        return Math.atan2(p2.y - this.y, p2.x - this.x);
    }

    /*
     *  Calculates the distance to a given particle
     */
    distanceTo(p) {
        let dx = p.x - this.x;
        let dy = p.y - this.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    /*
     *  Calculates and applies a gravitation vector to a given particle
     */
    gravitateTo(p) {
        let dx = p.x - this.x;
        let dy = p.y - this.y;
        let distSQ = (dx * dx) + (dy * dy);
        let dist = Math.sqrt(distSQ);
        let force = (p.mass) / distSQ; // Force = mass / square of the distance
        /*
        cos * hypotenuse = opposite side || cos = opposite side / hypotenuse
        sin * hypotenuse = adjacent side || sin = adjacent side / hypotenuse

        That being said, we can optimize this:
        let angle = this.angleTo(p);
        let ax = Math.cos(angle) * force;
        let ay = Math.sin(angle) * force;

        And save three trigo functions
        */
        let ax = (dx / dist) * force;
        let ay = (dy / dist) * force;

        this.vx += ax;
        this.vy += ay;
    }

    /*
     *  Registers a particle to gravitate to
     */
    addGravitation(p) {
        this.removeGravitation(p);
        this.gravitations.push(p);
    }

    /*
     *  Unregisters a gravitation particle
     */
    removeGravitation(p) {
        let length = this.gravitations.length;
        for (let i=0; i<length; i++){
            if (this.gravitations[i] === p) {
                this.gravitations.slice(i, 1);
                return true;
            }
        }
    }

    /*
     *  Gravitates to each registered gravitation particle
     */
    handleGravitations() {
        let length = this.gravitations.length;
        for (let i=0; i<length; i++){
            this.gravitateTo(this.gravitations[i]);
        }
    }

    /*
     *  Calculates and applies a spring vector to a given point
     */
    springTo(point, k, length) {
        let dx = point.x - this.x;
        let dy = point.y - this.y;
        let distance = Math.sqrt(dx * dx + dy * dy);
        let force = (distance - length || 0) * k;
        // Instead of getting cos / sin of angle, divide sides by hypotenuse
        this.vx += (dx / distance) * force;
        this.vy += (dy / distance) * force;
    }

    /*
     *  Registers a new spring point
     */
    addSpring(point, k, length) {
        this.removeSpring(point);
        this.springs.push({
            point: point,
            k: k,
            length: length || 0
        });
    }

    /*
     *  Unregisters a spring point
     */
    removeSpring(point) {
        let length = this.springs.length;
        for (let i=0; i<length; i++){
            if (this.springs[i].point === point) {
                this.springs.splice(i, 1);
                return;
            }
        }
    }

    /*
     *  Springs to each registered spring point
     */
    handleSprings() {
        let length = this.springs.length;
        for (let i=0; i<length; i++){
            let spring = this.springs[i];
            this.springTo(spring.point, spring.k, spring.length);
        }
    }
}
<commit_msg>Particle.js: gravitateTo method refactored<commit_after>import FEATURE_TOGGLE from '../../src/feature-toggle';

export default class Particle {

  /*
   *   Pass 'boxBounce' as { w: <with>, h: <height> } to make particle bounce inside a box
   *
   **/
    constructor(settings) {
        this.x = settings.x || 0;
        this.y = settings.y || 0;
        this.vx = (Math.cos(settings.direction) * settings.speed) || 0;
        this.vy = (Math.sin(settings.direction) * settings.speed) || 0;
        this.gravity = settings.gravity || 0;
        this.mass = settings.mass || 1;
        this.radius = settings.radius || settings.mass * 0.87;
        this.friction = settings.friction || 1;
        this.springs = [];
        this.gravitations = [];

        this.color = settings.color || "#000000";
        this.boxBounce = settings.boxBounce || false;
    }

    /*
     *  Updates the state of the particle
     */
    update() {
        this.handleSprings();
        this.handleGravitations();
        this.vy += this.gravity;
        this.vx *= this.friction;
        this.vy *= this.friction;
        this.x += this.vx;
        this.y += this.vy;

        if (this.boxBounce) {
          this.checkBorders(this.boxBounce.w, this.boxBounce.h);
        }
    }

    /*
     *  Gets the length of the velocity vector, which equals to the hypotenuse
     */
    getSpeed() {
        return Math.sqrt(this.vx * this.vx + this.vy * this.vy);
    }

    /*
     *  Using the actual Velocity vector's angle, sets a new length for it
     */
    setSpeed(speed) {
        let heading = this.getHeading();
        this.vx = Math.cos(heading) * speed;
        this.vy = Math.sin(heading) * speed;
    }

    /*
     *  Gets the angle direction of the velocity vector
     */
    getHeading() {
        return Math.atan2(this.vy, this.vx);
    }

    /*
     *  Changes the Velocity vector's angle and recalculate coordinates
     */
    setHeading(heading) {
        let speed = this.getSpeed();
        this.vx = Math.cos(heading) * speed;
        this.vy = Math.sin(heading) * speed;
    }

    /*
     *  Sums to the Velocity vector x and y values
     */
    accelerate(x, y) {
        this.vx += x;
        this.vy += y;
    }

    /*
    *  Bounce if the particle hits the box (i.e. screen) borders
    */
    checkBorders(width, height) {
      if (this.x + this.radius >= width) {
        this.x = width - this.radius;
        this.vx *=  -1;
      } else if (this.x - this.radius <= 0) {
        this.x = this.radius;
        this.vx *= -1;
      }

      if (this.y + this.radius >= height) {
        this.y = height - this.radius;
        this.vy *=  -1;
      } else if (this.y - this.radius <= 0) {
        this.y = this.radius;
        this.vy *= -1;
      }
    }

    /*
     *  Calculates the angle between this particle and 'p2'
     */
    angleTo(p2) {
        return Math.atan2(p2.y - this.y, p2.x - this.x);
    }

    /*
     *  Calculates the distance to a given particle
     */
    distanceTo(p) {
        let dx = p.x - this.x;
        let dy = p.y - this.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    /*
     *  Calculates and applies a gravitation vector to a given particle
     */
     gravitateTo(p, gravityFactor) {
         gravityFactor = gravityFactor || 0.04;

         let radiusSum = this.radius + p.radius;
         let massFactor = this.mass * p.mass;

         let dx = p.x - this.x;
         let dy = p.y - this.y;
         let distSQ = (dx * dx) + (dy * dy);
         let dist = Math.sqrt(distSQ);
         let surfaceDist = dist - radiusSum;

         // Cancel gravitation once objects collide
         // TODO: Verify if we can save the Math.sqrt() comparing squares
         if (dist < radiusSum + 5) {
           return;
         }

         //let force = (p.mass) / distSQ; // Force = mass / square of the distance
         let force = gravityFactor * massFactor / (surfaceDist * surfaceDist);

         let ax = (dx / surfaceDist) * force;
         let ay = (dy / surfaceDist) * force;

         this.vx += ax;
         this.vy += ay;
     }

    /*
     *  Registers a particle to gravitate to
     */
    addGravitation(p) {
        this.removeGravitation(p);
        this.gravitations.push(p);
    }

    /*
     *  Unregisters a gravitation particle
     */
    removeGravitation(p) {
        let length = this.gravitations.length;
        for (let i=0; i<length; i++){
            if (this.gravitations[i] === p) {
                this.gravitations.slice(i, 1);
                return true;
            }
        }
    }

    /*
     *  Gravitates to each registered gravitation particle
     */
    handleGravitations() {
        let length = this.gravitations.length;
        for (let i=0; i<length; i++){
            this.gravitateTo(this.gravitations[i]);
        }
    }

    /*
     *  Calculates and applies a spring vector to a given point
     */
    springTo(point, k, length) {
        let dx = point.x - this.x;
        let dy = point.y - this.y;
        let distance = Math.sqrt(dx * dx + dy * dy);
        let force = (distance - length || 0) * k;
        // Instead of getting cos / sin of angle, divide sides by hypotenuse
        this.vx += (dx / distance) * force;
        this.vy += (dy / distance) * force;
    }

    /*
     *  Registers a new spring point
     */
    addSpring(point, k, length) {
        this.removeSpring(point);
        this.springs.push({
            point: point,
            k: k,
            length: length || 0
        });
    }

    /*
     *  Unregisters a spring point
     */
    removeSpring(point) {
        let length = this.springs.length;
        for (let i=0; i<length; i++){
            if (this.springs[i].point === point) {
                this.springs.splice(i, 1);
                return;
            }
        }
    }

    /*
     *  Springs to each registered spring point
     */
    handleSprings() {
        let length = this.springs.length;
        for (let i=0; i<length; i++){
            let spring = this.springs[i];
            this.springTo(spring.point, spring.k, spring.length);
        }
    }
}
<|endoftext|>


<commit_before>/**
 * Bit Array
 * {bits} Array, the array of bits
 *
 * Asymptotic time complexities
 * +-----------------------+
 * | complement   |  O(1)  |
 * | union        |  O(1)  |
 * | intersection |  O(1)  |
 * | difference   |  O(1)  |
 * | resize       |  O(n)  |
 * | get          |  O(1)  |
 * | set          |  O(1)  |
 * | size         |  O(1)  |
 * +-----------------------+
 *
 */

'use strict';

/**
 * Transforms input into a number.
 * @param {*} input to become a number
 * @return {number} number version of input
 */
var shred = function(n){
  if(typeof n !== 'string'){
    n = JSON.stringify(n)
  }
  if(typeof n === 'number'){
    return n;
  }
  var res = 0;
  n.split("").map(function(bit){
    res += bit.charCodeAt(0);
  });
  return res;
}

/** Bitwise operators treat their operands as a sequence of 32 bits (zeros and ones) */
const INTEGER_SIZE = 32;

/** TODO: defaultValue is backwards
 * Instantiates a bit array with given size.
 * @param {number} the size of the bit array
 * @return {void}
 */
const BitArray = function(size, defaultValue){
  this.data = [];
  if(typeof size === 'undefined'){
    return; // Empty instance of a bit array
  }
  if(typeof size !== 'number'){
    size = shred(size);
  }
  if(typeof defaultValue === 'number'){
    if(defaultValue < -2147483648 || defaultValue > 2147483647){
      throw new Error("Default value for BitArray is out of range in BitArray constructor");
    }
  }else if(typeof defaultValue !== 'undefined'){
    throw new TypeError("Invalid argument: expected a number for default value in BitArray constructor");
  }
  var dv = defaultValue || 0;
  for(var i=0; i<Math.ceil(size/INTEGER_SIZE); ++i){
    this.data.push(dv);
  }
}

/**
 * Sets the bit from the bit array at desired location. It is important to
 * note that indexing starts at 0 (where 0 refers to the first element).
 * @param {number} the position of bit
 * @param {boolean} the new value of bit
 * @return {void}
 */
BitArray.prototype.set = function(index, value){
  if(index < 0 || index >= this.data.length * INTEGER_SIZE){
    throw new Error("Index out of bounds in BitArray.set");
  }
  var _index = Math.floor(index++/INTEGER_SIZE);
  if(!!value){
    this.data[_index] |= 1 << (INTEGER_SIZE - index);
  }else{
    this.data[_index] &= ~(1 << (INTEGER_SIZE- index));
  }
};

/**
 * Returns the bit from the bit array at desired location.
 * @param {number} the bit to find
 * @return {number} the bit at desired index
 */
BitArray.prototype.get = function(index){
  if(index < 0 || index >= this.data.length * INTEGER_SIZE){
    throw new Error("Index out of bounds in BitArray.get");
  }
  var _index = Math.floor(index++/INTEGER_SIZE);
  var seqCpy = this.data[_index];
  return (seqCpy >> INTEGER_SIZE-index) & 1;
};

/**
 * Resolve the complement bit array.
 * @param {BitArray} [bitarray = this] input bit array to complement
 * @return {BitArray} the complement bit array
 */
BitArray.prototype.complement = function(bitarray){
  if(typeof bitarray === 'undefined'){
    bitarray = this;
  }
  // If not bit array, attempt to convert
  else if(!(bitarray instanceof BitArray)){
    bitarray = new BitArray(bitarray);
  }
  var _bitarray = new BitArray();
  bitarray.data.map(function(sequence){
    _bitarray.data.push(~sequence);
  });
  return _bitarray;
};

/**
 * Converts the bit array into a string of bits.
 * @param {void}
 * @return {string} the string of bits for the bit array
 */
BitArray.prototype.toString = function(){
  var bitString = "";
  this.data.map(function(sequence){
    for (var i = 0; i<INTEGER_SIZE; i++){
      bitString += String(sequence >>> 31);
      sequence <<= 1;
    }
    bitString.split("").reverse().join("");
  });
  return bitString;
}

/**
 * Returns the size of the bit array.
 * @param {void}
 * @return {number} the size of the bit array
 */
BitArray.prototype.size = function(){
  return this.data.length * INTEGER_SIZE;
};

module.exports = BitArray;<commit_msg>more bitarray updates<commit_after>/**
 * Bit Array
 * {bits} Array, the array of bits
 *
 * Asymptotic time complexities
 * +-----------------------+
 * | complement   |  O(1)  |
 * | union        |  O(1)  |
 * | intersection |  O(1)  |
 * | difference   |  O(1)  |
 * | resize       |  O(n)  |
 * | get          |  O(1)  |
 * | set          |  O(1)  |
 * | size         |  O(1)  |
 * +-----------------------+
 *
 */

'use strict';

/**
 * Transforms input into a number.
 * @param {*} input to become a number
 * @return {number} number version of input
 */
var shred = function(n){
  if(typeof n === 'number'){
    return n;
  }
  if(typeof n !== 'string'){
    n = JSON.stringify(n)
  }
  var res = 0;
  n.split("").map(function(bit){
    res += bit.charCodeAt(0);
  });
  return res;
}

/** Bitwise operators treat their operands as a sequence of 32 bits (zeros and ones) */
const INTEGER_SIZE = 32;
const INT_MIN = -2147483648;
const INT_MAX = 2147483647;

/**
 * Instantiates a bit array with given size.
 * @param {number} the size of the bit array
 * @return {void}
 */
const BitArray = function(size){
  this.data = [];
  if(typeof size === 'undefined'){
    return; // Empty instance of a bit array
  }
  if(typeof size !== 'number'){
    size = shred(size);
  }
  for(var i=0; i<Math.ceil(size/INTEGER_SIZE); ++i){
    this.data.push(0);
  }
}

/**
 * Sets the bit from the bit array at desired location. It is important to
 * note that indexing starts at 0 (where 0 refers to the first element).
 * @param {number} the position of bit
 * @param {boolean} the new value of bit
 * @return {void}
 */
BitArray.prototype.set = function(index, value){
  if(index < 0 || index >= this.data.length * INTEGER_SIZE){
    throw new Error("Index out of bounds in BitArray.set");
  }
  var _index = Math.floor(index++/INTEGER_SIZE);
  if(!!value){
    this.data[_index] |= 1 << (INTEGER_SIZE - index);
  }else{
    this.data[_index] &= ~(1 << (INTEGER_SIZE- index));
  }
};

/**
 * Returns the bit from the bit array at desired location.
 * @param {number} the bit to find
 * @return {number} the bit at desired index
 */
BitArray.prototype.get = function(index){
  if(index < 0 || index >= this.data.length * INTEGER_SIZE){
    throw new Error("Index out of bounds in BitArray.get");
  }
  var _index = Math.floor(index++/INTEGER_SIZE);
  var seqCpy = this.data[_index];
  return (seqCpy >> INTEGER_SIZE-index) & 1;
};

/**
 * Resolve the complement bit array.
 * @param {BitArray} [bitarray = this] input bit array to complement
 * @return {BitArray} the complement bit array
 */
BitArray.prototype.complement = function(bitarray){
  if(typeof bitarray === 'undefined'){
    bitarray = this;
  }
  // If not bit array, attempt to convert
  else if(!(bitarray instanceof BitArray)){
    bitarray = new BitArray(bitarray);
  }
  var _bitarray = new BitArray();
  bitarray.data.map(function(sequence){
    _bitarray.data.push(~sequence);
  });
  return _bitarray;
};

/**
 * Converts the bit array into a string of bits.
 * @param {void}
 * @return {string} the string of bits for the bit array
 */
BitArray.prototype.toString = function(){
  var bitString = "";
  this.data.map(function(sequence){
    for (var i = 0; i<INTEGER_SIZE; i++){
      bitString += String(sequence >>> 31);
      sequence <<= 1;
    }
    bitString.split("").reverse().join("");
  });
  return bitString;
}

/**
 * Returns the size of the bit array.
 * @param {void}
 * @return {number} the size of the bit array
 */
BitArray.prototype.size = function(){
  return this.data.length * INTEGER_SIZE;
};

module.exports = BitArray;<|endoftext|>


<commit_before>import { keysFromContext, valueFromContext, entityFromContext }
  from '../format';

export const properties = new WeakMap();
export const contexts = new WeakMap();

/**
 * The `Localization` class is responsible for fetching resources and
 * formatting translations.
 *
 * l20n.js for HTML will create an instance of `Localization` for the default
 * set of `<link rel="localization">` elements.  You can get a reference to it
 * via:
 *
 *     const localization = document.l10n.get('main');
 *
 * Different names can be specified via the `name` attribute on the `<link>`
 * elements.
 */
export class Localization {

  /**
   * @param   {function}    requestBundles
   * @returns {Localization}
   */
  constructor(requestBundles, createContext) {

    /**
     * A Promise which resolves when the `Localization` instance has fetched
     * and parsed all localization resources in the user's first preferred
     * language (if available).
     *
     * ```javascript
     * localization.interactive.then(callback);
     * ```
     */
    this.interactive = requestBundles().then(
      bundles => fetchFirstBundle(bundles, createContext)
    );

    properties.set(this, {
      requestBundles, createContext
    });
  }

  requestLanguages(requestedLangs) {
    return this.interactive.then(
      bundles => changeLanguages(this, bundles, requestedLangs)
    );
  }

  formatWithFallback(bundles, keys, method, prev) {
    const ctx = contexts.get(bundles[0]);

    if (!ctx && prev) {
      return prev.translations;
    }

    const current = keysFromContext(ctx, keys, method, prev);

    if (!current.hasErrors) {
      return current.translations;
    }

    if (typeof console !== 'undefined') {
      current.errors.forEach(
        errs => errs.forEach(
          e => console.warn(e) // eslint-disable-line no-console
        )
      );
    }

    const { createContext } = properties.get(this);
    return fetchFirstBundle(bundles.slice(1), createContext).then(
      tailBundles => this.formatWithFallback(
        tailBundles, keys, method, current
      )
    );
  }

  formatEntities(keys) {
    return this.interactive.then(
      bundles => this.formatWithFallback(bundles, keys, entityFromContext)
    );
  }

  /**
   * A generalized version of `Localization.formatValue`.  Retrieve
   * translations corresponding to the passed keys.  Keys can either be simple
   * string identifiers or `[id, args]` arrays.
   *
   * Returns a Promise resolving to an array of the translation strings.
   *
   * ```javascript
   * document.l10n.formatValues(
   *   ['hello', { who: 'Mary' }],
   *   ['hello', { who: 'John' }],
   *   'welcome'
   * ).then(([helloMary, helloJohn, welcome]) =>
   *   console.log(helloMary, helloJohn, welcome)
   * );
   * // -> 'Hello, Mary!', 'Hello, John!', 'Welcome!'
   * ```
   *
   * @returns {Promise}
   */
  formatValues(...keys) {
    const keyTuples = keys.map(
      key => Array.isArray(key) ? key : [key, null]
    );
    return this.interactive.then(
      bundles => this.formatWithFallback(bundles, keyTuples, valueFromContext)
    );
  }

  /**
   * Retrieve the translation corresponding to the `id` identifier.
   *
   * If passed, `args` is a simple hash object with a list of variables that
   * will be interpolated in the value of the translation.
   *
   * Returns a Promise resolving to the translation string.

   * ```javascript
   * localization.formatValue('hello', { who: 'world' }).then(
   *   hello => console.log(hello));
   * // -> 'Hello, world!'
   * ```
   *
   * Use this sparingly for one-off messages which don't need to be
   * retranslated when the user changes their language preferences.
   *
   * @returns {Promise}
   */
  formatValue(id, args) {
    return this.formatValues([id, args]).then(
      ([val]) => val
    );
  }

}

function createContextFromBundle(bundle, createContext) {
  return bundle.fetch().then(resources => {
    const ctx = createContext(bundle.lang);
    resources
      .filter(res => res !== null)
      .forEach(res => ctx.addMessages(res));
    contexts.set(bundle, ctx);
    return ctx;
  });
}

export function fetchFirstBundle(bundles, createContext) {
  const [bundle] = bundles;

  if (!bundle) {
    return Promise.resolve(bundles);
  }

  return createContextFromBundle(bundle, createContext).then(
    () => bundles
  );
}

function changeLanguages(l10n, oldBundles, requestedLangs) {
  const { requestBundles, createContext } = properties.get(l10n);

  return l10n.interactive = requestBundles(requestedLangs).then(
    newBundles => equal(oldBundles, newBundles) ?
      oldBundles : fetchFirstBundle(newBundles, createContext)
  );
}

function equal(bundles1, bundles2) {
  return bundles1.length === bundles2.length &&
    bundles1.every(({lang}, i) => lang === bundles2[i].lang);
}
<commit_msg>Document the base Localization class<commit_after>import { keysFromContext, valueFromContext, entityFromContext }
  from '../format';

export const properties = new WeakMap();
export const contexts = new WeakMap();

/**
 * The `Localization` class is responsible for fetching resources and
 * formatting translations.
 *
 * It implements the fallback strategy in case of errors encountered during the
 * formatting of translations.
 *
 * In HTML and XUL, l20n.js will create an instance of `Localization` for the
 * default set of `<link rel="localization">` elements.  You can get
 * a reference to it via:
 *
 *     const localization = document.l10n.get('main');
 *
 * Different names can be specified via the `name` attribute on the `<link>`
 * elements.  One `document` can have more than one `Localization` instance,
 * but one `Localization` instance can only be assigned to a single `document`.
 *
 * `HTMLLocalization` and `XULLocalization` extend `Localization` and provide
 * `document`-specific methods for sanitizing translations containing markup
 * before they're inserted into the DOM.
 */
export class Localization {

  /**
   * Create an instance of the `Localization` class.
   *
   * The instance's configuration is provided by two runtime-dependent
   * functions passed to the constructor.
   *
   * The `requestBundles` function takes an array of language codes and returns
   * a Promise of an array of lazy `ResourceBundle` instances.  The
   * `Localization` instance will imediately call the `fetch` method of the
   * first bundle returned by `requestBundles` and may call `fetch` on
   * subsequent bundles in fallback scenarios.
   *
   * The array of bundles is the de-facto current fallback chain of languages
   * and fetch locations.
   *
   * The `createContext` function takes a language code and returns an instance
   * of `Intl.MessageContext`.  Since it's also provided to the constructor by
   * the runtime it may pass runtime-specific `functions` to the
   * `MessageContext` instances it creates.
   *
   * @param   {function}     requestBundles
   * @param   {function}     createContext
   * @returns {Localization}
   */
  constructor(requestBundles, createContext) {

    /**
     * A Promise which resolves when the `Localization` instance has fetched
     * and parsed all localization resources in the user's first preferred
     * language (if available).
     *
     *     localization.interactive.then(callback);
     */
    this.interactive = requestBundles().then(
      bundles => fetchFirstBundle(bundles, createContext)
    );

    // Keep `requestBundles` and `createContext` private.
    properties.set(this, {
      requestBundles, createContext
    });
  }

  /**
   * Initiate the change of the currently negotiated languages.
   *
   * `requestLanguages` takes an array of language codes representing user's
   * updated language preferences.
   *
   * @param   {string[]}     requestedLangs
   * @returns {Promise}
   */
  requestLanguages(requestedLangs) {
    // Get the current bundles to be able to compare them to the new result of
    // the language negotiation.
    return this.interactive.then(
      bundles => changeLanguages(this, bundles, requestedLangs)
    );
  }

  /**
   * Format translations and handle fallback if needed.
   *
   * Format translations for `keys` from `MessageContext` instances
   * corresponding to the current bundles.  In case of errors, fetch the next
   * bundle in the fallback chain, create a context for it, and recursively
   * call `formatWithFallback` again.
   *
   * @param   {ResourceBundle[]}   bundles - Current bundles.
   * @param   {Array[]}            keys    - Translation keys to format.
   * @param   {function}           method  - Formatting function.
   * @param   {string[]}           prev    - Previously formatted translations.
   * @returns {string[] | Promise}
   * @api     private
   */
  formatWithFallback(bundles, keys, method, prev) {
    const ctx = contexts.get(bundles[0]);

    // If a context for the head bundle doesn't exist we've reached the last
    // bundle in the fallback chain.  This is the end condition which returns
    // the translations formatted during the previous (recursive) calls to
    // `formatWithFallback`.
    if (!ctx && prev) {
      return prev.translations;
    }

    const current = keysFromContext(ctx, keys, method, prev);

    // `hasErrors` is a flag set by `keysFromContext` to notify about errors
    // during the formatting.  We can't just check the `length` of the `errors`
    // property because it is fixed and equal to the length of `keys`.
    if (!current.hasErrors) {
      return current.translations;
    }

    // In Gecko `console` needs to imported explicitly.
    if (typeof console !== 'undefined') {
      // The `errors` property is an array of arrays, each containing all
      // errors encountered for the translation at the same position in `keys`.
      // If there were no errors for a given translation, `errors` will contain
      // an `undefined` instead of the array of errors.  Most translations are
      // simple string which don't produce errors.
      current.errors.forEach(
        errs => errs ? errs.forEach(
          e => console.warn(e) // eslint-disable-line no-console
        ) : null
      );
    }

    const { createContext } = properties.get(this);

    // At this point we need to fetch the next bundle in the fallback chain and
    // create a `MessageContext` instance for it.
    return fetchFirstBundle(bundles.slice(1), createContext).then(
      tailBundles => this.formatWithFallback(
        tailBundles, keys, method, current
      )
    );
  }

  /**
   * Format translations into {value, attrs} objects.
   *
   * This is an internal method used by `LocalizationObserver` instances.  The
   * fallback logic is the same as in `formatValues` but the argument type is
   * stricter (an array of arrays) and it returns {value, attrs} objects which
   * are suitable for the translation of DOM elements.
   *
   *     document.l10n.formatEntities([j
   *       ['hello', { who: 'Mary' }],
   *       ['welcome', undefined]
   *     ]).then(console.log);
   *
   *     // [
   *     //   { value: 'Hello, Mary!', attrs: null },
   *     //   { value: 'Welcome!', attrs: { title: 'Hello' } }
   *     // ]
   *
   * Returns a Promise resolving to an array of the translation strings.
   *
   * @param   {Array[]}   keys
   * @returns {Promise}
   * @api     private
   */
  formatEntities(keys) {
    return this.interactive.then(
      bundles => this.formatWithFallback(bundles, keys, entityFromContext)
    );
  }

  /**
   * Retrieve translations corresponding to the passed keys.
   *
   * A generalized version of `Localization.formatValue`.  Keys can either be
   * simple string identifiers or `[id, args]` arrays.
   *
   *     document.l10n.formatValues(
   *       ['hello', { who: 'Mary' }],
   *       ['hello', { who: 'John' }],
   *       'welcome'
   *     ).then(console.log);
   *
   *     // ['Hello, Mary!', 'Hello, John!', 'Welcome!']
   *
   * Returns a Promise resolving to an array of the translation strings.
   *
   * @param   {...(Array | string)}   keys
   * @returns {Promise}
   */
  formatValues(...keys) {
    // Convert string keys into arrays that `formatWithFallback` expects.
    const keyTuples = keys.map(
      key => Array.isArray(key) ? key : [key, null]
    );
    return this.interactive.then(
      bundles => this.formatWithFallback(bundles, keyTuples, valueFromContext)
    );
  }

  /**
   * Retrieve the translation corresponding to the `id` identifier.
   *
   * If passed, `args` is a simple hash object with a list of variables that
   * will be interpolated in the value of the translation.
   *
   *     localization.formatValue(
   *       'hello', { who: 'world' }
   *     ).then(console.log);
   *
   *     // 'Hello, world!'
   *
   * Returns a Promise resolving to the translation string.
   *
   * Use this sparingly for one-off messages which don't need to be
   * retranslated when the user changes their language preferences, e.g. in
   * notifications.
   *
   * @param   {string}  id   - Identifier of the translation to format
   * @param   {Object}  args - Optional external arguments
   * @returns {Promise}
   */
  formatValue(id, args) {
    return this.formatValues([id, args]).then(
      ([val]) => val
    );
  }

}

/**
 * Given a `ResourceBundle`, create an instance of `MessageContext`.
 *
 * Fetches the bundle's resources and creates a context from them.
 *
 * @param   {ResourceBundle}  bundle
 * @param   {function}        createContext
 * @returns {Promise}
 * @api     private
 */
function createContextFromBundle(bundle, createContext) {
  return bundle.fetch().then(resources => {
    const ctx = createContext(bundle.lang);
    resources
      // Filter out resources which failed to load correctly (e.g. 404).
      .filter(res => res !== null)
      .forEach(res => ctx.addMessages(res));
    // Save the reference to the context.
    contexts.set(bundle, ctx);
    return ctx;
  });
}

/**
 * Create a `MessageContext` for the first bundle in the fallback chain.
 *
 * @param   {ResourceBundle[]}  bundle
 * @param   {function}          createContext
 * @returns {Promise}
 * @api     private
 */
export function fetchFirstBundle(bundles, createContext) {
  const [bundle] = bundles;

  if (!bundle) {
    return Promise.resolve(bundles);
  }

  return createContextFromBundle(bundle, createContext).then(
    () => bundles
  );
}

/**
 * Perform language negotiation.
 *
 * @returns {Promise}
 * @api     private
 */
function changeLanguages(l10n, oldBundles, requestedLangs) {
  const { requestBundles, createContext } = properties.get(l10n);

  return l10n.interactive = requestBundles(requestedLangs).then(
    newBundles => equal(oldBundles, newBundles) ?
      oldBundles : fetchFirstBundle(newBundles, createContext)
  );
}

/**
 *
 * Test of two fallback chains are functionally the same.
 *
 * @param   {ResourceBundle[]}  bundles1
 * @param   {ResourceBundle[]}  bundles2
 * @returns {boolean}
 * @api     private
 */
function equal(bundles1, bundles2) {
  return bundles1.length === bundles2.length &&
    bundles1.every(({lang}, i) => lang === bundles2[i].lang);
}
<|endoftext|>


<commit_before>var parser = require('./query-parser'),
    iterator = require('./iterator');

var selector = (function() {
    "use strict";

    var getMatchingFunction = function(query) {
        var type = typeof query,
            matchingFunction;

        if(type === 'function') {
            matchingFunction = query;
        } else if(type === 'string') {
            var ruleSetList = parser(query);

            matchingFunction = function(element) {
                throw new Error('To implement');
            };
        } else {
            throw new Error('Unexpected type of query : ' + type);
        }

        return matchingFunction;
    };

    var getElements = function(root, query, limit) {
        throw new Error('To implement');
    };

    var getElement = function(root, query) {
        return getElements(root, query, 1);
    };

    var getParents = function(root, query, limit) {
        throw new Error('To implement');
    };

    var getParent = function(root, query) {
        return getParents(root, query, 1);
    };

    var selector = getElements;
    selector.getElements = getElements;
    selector.getElement = getElement;
    selector.getParents = getParents;
    selector.getParent = getParent;

    return selector;
}());

module.exports = selector;<commit_msg>crude implementation of matchingFunction<commit_after>var parser = require('./query-parser'),
    iterator = require('./iterator');

var selector = (function() {
    "use strict";

    var getMatchingFunction = function(query) {
        var type = typeof query,
            matchingFunction;

        if(type === 'function') {
            matchingFunction = query;
        } else if(type === 'string') {
            var ruleSetList = parser(query);

            matchingFunction = function(element) {
                for(var i = 0; i < ruleSetList.length; i++) {
                    var ruleSet = ruleSetList[i];

                    var matching = true;

                    for(var j = 0; j < ruleSet.length && matching; j++) {
                        var property = ruleSet[j].property,
                            value = ruleSet[j].value,
                            operand = ruleSet[j].operand; //willfully ignored so far

                        if(property === 'class') {
                            var className = element.className;

                            matching = (className && (new RegExp('(^| )' + value + '($| )')).test(className));
                        } else if(property === 'tagname') {
                            var tagname = element.apiName.substr(element.apiName.lastIndexOf('.') + 1).toLowerCase();

                            matching = (tagname == value.toLowerCase());
                        } else {
                            matching = (element[property] == value);
                        }
                    }

                    if(matching) {
                        return true;
                    }
                }

                return false;
            };
        } else {
            throw new Error('Unexpected type of query : ' + type);
        }

        return matchingFunction;
    };

    var getElements = function(root, query, limit) {
        throw new Error('To implement');
    };

    var getElement = function(root, query) {
        return getElements(root, query, 1);
    };

    var getParents = function(root, query, limit) {
        throw new Error('To implement');
    };

    var getParent = function(root, query) {
        return getParents(root, query, 1);
    };

    var selector = getElements;
    selector.getElements = getElements;
    selector.getElement = getElement;
    selector.getParents = getParents;
    selector.getParent = getParent;

    return selector;
}());

module.exports = selector;<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');

const TAB_WIDTH = 8;

class Terminal {
  constructor() {
    this.state = {
      lastRefresh: 0,
      lines: [''],
      lineWrap: 256,
      maxLines: 2048,
      pointerLine: 0,
      pointerColumn: 0,
      refreshDelayMillis: 64,
      refreshQueued: null,
      trimCount: 64
    };

    this.queue = this.queue.bind(this);
  }

  queue(cb){
    this.state.lastRefresh = Date.now();
    this.state.refreshQueued = null;
    cb();
  }

  clearAll(){
    const { refreshQueued } = this.state;

    this.state.lines = [''];
    this.state.pointerLine = 0;
    this.state.pointerColumn = 0;

    if(refreshQueued != null){
      clearInterval(refreshQueued);
      this.state.refreshQueued = null;
    }
    this.state.lastRefresh = 0;
  }

  refreshBuffer(msg, cb) {
    const { lastRefresh, refreshDelayMillis, refreshQueued } = this.state;

    this._update(msg);

    if(refreshQueued != null){
      return;
    }

    // TODO: remove zalgo
    if(lastRefresh < Date.now() - refreshDelayMillis){
      this.queue(cb);
    } else {
      this.state.refreshQueued = setTimeout(this.queue, refreshDelayMillis, cb);
    }
  }

  _update(events) {
    if(Array.isArray(events)){
      _.forEach(events, this.processEvent, this);
    }else{
      this.processEvent(events);
    }
  }

  setCursorPosition(line, col){
    const { lines } = this.state;
    for(var ix = lines.length; ix <= line; ix++){
      lines[ix] = '';
    }
    this.state.pointerLine = Math.max(0, line);
    this.state.pointerColumn = Math.min(255, Math.max(0, col));
  }

  backspace(){
    const { lines, pointerLine, pointerColumn } = this.state;
    if(pointerColumn > 0){
      const targetLine = lines[pointerLine];
      if(pointerColumn < targetLine.length){
        lines[pointerLine] = targetLine.slice(0, pointerColumn - 1) + targetLine.slice(pointerColumn);
      } else {
        lines[pointerLine] = targetLine.slice(0, pointerColumn - 1);
      }
      this.state.pointerColumn = pointerColumn - 1;
    } else {
      const prevLineNum = Math.max(0, pointerLine - 1);
      const prevLine = lines[prevLineNum] || '';
      this.state.pointerLine = prevLineNum;
      this.state.pointerColumn = prevLine.length;
    }
  }

  clearEol(){
    const { lines, pointerLine, pointerColumn } = this.state;
    const line = lines[pointerLine] || '';
    if(pointerColumn < line.length){
      lines[pointerLine] = line.slice(0, pointerColumn);
    }
  }

  clearBelow(){
    const { lines, pointerLine } = this.state;
    const newLines = lines.slice(0, Math.min(pointerLine + 1, lines.length));
    this.state.lines = newLines;
  }

  tab(){
    const { pointerColumn } = this.state;
    const tabColumn = Math.floor(pointerColumn / TAB_WIDTH);
    this.state.pointerColumn = (tabColumn + 1) * TAB_WIDTH;
  }

  processEvent(evt){
    const { pointerLine, pointerColumn } = this.state;
    switch(evt.type){
      case 'text':
        this.addText(evt.data);
        break;
      case 'linefeed':
        this.setCursorPosition(pointerLine + 1, 0);
        break;
      case 'cursor-home':
        this.setCursorPosition(0, 0);
        break;
      case 'cursor-position':
        this.setCursorPosition(evt.data[1], evt.data[0]);
        break;
      case 'cursor-position-x':
        this.setCursorPosition(pointerLine, evt.data[0]);
        break;
      case 'cursor-position-y':
        this.setCursorPosition(evt.data[0], pointerColumn);
        break;
      case 'cursor-left':
        this.setCursorPosition(pointerLine, pointerColumn - 1);
        break;
      case 'cursor-right':
        this.setCursorPosition(pointerLine, pointerColumn + 1);
        break;
      case 'cursor-up':
        this.setCursorPosition(pointerLine - 1, pointerColumn);
        break;
      case 'cursor-down':
        this.setCursorPosition(pointerLine + 1, pointerColumn);
        break;
      case 'clear-screen':
        this.clearAll();
        break;
      case 'backspace':
        this.backspace();
        break;
      case 'clear-eol':
        this.clearEol();
        break;
      case 'clear-below':
        this.clearBelow();
        break;
      case 'tab':
        this.tab();
        break;
      default:
        console.log('Not yet implemented:', evt);
        break;
    }
  }

  addText(newText){
    const { lines, lineWrap, trimCount, maxLines } = this.state;

    let { pointerColumn, pointerLine } = this.state;

    while(newText.length > 0){
      let oldLine = lines[pointerLine] || '';
      let initial = _.padRight(oldLine.slice(0, pointerColumn), pointerColumn);

      let insert = newText.slice(0, Math.max(lineWrap - initial.length, 0));

      let remainder = oldLine.slice(insert.length + initial.length);

      let leftOver = newText.slice(insert.length);

      lines[pointerLine] = initial + insert + remainder;

      newText = leftOver;
      if(newText.length > 0){
        pointerLine++;
        pointerColumn = 0;
      }else{
        pointerColumn = initial.length + insert.length;
      }
    }

    if(lines.length > maxLines){
      const newLines = lines.slice(trimCount);
      this.state.lines = newLines;
      this.state.pointerLine = Math.max(0, pointerLine - trimCount);
      this.state.pointerColumn = pointerColumn;
    } else {
      this.state.pointerLine = pointerLine;
      this.state.pointerColumn = pointerColumn;
    }
  }

  getLines(){
    return this.state.lines;
  }

}

module.exports = Terminal;
<commit_msg>Improve clear-down to avoid accidental scrolling<commit_after>'use strict';

const _ = require('lodash');

const TAB_WIDTH = 8;

class Terminal {
  constructor() {
    this.state = {
      lastRefresh: 0,
      lines: [''],
      lineWrap: 256,
      maxLines: 2048,
      pointerLine: 0,
      pointerColumn: 0,
      refreshDelayMillis: 64,
      refreshQueued: null,
      trimCount: 64
    };

    this.queue = this.queue.bind(this);
  }

  queue(cb){
    this.state.lastRefresh = Date.now();
    this.state.refreshQueued = null;
    cb();
  }

  clearAll(){
    const { refreshQueued } = this.state;

    this.state.lines = [''];
    this.state.pointerLine = 0;
    this.state.pointerColumn = 0;

    if(refreshQueued != null){
      clearInterval(refreshQueued);
      this.state.refreshQueued = null;
    }
    this.state.lastRefresh = 0;
  }

  refreshBuffer(msg, cb) {
    const { lastRefresh, refreshDelayMillis, refreshQueued } = this.state;

    this._update(msg);

    if(refreshQueued != null){
      return;
    }

    // TODO: remove zalgo
    if(lastRefresh < Date.now() - refreshDelayMillis){
      this.queue(cb);
    } else {
      this.state.refreshQueued = setTimeout(this.queue, refreshDelayMillis, cb);
    }
  }

  _update(events) {
    if(Array.isArray(events)){
      _.forEach(events, this.processEvent, this);
    }else{
      this.processEvent(events);
    }
  }

  setCursorPosition(line, col){
    const { lines } = this.state;
    for(var ix = lines.length; ix <= line; ix++){
      lines[ix] = '';
    }
    this.state.pointerLine = Math.max(0, line);
    this.state.pointerColumn = Math.min(255, Math.max(0, col));
  }

  backspace(){
    const { lines, pointerLine, pointerColumn } = this.state;
    if(pointerColumn > 0){
      const targetLine = lines[pointerLine];
      if(pointerColumn < targetLine.length){
        lines[pointerLine] = targetLine.slice(0, pointerColumn - 1) + targetLine.slice(pointerColumn);
      } else {
        lines[pointerLine] = targetLine.slice(0, pointerColumn - 1);
      }
      this.state.pointerColumn = pointerColumn - 1;
    } else {
      const prevLineNum = Math.max(0, pointerLine - 1);
      const prevLine = lines[prevLineNum] || '';
      this.state.pointerLine = prevLineNum;
      this.state.pointerColumn = prevLine.length;
    }
  }

  clearEol(){
    const { lines, pointerLine, pointerColumn } = this.state;
    const line = lines[pointerLine] || '';
    if(pointerColumn < line.length){
      lines[pointerLine] = line.slice(0, pointerColumn);
    }
  }

  clearBelow(){
    const { lines, pointerLine } = this.state;
    for(let ix = pointerLine+1; ix < lines.length; ix++){
      lines[ix] = '';
    }
  }

  tab(){
    const { pointerColumn } = this.state;
    const tabColumn = Math.floor(pointerColumn / TAB_WIDTH);
    this.state.pointerColumn = (tabColumn + 1) * TAB_WIDTH;
  }

  processEvent(evt){
    const { pointerLine, pointerColumn } = this.state;
    switch(evt.type){
      case 'text':
        this.addText(evt.data);
        break;
      case 'linefeed':
        this.setCursorPosition(pointerLine + 1, 0);
        break;
      case 'cursor-home':
        this.setCursorPosition(0, 0);
        break;
      case 'cursor-position':
        this.setCursorPosition(evt.data[1], evt.data[0]);
        break;
      case 'cursor-position-x':
        this.setCursorPosition(pointerLine, evt.data[0]);
        break;
      case 'cursor-position-y':
        this.setCursorPosition(evt.data[0], pointerColumn);
        break;
      case 'cursor-left':
        this.setCursorPosition(pointerLine, pointerColumn - 1);
        break;
      case 'cursor-right':
        this.setCursorPosition(pointerLine, pointerColumn + 1);
        break;
      case 'cursor-up':
        this.setCursorPosition(pointerLine - 1, pointerColumn);
        break;
      case 'cursor-down':
        this.setCursorPosition(pointerLine + 1, pointerColumn);
        break;
      case 'clear-screen':
        this.clearAll();
        break;
      case 'backspace':
        this.backspace();
        break;
      case 'clear-eol':
        this.clearEol();
        break;
      case 'clear-below':
        this.clearBelow();
        break;
      case 'tab':
        this.tab();
        break;
      default:
        console.log('Not yet implemented:', evt);
        break;
    }
  }

  addText(newText){
    const { lines, lineWrap, trimCount, maxLines } = this.state;

    let { pointerColumn, pointerLine } = this.state;

    while(newText.length > 0){
      let oldLine = lines[pointerLine] || '';
      let initial = _.padRight(oldLine.slice(0, pointerColumn), pointerColumn);

      let insert = newText.slice(0, Math.max(lineWrap - initial.length, 0));

      let remainder = oldLine.slice(insert.length + initial.length);

      let leftOver = newText.slice(insert.length);

      lines[pointerLine] = initial + insert + remainder;

      newText = leftOver;
      if(newText.length > 0){
        pointerLine++;
        pointerColumn = 0;
      }else{
        pointerColumn = initial.length + insert.length;
      }
    }

    if(lines.length > maxLines){
      const newLines = lines.slice(trimCount);
      this.state.lines = newLines;
      this.state.pointerLine = Math.max(0, pointerLine - trimCount);
      this.state.pointerColumn = pointerColumn;
    } else {
      this.state.pointerLine = pointerLine;
      this.state.pointerColumn = pointerColumn;
    }
  }

  getLines(){
    return this.state.lines;
  }

}

module.exports = Terminal;
<|endoftext|>


<commit_before>import _ from 'lodash';
import * as TextFormatting from 'src/helpers/TextFormatting';
import formatIrcMessage from 'src/libs/MessageFormatter';
import GlobalApi from 'src/libs/GlobalApi';
import state from './state';

let nextId = 0;

export default class Message {
    constructor(message, user) {
        this.id = nextId++;
        this.time = message.time || Date.now();
        this.nick = message.nick;
        this.message = message.message;
        this.tags = message.tags;
        this.type = message.type || 'message';
        this.type_extra = message.type_extra;
        this.ignore = false;
        this.mentioned_urls = [];
        this.html = '';

        // We don't want the user object to be enumerable
        Object.defineProperty(this, 'user', {
            value: user,
        });
    }

    toHtml(messageList) {
        if (this.html) {
            return this.html;
        }

        // Allow plugins to render their own messages if needed
        GlobalApi.singleton().emit('message:render', { message: this });
        if (this.html) {
            return this.html;
        }

        let showEmoticons = state.setting('buffers.show_emoticons') &&
            !messageList.buffer.isSpecial();
        let emojiList = state.setting('emojis');
        let emojiLocation = state.setting('emojiLocation');
        let userList = messageList.buffer.users;
        let useExtraFormatting = !messageList.buffer.isSpecial() &&
            messageList.useExtraFormatting &&
            this.type === 'privmsg';

        let html = '';
        let blocks = formatIrcMessage(this.message, {
            extras: useExtraFormatting,
        });

        blocks.forEach((bl, idx) => {
            let style = '';
            let classes = '';

            Object.keys(bl.styles).forEach(s => {
                if (s === 'underline') {
                    style += 'text-decoration:underline;';
                } else if (s === 'bold') {
                    style += 'font-weight:bold;';
                } else if (s === 'italic') {
                    style += 'font-style:italic;';
                } else if (s === 'quote') {
                    classes += 'kiwi-formatting-extras-quote ';
                } else if (s === 'block') {
                    classes += 'kiwi-formatting-extras-block ';
                } else if (s === 'color') {
                    classes += `irc-fg-colour-${bl.styles[s]} `;
                } else if (s === 'background') {
                    classes += `irc-bg-colour-${bl.styles[s]} `;
                }
            });

            let content = this.enrichText(
                bl.content,
                showEmoticons,
                emojiList,
                emojiLocation,
                userList
            );

            if (style === '' && classes === '') {
                html += content;
            } else if (style !== '' && classes !== '') {
                html += `<span style="${style}" class="${classes}">${content}</span>`;
            } else if (style !== '') {
                html += `<span style="${style}">${content}</span>`;
            } else if (classes !== '') {
                html += `<span class="${classes}">${content}</span>`;
            }
        });

        this.html = html;
        return this.html;
    }

    enrichText(text, showEmoticons, emojiList, emojiLocation, userList) {
        let words = text.split(' ');
        words = words.map((word, wordIdx) => {
            let parsed;

            let linkified = TextFormatting.linkifyUrls(word, {
                addHandle: true,
                handleClass: 'fa fa-chevron-right kiwi-messagelist-message-linkhandle',
            });
            if (linkified.urls.length > 0) {
                this.mentioned_urls = this.mentioned_urls.concat(linkified.urls);
            }
            if (linkified.html !== word) return linkified.html;

            parsed = TextFormatting.linkifyChannels(word);
            if (parsed !== word) return parsed;

            parsed = TextFormatting.linkifyUsers(word, userList);
            if (parsed !== word) return parsed;

            if (showEmoticons) {
                parsed = TextFormatting.addEmojis(
                    { word, words, wordIdx },
                    emojiList,
                    emojiLocation
                );
                if (parsed !== word) return parsed;
            }

            return _.escape(word);
        });

        return words.join(' ');
    }
}
<commit_msg>Make use of IRCv3 message IDs<commit_after>import _ from 'lodash';
import * as TextFormatting from 'src/helpers/TextFormatting';
import formatIrcMessage from 'src/libs/MessageFormatter';
import GlobalApi from 'src/libs/GlobalApi';
import state from './state';

let nextId = 0;

export default class Message {
    constructor(message, user) {
        this.id = message.tags && message.tags['draft/msgid'] ?
            message.tags['draft/msgid'] :
            nextId++;
        this.time = message.time || Date.now();
        this.nick = message.nick;
        this.message = message.message;
        this.tags = message.tags;
        this.type = message.type || 'message';
        this.type_extra = message.type_extra;
        this.ignore = false;
        this.mentioned_urls = [];
        this.html = '';

        // We don't want the user object to be enumerable
        Object.defineProperty(this, 'user', {
            value: user,
        });
    }

    toHtml(messageList) {
        if (this.html) {
            return this.html;
        }

        // Allow plugins to render their own messages if needed
        GlobalApi.singleton().emit('message:render', { message: this });
        if (this.html) {
            return this.html;
        }

        let showEmoticons = state.setting('buffers.show_emoticons') &&
            !messageList.buffer.isSpecial();
        let emojiList = state.setting('emojis');
        let emojiLocation = state.setting('emojiLocation');
        let userList = messageList.buffer.users;
        let useExtraFormatting = !messageList.buffer.isSpecial() &&
            messageList.useExtraFormatting &&
            this.type === 'privmsg';

        let html = '';
        let blocks = formatIrcMessage(this.message, {
            extras: useExtraFormatting,
        });

        blocks.forEach((bl, idx) => {
            let style = '';
            let classes = '';

            Object.keys(bl.styles).forEach(s => {
                if (s === 'underline') {
                    style += 'text-decoration:underline;';
                } else if (s === 'bold') {
                    style += 'font-weight:bold;';
                } else if (s === 'italic') {
                    style += 'font-style:italic;';
                } else if (s === 'quote') {
                    classes += 'kiwi-formatting-extras-quote ';
                } else if (s === 'block') {
                    classes += 'kiwi-formatting-extras-block ';
                } else if (s === 'color') {
                    classes += `irc-fg-colour-${bl.styles[s]} `;
                } else if (s === 'background') {
                    classes += `irc-bg-colour-${bl.styles[s]} `;
                }
            });

            let content = this.enrichText(
                bl.content,
                showEmoticons,
                emojiList,
                emojiLocation,
                userList
            );

            if (style === '' && classes === '') {
                html += content;
            } else if (style !== '' && classes !== '') {
                html += `<span style="${style}" class="${classes}">${content}</span>`;
            } else if (style !== '') {
                html += `<span style="${style}">${content}</span>`;
            } else if (classes !== '') {
                html += `<span class="${classes}">${content}</span>`;
            }
        });

        this.html = html;
        return this.html;
    }

    enrichText(text, showEmoticons, emojiList, emojiLocation, userList) {
        let words = text.split(' ');
        words = words.map((word, wordIdx) => {
            let parsed;

            let linkified = TextFormatting.linkifyUrls(word, {
                addHandle: true,
                handleClass: 'fa fa-chevron-right kiwi-messagelist-message-linkhandle',
            });
            if (linkified.urls.length > 0) {
                this.mentioned_urls = this.mentioned_urls.concat(linkified.urls);
            }
            if (linkified.html !== word) return linkified.html;

            parsed = TextFormatting.linkifyChannels(word);
            if (parsed !== word) return parsed;

            parsed = TextFormatting.linkifyUsers(word, userList);
            if (parsed !== word) return parsed;

            if (showEmoticons) {
                parsed = TextFormatting.addEmojis(
                    { word, words, wordIdx },
                    emojiList,
                    emojiLocation
                );
                if (parsed !== word) return parsed;
            }

            return _.escape(word);
        });

        return words.join(' ');
    }
}
<|endoftext|>


<commit_before>
// modules ====================================================
var express = require("express");
var r = require("rethinkdb");
var bluebird = require("bluebird");
//var jwt = require("express-jwt");
//var cors = require("cors");
//var dbModel = new db();
//dbModel.setupDisastersDB();

// configuration ============================================
// config files
//require('../Javascript/mongodb_config.js');
var config = require('./Javascript/rethinkdb_config.js');

var app = express();
//app.use(express.static(__dirname + "/public"));
var port = 3500;        // or var port = process.env.PORT || 3500;
var scrapeCount = 1;
var database = "quake";

// USGS earthquake data feed url
var feedUrl = "earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson";

// Fetch data from USGS, transform locations into point objects.
// Insert the data into the 'earthquakes' table.
var refresh = r.table("quakes").insert(r.http("earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson")("features").merge(function(item) {
  return {
    geometry: r.point(item("geometry")("coordinates")(0),
      item("geometry")("coordinates")(1))
  }
}), {conflict: function(id, oldDoc, newDoc) {
  return oldDoc;
}});

// Initial setup, creating the database and table and geospatial
// index on the 'geometry' property, access the above querry and
// populate the table with data
var conn;
r.connect(config.database).then(function(c) {
    conn = c;
    return r.dbCreate(config.database.db).run(conn);
}).then(function() {
    return r.tableCreate("quakes").run(conn);
}).then(function() {
    return r.table("quakes").indexCreate(
        "propertiesTime", r.row('properties')('time'), {multi: true}).run(conn);
}).then(function() {
    return refresh.run(conn);
}).error(function(err) {
    if(err.msg.indexOf("already exists") == -1)
        console.log(err);
}).finally(function () {
    if(conn)
        conn.close();
});

// Use the refresh query above to automatically update the 'earthquake' database
// with new data at 30 minutes intervals and delete the records that are older than 30 days
var time;
setInterval(function() {
    time = new Date();
    console.log("Scrapping number: " + scrapeCount++ + ", current-time: " + time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds());

    var connect;

    r.connect(config.database).then(function (c) {
        connect = c;

        return bluebird.join(refresh.run(connect), r.table("quakes")
            .filter(r.epochTime(r.row("properties")("time").div(1000)).lt(
                r.now().sub(60 * 60 * 24 * 30))).delete().run(connect));
    }).error(function(err) {
        console.log("Failed to refresh:", err);
    }).finally(function() {
        if (connect)
            connect.close();
    });
}, (5 * 1000 * 60));

// start app =================================================
app.listen(port);
console.log("Scrapper Server running on port: " + port);
exports = module.exports = app;
<commit_msg>conflicting names<commit_after>
// modules ====================================================
var express = require("express");
var r = require("rethinkdb");
var bluebird = require("bluebird");
//var jwt = require("express-jwt");
//var cors = require("cors");
//var dbModel = new db();
//dbModel.setupDisastersDB();

// configuration ============================================
// config files
//require('../Javascript/mongodb_config.js');
var config = require('./Javascript/rethinkdb_config.js');

var app = express();
//app.use(express.static(__dirname + "/public"));
var port = 3500;        // or var port = process.env.PORT || 3500;
var scrapeCount = 1;
var database = "quake";

// USGS earthquake data feed url
var feedUrl = "earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson";

// Fetch data from USGS, transform locations into point objects.
// Insert the data into the 'earthquakes' table.
var refresh = r.db("earthquakes").table("quakes").insert(r.http("http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson")("features").merge(function(item) {
  return {
    geometry: r.point(item("geometry")("coordinates")(0),
      item("geometry")("coordinates")(1))
  }
}), {conflict: function(id, oldDoc, newDoc) {
  return oldDoc;
}});

// Use the refresh query above to automatically update the 'earthquake' database
// with new data at 30 minutes intervals and delete the records that are older than 30 days
var time;
setInterval(function() {
    time = new Date();
    console.log("Scrapping number: " + scrapeCount++ + ", current-time: " + time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds());

    var connect;

    r.connect(config.database).then(function (c) {
        connect = c;

        return bluebird.join(refresh.run(connect), r.table("quakes")
            .filter(r.epochTime(r.row("properties")("time").div(1000)).lt(
                r.now().sub(60 * 60 * 24 * 30))).delete().run(connect));
    }).error(function(err) {
        console.log("Failed to refresh:", err);
    }).finally(function() {
        if (connect)
            connect.close();
    });
}, (5 * 1000 * 60));

// start app =================================================
app.listen(port);
console.log("Scrapper Server running on port: " + port);
exports = module.exports = app;
<|endoftext|>