<commit_before>/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ /*global define */

function EventRecorder(params) {
	"use strict";
	var ms = this; // myself = ms
	this.start		= null;
	this.actual		= 0;	// holds the recorded time
	this.recording	= false;

	this.apt		= 0; // actual_playback_time

	this.events		= [];

	this.width		= null;
	this.height		= null;

	this.it			= 1; // iterator for playplack

	this.mouse      = null;

	this.all		= []; // holds all the elements of the page

	this.active_input = null; // for text area and input fields

	// records the movements
	this.record = function record() {
		ms.width	= window.innerWidth;
		ms.height	= window.innerHeight;
		ms.recording = true;
		ms.start = new Date().getTime();
		ms.loop();
	};

	// stop
	this.stop = function stop() {
		ms.recording	= false;
	};

	//
	this.key_input = function key_input(key) {
		var c,
		text,
		div = document.getElementById(ms.active_input);
		text = div.value;

		console.log(key.keyCode);

		if (key.keyCode === 8) // Backspace
			{
			if (text.length !== 0) text = text.slice(0, text.length -1);
			div.value = text;
			}
		else
			{
			c = String.fromCharCode(key.charCode);
			text += c;

			div.value = text;
			}

	};

	this.special_keys = function special_keys(keyCode) {

		var c,
		text,
		div = document.getElementById(ms.active_input);
		text = div.value;

		switch(keyCode)
			{
			case 8:
				if (text.length !== 0) { text = text.slice(0, text.length -1); }
				div.value = text;
			break;
			}
	}

	this.display = function display() {
		var el,
			div,
			ev = ms.events[ms.it];

		ms.it ++;

		switch(ev.type) {
			case "mousemove":
				ms.mouse.style.left	= ev.x;
				ms.mouse.style.top	= ev.y;
			break;

			case "keypress":
				ms.key_input(ev.keyCode);
			break;

			case "keyup":
			   ms.special_keys(ev.keyCode);
			break;

			case "click":
				if (ev.elements) {
					for (var i = 0; i < ev.elements.length; i++) {
						el = ev.elements[i];
						if (el.id) {
							div = document.getElementById(el.id);
							div.click();

							if (el.focus === true) {
									div.focus();
									ms.active_input = div.id;
									}
							}
					}
				}
			break;

		}

		ms.replay();
	};

	// replay
	this.replay = function replay() {
		var ev, to;

		if (ms.it < ms.events.length) {
			ev = ms.events[ms.it];
			to = ev.time-ms.apt;
			// console.log(to);
			ms.apt = ev.time;
			window.setTimeout(ms.display, to);

		}
		else {
			ms.it = 0;	// sets the iterator back to the beginning
		}

	};

	this.loop = function loop() {
		ms.actual = new Date().getTime() - ms.start;
		if (ms.recording) { window.setTimeout(ms.loop, 20); }
	};

	// transforms absolute values into percentage value
	this.percentpos	= function percentpos(event) {
		var pos = {};
		pos.x = ((event.clientX / ms.width) *100).toFixed(4) + "%";
		pos.y = ((event.clientY / ms.height)*100).toFixed(4) + "%";
		return pos;
	};

	// checks which element is touched by the mouse
	this.get_element = function get_element(event) {
		var div,
			n = ms.all.length,
			style,
			list = [];

		console.log("get_element " + n);

		while(n--) {
			div = ms.all[n];
			style = div.getBoundingClientRect();
			if (event.clientX < style.right &&
				event.clientX > style.left &&
				event.clientY > style.top &&
				event.clientY < style.bottom) {
					if (div.id) {
						console.log(div.tagName);
						if (div.tagName === "TEXTAREA") list.push({ id: div.id,
																	focus: true });
						else list.push({ id: div.id });
						}
					else {
						if (div.className) { list.push({ className: div.className }); }
					}
				}

		}

		return list;
	};

	this.mouseclick	= function mouseclick(event) {
		if (ms.recording) {
			var pos, time, list;
			time = new Date().getTime() - ms.start;
			pos = ms.percentpos(event);

			list = ms.get_element(event);

			ms.events.push({
				type:	"click",
				x:		pos.x,
				y:		pos.y,
				time:	time,
				elements: list
			});

		}
	};

	this.mousemove = function mousemove(event) {
		if (ms.recording) {
			var pos, time = new Date().getTime() - ms.start;
			pos = ms.percentpos(event);

			ms.events.push({
				type:	"mousemove",
				x:		pos.x,
				y:		pos.y,
				time:	time
			});
		}
	};

	this.create_clipboard = function() {
		var s, node;
		var node = document.createElement("div");
		node.id = "EventRecorderJSON";

		document.body.appendChild(node);

		s = document.getElementById("EventRecorderJSON");

		s.style.width 	= "50%";
		s.style.height  = "80%";
		s.style.left 	= "20px";
		s.style.top		= "20px";
		s.style.position = "absolute";
		s.style.backgroundColor = "white";
		s.style.zIndex = 1000;
		s.style.border = "thick solid black";

		node = document.createElement("div");
		node.id = "EventRecorderData";

		s = document.getElementById("EventRecorderJSON");
		s.appendChild(node);

		s = document.getElementById("EventRecorderData");

		s.style.width 	= "96%";
		s.style.height  = "80%";
		s.style.left 	= "2%";
		s.style.top		= "2%";
		s.style.position = "absolute";
		// s.style.backgroundColor = "orange";
		s.style.overflow = "hidden";

		node = document.createElement("div");
		node.id = "SubmitEvents";

		s = document.getElementById("EventRecorderJSON");
		s.appendChild(node);

		s = document.getElementById("SubmitEvents");

		s.style.width 	= "16%";
		s.style.height  = "20px";
		s.style.left 	= "2%";
		s.style.bottom		= "2%";
		s.style.position = "absolute";
		s.style.backgroundColor = "gainsboro";
		s.style.padding = "10px";
		s.style.border = "solid black";
		s.style.textAlign = "center";
		s.style.cursor	  = "pointer";
		s.innerHTML = "CLICK + CTRL + C";

		s.onclick = ms.copy_to_clipboard;

		node = document.createElement("div");
		node.id = "CloseEvents";
		s = document.getElementById("EventRecorderJSON");
		s.appendChild(node);

		s = document.getElementById("CloseEvents");

		s.style.width 	= "10%";
		s.style.height  = "20px";
		s.style.left 	= "22%";
		s.style.bottom		= "2%";
		s.style.position = "absolute";
		s.style.backgroundColor = "gainsboro";
		s.style.padding = "10px";
		s.style.border = "solid black";
		s.style.textAlign = "center";
		s.style.cursor	  = "pointer";
		s.innerHTML = "CLOSE";

		s.onclick = ms.close_clipboard;

	};

	 this.copy_to_clipboard = function copy_to_clipboard() {

      var node = document.getElementById("EventRecorderData" );

        if ( document.selection ) {
            var range = document.body.createTextRange();
            range.moveToElementText( node  );
            range.select();
        } else if ( window.getSelection ) {
            var range = document.createRange();
            range.selectNodeContents( node );
            window.getSelection().removeAllRanges();
            window.getSelection().addRange( range );
        }
 	};

	this.close_clipboard = function close_clipboard() {
		var node = document.getElementById("EventRecorderJSON" );
		node.style.display = "none";
	}

	this.json = function json() {
		var node, div, s, text = JSON.stringify(ms.events, null, 2);

		div = document.getElementById("EventRecorderData");
		if (! div) {
			ms.create_clipboard();
			div = document.getElementById("EventRecorderData");
		};

		div.innerHTML = text;

		node = document.getElementById("EventRecorderJSON" );
		node.style.display = "block";
	};

	this.store_keyup_event = function(event) {

		if (ms.recording) {
			var time = new Date().getTime() - ms.start;
			console.log(event.keyCode);

			ms.events.push({
				type:	"keyup",
				keyCode: event.keyCode,
				time:	time
			});
		}
	}

	this.keyup = function keyup(event) {
		switch(event.keyCode)
		 {
		 case 8: // backspace
		 	ms.store_keyup_event(event);
		 break;
		 }
	};

	this.keypress = function keypress(event) {
		if (ms.recording) {
			var time = new Date().getTime() - ms.start;
			console.log(event.charCode);

			ms.events.push({
				type:	"keypress",
				keyCode: event,
				time:	time
			});
		}
	};

	// gets all the elements of the page
	this.get_all_elements = function get_all_elements() {
		ms.all = document.getElementsByTagName("*");
	};

	this.init = function init() {
		window.er = this;

		ms.get_all_elements();
		ms.mouse = document.getElementById("mymouse");

		document.onmousemove	= ms.mousemove;
		document.onclick		= ms.mouseclick;
		document.onkeypress		= ms.keypress;
		document.onkeyup		= ms.keyup;

		if (params) {
			if (params.events) ms.events = params.events;
			if (params.autostart) ms.replay();
		}

	};

	ms.init();
}

module.exports = exports = EventRecorder;<commit_msg>mouseover started<commit_after>/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ /*global define */

Array.prototype.diff = function(a) {
    return this.filter(function(i) {return a.indexOf(i) < 0;});
};

function EventRecorder(params) {
	"use strict";
	var ms = this; // myself = ms
	this.start		= null;
	this.actual		= 0;	// holds the recorded time
	this.recording	= false;

	this.apt		= 0; // actual_playback_time

	this.events		= [];

	this.width		= null;
	this.height		= null;

	this.it			= 1; // iterator for playplack

	this.mouse      = null;

	this.all		= []; // holds all the elements of the page

	this.active_input = null; // for text area and input fields

	this.touched_elements = []; // the elements that the virtual mouse touches

	// records the movements
	this.record = function record() {
		ms.width	= window.innerWidth;
		ms.height	= window.innerHeight;
		ms.recording = true;
		ms.start = new Date().getTime();
		ms.loop();
	};

	// stop
	this.stop = function stop() {
		ms.recording	= false;
	};

	//
	this.key_input = function key_input(key) {
		var c,
		text,
		div = document.getElementById(ms.active_input);
		text = div.value;

		console.log(key.keyCode);

		if (key.keyCode === 8) // Backspace
			{
			if (text.length !== 0) text = text.slice(0, text.length -1);
			div.value = text;
			}
		else
			{
			c = String.fromCharCode(key.charCode);
			text += c;

			div.value = text;
			}

	};

	this.special_keys = function special_keys(keyCode) {

		var c,
		text,
		div = document.getElementById(ms.active_input);
		text = div.value;

		switch(keyCode)
			{
			case 8:
				if (text.length !== 0) { text = text.slice(0, text.length -1); }
				div.value = text;
			break;
			}
	}

	this.display = function display() {
		var el,
			div,
			ev = ms.events[ms.it];

		ms.it ++;

		switch(ev.type) {
			case "mousemove":
				ms.mouse.style.left	= ev.x;
				ms.mouse.style.top	= ev.y;
			break;

			case "keypress":
				if (ev.key_ctrl) alert("KONTROLLE");
				ms.key_input(ev.keyCode);
			break;

			case "keyup":
			   ms.special_keys(ev.keyCode);
			break;

			case "click":
				if (ev.elements) {
					for (var i = 0; i < ev.elements.length; i++) {
						el = ev.elements[i];
						if (el.id) {
							div = document.getElementById(el.id);
							div.click();

							if (el.focus === true) {
									div.focus();
									ms.active_input = div.id;
									}
							}
					}
				}
			break;

		}

		ms.replay();
	};

	// replay
	this.replay = function replay() {
		var ev, to;

		if (ms.it < ms.events.length) {
			ev = ms.events[ms.it];
			to = ev.time-ms.apt;
			// console.log(to);
			ms.apt = ev.time;
			window.setTimeout(ms.display, to);

		}
		else {
			ms.it = 0;	// sets the iterator back to the beginning
		}

	};

	this.loop = function loop() {
		ms.actual = new Date().getTime() - ms.start;
		if (ms.recording) { window.setTimeout(ms.loop, 20); }
	};

	// transforms absolute values into percentage value
	this.percentpos	= function percentpos(event) {
		var pos = {};
		pos.x = ((event.clientX / ms.width) *100).toFixed(4) + "%";
		pos.y = ((event.clientY / ms.height)*100).toFixed(4) + "%";
		return pos;
	};

	// checks which element is touched by the mouse
	this.get_element = function get_element(event) {
		var div,
			n = ms.all.length,
			style,
			list = [];

		while(n--) {
			div = ms.all[n];
			style = div.getBoundingClientRect();
			if (event.clientX < style.right &&
				event.clientX > style.left &&
				event.clientY > style.top &&
				event.clientY < style.bottom) {
					if (div.id) {
						console.log(div.tagName);
						if (div.tagName === "TEXTAREA") list.push({ id: div.id,
																	focus: true });
						else list.push({ id: div.id });
						}
					else {
						if (div.className) { list.push({ className: div.className }); }
					}
				}

		}

		return list;
	};

	this.on_element = function on_element(event) {
		var div,
			n = ms.all.length,
			style,
			list = [];

			while(n--) {
				div = ms.all[n];
				style = div.getBoundingClientRect();
				if (event.clientX < style.right && event.clientX > style.left && event.clientY > style.top && event.clientY < style.bottom) {
					list.push(div);
				}

			}
	return list;

	};

	this.mouseclick	= function mouseclick(event) {
		if (ms.recording) {
			var pos, time, list;
			time = new Date().getTime() - ms.start;
			pos = ms.percentpos(event);

			list = ms.get_element(event);

			ms.events.push({
				type:	"click",
				x:		pos.x,
				y:		pos.y,
				time:	time,
				elements: list
			});

		}
	};

	// to detect a new element causing a mousover effect
	this.check_onmouseover = function check_onmouseover(event) {
		var old_elements = ms.touched_elements;
		ms.touched_elements = ms.on_element(event);

		var x = ms.touched_elements.diff( old_elements);

		if (x.length !== 0) console.log(x.length);

	};

	this.mousemove = function mousemove(event) {
		if (ms.recording) {
			var pos, time = new Date().getTime() - ms.start;
			pos = ms.percentpos(event);

			ms.events.push({
				type:	"mousemove",
				x:		pos.x,
				y:		pos.y,
				time:	time
			});

			ms.check_onmouseover(event);

		}
	};

	this.create_clipboard = function() {
		var s, node;
		var node = document.createElement("div");
		node.id = "EventRecorderJSON";

		document.body.appendChild(node);

		s = document.getElementById("EventRecorderJSON");

		s.style.width 	= "50%";
		s.style.height  = "80%";
		s.style.left 	= "20px";
		s.style.top		= "20px";
		s.style.position = "absolute";
		s.style.backgroundColor = "white";
		s.style.zIndex = 1000;
		s.style.border = "thick solid black";

		node = document.createElement("div");
		node.id = "EventRecorderData";

		s = document.getElementById("EventRecorderJSON");
		s.appendChild(node);

		s = document.getElementById("EventRecorderData");

		s.style.width 	= "96%";
		s.style.height  = "80%";
		s.style.left 	= "2%";
		s.style.top		= "2%";
		s.style.position = "absolute";
		// s.style.backgroundColor = "orange";
		s.style.overflow = "hidden";

		node = document.createElement("div");
		node.id = "SubmitEvents";

		s = document.getElementById("EventRecorderJSON");
		s.appendChild(node);

		s = document.getElementById("SubmitEvents");

		s.style.width 	= "16%";
		s.style.height  = "20px";
		s.style.left 	= "2%";
		s.style.bottom		= "2%";
		s.style.position = "absolute";
		s.style.backgroundColor = "gainsboro";
		s.style.padding = "10px";
		s.style.border = "solid black";
		s.style.textAlign = "center";
		s.style.cursor	  = "pointer";
		s.innerHTML = "CLICK + CTRL + C";

		s.onclick = ms.copy_to_clipboard;

		node = document.createElement("div");
		node.id = "CloseEvents";
		s = document.getElementById("EventRecorderJSON");
		s.appendChild(node);

		s = document.getElementById("CloseEvents");

		s.style.width 	= "10%";
		s.style.height  = "20px";
		s.style.left 	= "22%";
		s.style.bottom		= "2%";
		s.style.position = "absolute";
		s.style.backgroundColor = "gainsboro";
		s.style.padding = "10px";
		s.style.border = "solid black";
		s.style.textAlign = "center";
		s.style.cursor	  = "pointer";
		s.innerHTML = "CLOSE";

		s.onclick = ms.close_clipboard;

	};

	 this.copy_to_clipboard = function copy_to_clipboard() {

      var node = document.getElementById("EventRecorderData" );

        if ( document.selection ) {
            var range = document.body.createTextRange();
            range.moveToElementText( node  );
            range.select();
        } else if ( window.getSelection ) {
            var range = document.createRange();
            range.selectNodeContents( node );
            window.getSelection().removeAllRanges();
            window.getSelection().addRange( range );
        }
 	};

	this.close_clipboard = function close_clipboard() {
		var node = document.getElementById("EventRecorderJSON" );
		node.style.display = "none";
	}

	this.json = function json() {
		var node, div, s, text = JSON.stringify(ms.events, null, 2);

		div = document.getElementById("EventRecorderData");
		if (! div) {
			ms.create_clipboard();
			div = document.getElementById("EventRecorderData");
		};

		div.innerHTML = text;

		node = document.getElementById("EventRecorderJSON" );
		node.style.display = "block";
	};

	this.store_keyup_event = function(event) {

		if (ms.recording) {
			var time = new Date().getTime() - ms.start;
			console.log(event.keyCode);

			ms.events.push({
				type:	"keyup",
				keyCode: event.keyCode,
				time:	time
			});
		}
	}

	this.key_ctrl = false;

	this.keydown = function keydwon(event) {
		switch(event.keyCode)
			{
			case 17:
			  console.log("KEYCONTROL_TRUE");
			  ms.key_ctrl = true;
			break;

			case 65: // A
			  if (ms.key_ctrl === true) {
			  	// everything is marked
			  };

			case 67: // C
			  if (ms.key_ctrl === true) {
			  	// selection copied
			  };

			case 88: // X
				// selection extracted
			break;

			break;
			}
	};

	this.keyup = function keyup(event) {
		switch(event.keyCode)
		 {
		 case 8: // backspace
		 	ms.store_keyup_event(event);
		 break;

		 case 17:
		 	console.log("KEYCONTROL_FALSE");
		  	ms.key_ctrl = false;
		 break;
		 }
	};

	this.keypress = function keypress(event) {

		if (ms.recording) {
			var time = new Date().getTime() - ms.start;
			console.log(event.charCode);

			var o = {
				type:	"keypress",
				keyCode: event,
				time:	time
			};

			ms.events.push(o);
		}
	};

	// gets all the elements of the page
	this.get_all_elements = function get_all_elements() {
		ms.all = document.getElementsByTagName("*");
	};

	this.init = function init() {
		window.er = this;

		ms.get_all_elements();
		ms.mouse = document.getElementById("mymouse");

		document.onmousemove	= ms.mousemove;
		document.onclick		= ms.mouseclick;
		document.onkeydown		= ms.keydown;
		document.onkeypress		= ms.keypress;
		document.onkeyup		= ms.keyup;

		if (params) {
			if (params.events) ms.events = params.events;
			if (params.autostart) ms.replay();
		}

	};

	ms.init();
}

module.exports = exports = EventRecorder;<|endoftext|>


<commit_before>/*
 * Copyright 2015 the Mimu Authors (Dan Bornstein et alia).
 * Licensed AS IS and WITHOUT WARRANTY under the Apache License,
 * Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>
 */

"use strict";

// Fraction of width which is to be used for labels.
var LABEL_WIDTH_AMT = 0.06;

// Fraction of the canvas width to take as the width of a thin line.
var THIN_LINE_AMT = 0.002;

// Fraction of the canvas width to take as the width of a thick line.
var THICK_LINE_AMT = 0.00275;

// Oscilloscope renderer. Expects values in the range `-1` to `1`. If timing
// labels are to be emitted, then the `sampleRate` and `buffer` need to be set.
//
// Note that, only half of a buffer will ever be rendered: The renderer
// attempts to find an upward zero-crossing somewhere in the first half of
// the buffer and renders from there. This helps make for a more stable
// display.
class Oscilloscope extends CanvasWidget {
    // Takes a DOM node to insert an oscilloscope into. The node should
    // be an empty container.
    constructor(node) {
        super(node, "soundDisplay");

        // Sample rate. Used for generating labels.
        this._sampleRate = 0;

        // Expected size of frequency array. Used for generating labels.
        this._frequencyBinCount = 0;

        // Buffer of samples to render.
        this._buffer = undefined;

        this.render();  // Draw an empty display.
    }

    get buffer() {
        return this._buffer;
    }

    set buffer(buffer) {
        this._buffer = buffer;
        this.renderBackground();
    }

    set sampleRate(value) {
        this._sampleRate = value;
        this.renderBackground();
    }

    // Render the background canvas.
    renderBackground() {
        var canvas = this.background;
        var style = this.cloneComputedStyle();
        var ctx = canvas.getContext("2d");
        var width = canvas.width;
        var height = canvas.height;
        var labelWidth = width * LABEL_WIDTH_AMT;
        var graphWidth = width - labelWidth;
        var graphHeight = height;
        var middleY = graphHeight / 2;
        var thin = Math.floor(width * THIN_LINE_AMT);

        var backgroundColor = Color.parse(style.backgroundColor);
        var foregroundColor = Color.parse(style.color);
        var mainColor = foregroundColor.mix(backgroundColor, 0.5).toCss();
        var darkColor = foregroundColor.mix(backgroundColor, 0.1).toCss();
        backgroundColor = backgroundColor.toCss();

        ctx.fillStyle = backgroundColor;
        ctx.fillRect(0, 0, width, height);

        ctx.fillStyle = mainColor;
        ctx.fillRect(labelWidth, 0, thin, graphHeight);

        var bufferSize = this._buffer && this._buffer.length;
        var sampleRate = this._sampleRate;
        if (!(   bufferSize && (bufferSize > 0)
              && sampleRate && (sampleRate > 0))) {
            return;
        }

        var tickLength = height / 50;

        style.fontSize = Math.round(width / 70) + "px";
        ctx.font = style.font;

        // Label the y axis (instantaneous amplitude).

        ctx.textBaseline = "middle";

        for (var v = -0.75; v < 1; v += 0.25) {
            var y = (-v + 1) / 2 * graphHeight;
            var text = v + " ";
            if ((v * 2) == Math.round(v * 2)) {
                var extraX = (v === 0) ? 0 : (thin * 2);
                ctx.fillStyle = (v === 0) ? mainColor : darkColor;
                ctx.fillRect(labelWidth + extraX, y, graphWidth - extraX, thin);
            }
            ctx.fillStyle = mainColor;
            ctx.fillRect(labelWidth - tickLength, y, tickLength, thin);

            var textWidth = ctx.measureText(text).width;
            ctx.fillText(text, labelWidth - tickLength - textWidth, y);
        }

        // Aim for up to ten time labels (with zero unlabeled). The `labelSecs`
        // calculation rounds the total buffer width in seconds down to a
        // power of ten; from there, we double the label count until we hit
        // more than ten.
        var renderSize = bufferSize / 2;
        var renderSecs = renderSize / sampleRate;
        var labelSecs = Math.pow(10, Math.floor(Math.log10(renderSecs)));

        for (;;) {
            var labelCount = Math.floor(renderSecs / labelSecs);
            if (labelCount > 10) {
                labelSecs *= 2;  // Because we went one too far in the loop.
                break;
            }
            labelSecs /= 2;
        }

        ctx.textBaseline = "top";

        for (var s = labelSecs; s < renderSecs; s += labelSecs) {
            var x = (graphWidth * ((s * sampleRate) / renderSize)) + labelWidth;
            var text = (Math.round(s * 100000) / 100) + "\u200Ams";

            ctx.fillStyle = darkColor;
            ctx.fillRect(x, 0, thin, height);

            ctx.fillStyle = mainColor;
            ctx.fillRect(x, middleY - (tickLength / 2), thin, tickLength);

            var textWidth = ctx.measureText(text).width;
            if ((x + (textWidth / 2)) < width) {
                ctx.fillText(text,
                    x - (textWidth / 2), middleY + (tickLength / 2));
            }
        }
    }

    // Render the oscilloscope's sample buffer into the foreground canvas.
    render() {
        var buf = this._buffer;
        var canvas = this.foreground;
        var ctx = canvas.getContext("2d");
        var width = canvas.width;
        var height = canvas.height;
        var labelWidth = width * LABEL_WIDTH_AMT;
        var graphWidth = width - labelWidth;
        var graphHeight = height;
        var thick = Math.floor(width * THICK_LINE_AMT);

        ctx.clearRect(0, 0, width, height);

        if (!buf) {
            return;
        }

        ctx.strokeStyle = this.getComputedStyle().color;
        ctx.lineWidth = thick;
        ctx.lineCap = "round";
        ctx.lineJoin = "round";
        ctx.beginPath();

        // Find an upward zero-crossing, or fall back if it doesn't seem
        // fruitful.
        var idxMax = buf.length / 2;
        var base = 0;
        while ((base < idxMax) && (buf[base] >= 0)) { base++; }
        while ((base < idxMax) && (buf[base] < 0)) { base++; }
        if (base >= idxMax) {
            base = 0; // No suitable zero-crossing.
        }

        for (var idx = 0; idx < idxMax; idx++) {
            var samp = buf[base + idx];
            var x = labelWidth + (idx / idxMax * graphWidth);
            var y = (-samp + 1) / 2 * graphHeight;
            if (idx === 0) {
                ctx.moveTo(x, y);
            } else {
                ctx.lineTo(x, y);
            }
        }
        ctx.stroke();
    }
}
<commit_msg>Remove dead code.<commit_after>/*
 * Copyright 2015 the Mimu Authors (Dan Bornstein et alia).
 * Licensed AS IS and WITHOUT WARRANTY under the Apache License,
 * Version 2.0. Details: <http://www.apache.org/licenses/LICENSE-2.0>
 */

"use strict";

// Fraction of width which is to be used for labels.
var LABEL_WIDTH_AMT = 0.06;

// Fraction of the canvas width to take as the width of a thin line.
var THIN_LINE_AMT = 0.002;

// Fraction of the canvas width to take as the width of a thick line.
var THICK_LINE_AMT = 0.00275;

// Oscilloscope renderer. Expects values in the range `-1` to `1`. If timing
// labels are to be emitted, then the `sampleRate` and `buffer` need to be set.
//
// Note that, only half of a buffer will ever be rendered: The renderer
// attempts to find an upward zero-crossing somewhere in the first half of
// the buffer and renders from there. This helps make for a more stable
// display.
class Oscilloscope extends CanvasWidget {
    // Takes a DOM node to insert an oscilloscope into. The node should
    // be an empty container.
    constructor(node) {
        super(node, "soundDisplay");

        // Sample rate. Used for generating labels.
        this._sampleRate = 0;

        // Buffer of samples to render.
        this._buffer = undefined;

        this.render();  // Draw an empty display.
    }

    get buffer() {
        return this._buffer;
    }

    set buffer(buffer) {
        this._buffer = buffer;
        this.renderBackground();
    }

    set sampleRate(value) {
        this._sampleRate = value;
        this.renderBackground();
    }

    // Render the background canvas.
    renderBackground() {
        var canvas = this.background;
        var style = this.cloneComputedStyle();
        var ctx = canvas.getContext("2d");
        var width = canvas.width;
        var height = canvas.height;
        var labelWidth = width * LABEL_WIDTH_AMT;
        var graphWidth = width - labelWidth;
        var graphHeight = height;
        var middleY = graphHeight / 2;
        var thin = Math.floor(width * THIN_LINE_AMT);

        var backgroundColor = Color.parse(style.backgroundColor);
        var foregroundColor = Color.parse(style.color);
        var mainColor = foregroundColor.mix(backgroundColor, 0.5).toCss();
        var darkColor = foregroundColor.mix(backgroundColor, 0.1).toCss();
        backgroundColor = backgroundColor.toCss();

        ctx.fillStyle = backgroundColor;
        ctx.fillRect(0, 0, width, height);

        ctx.fillStyle = mainColor;
        ctx.fillRect(labelWidth, 0, thin, graphHeight);

        var bufferSize = this._buffer && this._buffer.length;
        var sampleRate = this._sampleRate;
        if (!(   bufferSize && (bufferSize > 0)
              && sampleRate && (sampleRate > 0))) {
            return;
        }

        var tickLength = height / 50;

        style.fontSize = Math.round(width / 70) + "px";
        ctx.font = style.font;

        // Label the y axis (instantaneous amplitude).

        ctx.textBaseline = "middle";

        for (var v = -0.75; v < 1; v += 0.25) {
            var y = (-v + 1) / 2 * graphHeight;
            var text = v + " ";
            if ((v * 2) == Math.round(v * 2)) {
                var extraX = (v === 0) ? 0 : (thin * 2);
                ctx.fillStyle = (v === 0) ? mainColor : darkColor;
                ctx.fillRect(labelWidth + extraX, y, graphWidth - extraX, thin);
            }
            ctx.fillStyle = mainColor;
            ctx.fillRect(labelWidth - tickLength, y, tickLength, thin);

            var textWidth = ctx.measureText(text).width;
            ctx.fillText(text, labelWidth - tickLength - textWidth, y);
        }

        // Aim for up to ten time labels (with zero unlabeled). The `labelSecs`
        // calculation rounds the total buffer width in seconds down to a
        // power of ten; from there, we double the label count until we hit
        // more than ten.
        var renderSize = bufferSize / 2;
        var renderSecs = renderSize / sampleRate;
        var labelSecs = Math.pow(10, Math.floor(Math.log10(renderSecs)));

        for (;;) {
            var labelCount = Math.floor(renderSecs / labelSecs);
            if (labelCount > 10) {
                labelSecs *= 2;  // Because we went one too far in the loop.
                break;
            }
            labelSecs /= 2;
        }

        ctx.textBaseline = "top";

        for (var s = labelSecs; s < renderSecs; s += labelSecs) {
            var x = (graphWidth * ((s * sampleRate) / renderSize)) + labelWidth;
            var text = (Math.round(s * 100000) / 100) + "\u200Ams";

            ctx.fillStyle = darkColor;
            ctx.fillRect(x, 0, thin, height);

            ctx.fillStyle = mainColor;
            ctx.fillRect(x, middleY - (tickLength / 2), thin, tickLength);

            var textWidth = ctx.measureText(text).width;
            if ((x + (textWidth / 2)) < width) {
                ctx.fillText(text,
                    x - (textWidth / 2), middleY + (tickLength / 2));
            }
        }
    }

    // Render the oscilloscope's sample buffer into the foreground canvas.
    render() {
        var buf = this._buffer;
        var canvas = this.foreground;
        var ctx = canvas.getContext("2d");
        var width = canvas.width;
        var height = canvas.height;
        var labelWidth = width * LABEL_WIDTH_AMT;
        var graphWidth = width - labelWidth;
        var graphHeight = height;
        var thick = Math.floor(width * THICK_LINE_AMT);

        ctx.clearRect(0, 0, width, height);

        if (!buf) {
            return;
        }

        ctx.strokeStyle = this.getComputedStyle().color;
        ctx.lineWidth = thick;
        ctx.lineCap = "round";
        ctx.lineJoin = "round";
        ctx.beginPath();

        // Find an upward zero-crossing, or fall back if it doesn't seem
        // fruitful.
        var idxMax = buf.length / 2;
        var base = 0;
        while ((base < idxMax) && (buf[base] >= 0)) { base++; }
        while ((base < idxMax) && (buf[base] < 0)) { base++; }
        if (base >= idxMax) {
            base = 0; // No suitable zero-crossing.
        }

        for (var idx = 0; idx < idxMax; idx++) {
            var samp = buf[base + idx];
            var x = labelWidth + (idx / idxMax * graphWidth);
            var y = (-samp + 1) / 2 * graphHeight;
            if (idx === 0) {
                ctx.moveTo(x, y);
            } else {
                ctx.lineTo(x, y);
            }
        }
        ctx.stroke();
    }
}
<|endoftext|>


<commit_before>/**
* Module that contains the configurable settings panel implementation
* Depends on core.js, utils.js, trasnlations.js
*/
/*global JSAV, jQuery */
(function($) {
  "use strict";
  if (typeof JSAV === "undefined") { return; }
  var speedChoices = [5000, 3000, 1500, 1000, 500, 400, 300, 200, 100, 50];
  var getSpeedChoice = function(speedVal) {
    var curval = speedChoices.length - 1;
    while (curval && speedChoices[curval] < speedVal) {
      curval--;
    }
    return curval + 1;
  };
  var speedSetting = function(settings) {
    return function() {
      var translate;
      if (settings.jsav) {
        translate = settings.jsav._translate;
      } else {
        translate = JSAV.utils.getInterpreter(JSAV._translations, "en");
      }
      var curSpeed = JSAV.ext.SPEED;
      var rangeSupported = !!$.support.inputTypeRange;
      // get the closest speed choice to the current speed
      var curval = getSpeedChoice(curSpeed);
      // add explanation if using range slider, help text otherwise
      var $elem = $('
' + translate("animationSpeed") + (rangeSupported?' ' + translate("(slowFast)"):'') +
          ': <input type="range" min="1" max="10" step="1" size="30" value="' + curval + '"/> ' +
          (rangeSupported?'':'<button>' + translate("save") + '</button>
' + translate("valueBetween1And10")) +
          '
');
      // event handler function for storing the speed
      var speedChangeHandler = function() {
        var speed = parseInt($(this).val(), 10);
        if (isNaN(speed) || speed < 1 || speed > 10) { return; }
        speed = speedChoices[speed - 1]; // speed in milliseconds
        curSpeed = speed;
        JSAV.ext.SPEED = speed;
        //trigger speed change event to update all AVs on the page
        $(document).trigger("jsav-speed-change", speed);
        if (localStorage) {
          localStorage.setItem("jsav-speed", speed);
        }
      };
      // set the value and add a change event listener
      var $inputElem = $elem.find("input").val(curval);
      if (rangeSupported) {
        $inputElem.change(speedChangeHandler);
      } else {
        $elem.find("button").click(function() {
          speedChangeHandler.call($inputElem);
          var savedElem = $("" + translate("saved") + "");
          setTimeout(function() { savedElem.fadeOut(); }, 1000);
          $(this).after(savedElem);
        });
      }

      // return the element
      return $elem;
    };
  };

  /* Creates an input component to be used in the settings panel. varname should be unique
    within the document. Options can specify the label of the component, in which case
    a label element is created. Option value specifies the default value of the element.
    Every other option will be set as an attribute of the input element. */
  var createInputComponent = function(varname, options) {
    var label,
        opts = $.extend({"type": "text"}, options),
        input = $('<input id="jsavsettings-' + varname + '" type="' +
          opts.type + '"/>');
    if ('label' in opts) {
      label = $('' + opts.label + "");
    }
    if ('value' in opts) {
      input.val(opts.value);
    }
    for (var attr in opts) {
      if (['label', 'value', 'type'].indexOf(attr) === -1) {
        input.attr(attr, opts[attr]);
      }
    }
    return $('
').append(label).append(input);
  };

  /* Creates a select component to be used in the settings panel. varname should be unique
    within the document. Options can specify the label of the component, in which case
    a label element is created. Option value specifies the default value of the element.
    Option options should be a map where every key-value pair will make for an option element
    in the form. Every other option will be set as an attribute of the input element. */
  var createSelectComponent = function(varname, options) {
    var label,
        select = $('<select id="jsavsettings-' + varname + '" />');
    if ('label' in options) {
      label = $('' + options.label + "");
    }
    var opts = options.options;
    for (var key in opts) {
      if (opts.hasOwnProperty(key)) {
        select.append('<option value="' + key + '">' + opts[key] + '</option>');
      }
    }
    if ('value' in options) {
      select.val(options.value);
    }
    var toCheck = ['label', 'value', 'options', 'type'];
    for (var attr in options) {
      if ($.inArray(attr, toCheck) === -1) {
        select.attr(attr, options[attr]);
      }
    }
    return $('
').append(label).append(select);
  };

  var Settings = function(elem) {
      this.components = [];
      this.add(speedSetting(this));

      var that = this;
      if (elem) {
        $(elem).click(function(e) {
          e.preventDefault();
          that.show();
        });
      }
    },
    sproto = Settings.prototype;
  sproto.show = function() {
    var $cont = $("

");
    for (var i = 0; i < this.components.length; i++) {
      $cont.append(this.components[i]);
    }

    var title = this.jsav? this.jsav._translate("settings"): "Settings";
    this.dialog = JSAV.utils.dialog($cont, {title: title});
  };
  sproto.close = function() {
    if (this.dialog) {
      this.dialog.close();
    }
  };
  sproto.add = function(create, options) {
    if ($.isFunction(create)) {
      // create is a function that returns a DOM Element or jQuery object or HTML string
      this.components.push(create);
    } else {
      // create is a name of a variable
      if (!('type' in options)) {
        return;
      }
      var elem, func;
      if (options.type === 'select') {
        func = createSelectComponent;
      } else {
        func = createInputComponent;
      }
      elem = func(create, options);
      this.components.push(elem);
      return elem.find("input, select");
    }
  };
  JSAV.utils.Settings = Settings;
  JSAV.init(function() {
    if (this.options.settings) {
      this.settings = this.options.settings;
    } else {
      this.settings = new Settings($(this.container).find(".jsavsettings").show());
    }
    // set a reference to the JSAV instance for translation support
    this.settings.jsav = this;
  });
}(jQuery));<commit_msg>The default language for the settings dialog is set from window.JSAV_OPTIONS (if possible) when Settings is not linked to a JSAV instance.<commit_after>/**
* Module that contains the configurable settings panel implementation
* Depends on core.js, utils.js, trasnlations.js
*/
/*global JSAV, jQuery */
(function($) {
  "use strict";
  if (typeof JSAV === "undefined") { return; }
  var speedChoices = [5000, 3000, 1500, 1000, 500, 400, 300, 200, 100, 50];
  var getSpeedChoice = function(speedVal) {
    var curval = speedChoices.length - 1;
    while (curval && speedChoices[curval] < speedVal) {
      curval--;
    }
    return curval + 1;
  };
  var speedSetting = function(settings) {
    return function() {
      var translate;
      if (settings.jsav) {
        translate = settings.jsav._translate;
      } else {
        var lang = window.JSAV_OPTIONS ? window.JSAV_OPTIONS.lang || "en" : "en";
        translate = JSAV.utils.getInterpreter(JSAV._translations, lang);
      }
      var curSpeed = JSAV.ext.SPEED;
      var rangeSupported = !!$.support.inputTypeRange;
      // get the closest speed choice to the current speed
      var curval = getSpeedChoice(curSpeed);
      // add explanation if using range slider, help text otherwise
      var $elem = $('
' + translate("animationSpeed") + (rangeSupported?' ' + translate("(slowFast)"):'') +
          ': <input type="range" min="1" max="10" step="1" size="30" value="' + curval + '"/> ' +
          (rangeSupported?'':'<button>' + translate("save") + '</button>
' + translate("valueBetween1And10")) +
          '
');
      // event handler function for storing the speed
      var speedChangeHandler = function() {
        var speed = parseInt($(this).val(), 10);
        if (isNaN(speed) || speed < 1 || speed > 10) { return; }
        speed = speedChoices[speed - 1]; // speed in milliseconds
        curSpeed = speed;
        JSAV.ext.SPEED = speed;
        //trigger speed change event to update all AVs on the page
        $(document).trigger("jsav-speed-change", speed);
        if (localStorage) {
          localStorage.setItem("jsav-speed", speed);
        }
      };
      // set the value and add a change event listener
      var $inputElem = $elem.find("input").val(curval);
      if (rangeSupported) {
        $inputElem.change(speedChangeHandler);
      } else {
        $elem.find("button").click(function() {
          speedChangeHandler.call($inputElem);
          var savedElem = $("" + translate("saved") + "");
          setTimeout(function() { savedElem.fadeOut(); }, 1000);
          $(this).after(savedElem);
        });
      }

      // return the element
      return $elem;
    };
  };

  /* Creates an input component to be used in the settings panel. varname should be unique
    within the document. Options can specify the label of the component, in which case
    a label element is created. Option value specifies the default value of the element.
    Every other option will be set as an attribute of the input element. */
  var createInputComponent = function(varname, options) {
    var label,
        opts = $.extend({"type": "text"}, options),
        input = $('<input id="jsavsettings-' + varname + '" type="' +
          opts.type + '"/>');
    if ('label' in opts) {
      label = $('' + opts.label + "");
    }
    if ('value' in opts) {
      input.val(opts.value);
    }
    for (var attr in opts) {
      if (['label', 'value', 'type'].indexOf(attr) === -1) {
        input.attr(attr, opts[attr]);
      }
    }
    return $('
').append(label).append(input);
  };

  /* Creates a select component to be used in the settings panel. varname should be unique
    within the document. Options can specify the label of the component, in which case
    a label element is created. Option value specifies the default value of the element.
    Option options should be a map where every key-value pair will make for an option element
    in the form. Every other option will be set as an attribute of the input element. */
  var createSelectComponent = function(varname, options) {
    var label,
        select = $('<select id="jsavsettings-' + varname + '" />');
    if ('label' in options) {
      label = $('' + options.label + "");
    }
    var opts = options.options;
    for (var key in opts) {
      if (opts.hasOwnProperty(key)) {
        select.append('<option value="' + key + '">' + opts[key] + '</option>');
      }
    }
    if ('value' in options) {
      select.val(options.value);
    }
    var toCheck = ['label', 'value', 'options', 'type'];
    for (var attr in options) {
      if ($.inArray(attr, toCheck) === -1) {
        select.attr(attr, options[attr]);
      }
    }
    return $('
').append(label).append(select);
  };

  var Settings = function(elem) {
      this.components = [];
      this.add(speedSetting(this));

      var that = this;
      if (elem) {
        $(elem).click(function(e) {
          e.preventDefault();
          that.show();
        });
      }
    },
    sproto = Settings.prototype;
  sproto.show = function() {
    var $cont = $("

");
    for (var i = 0; i < this.components.length; i++) {
      $cont.append(this.components[i]);
    }

    var translate;
    if (this.jsav) {
      translate = this.jsav._translate;
    } else {
      var lang = window.JSAV_OPTIONS ? window.JSAV_OPTIONS.lang || "en" : "en";
      translate = JSAV.utils.getInterpreter(JSAV._translations, lang);
    }
    var title = translate("settings");
    this.dialog = JSAV.utils.dialog($cont, {title: title});
  };
  sproto.close = function() {
    if (this.dialog) {
      this.dialog.close();
    }
  };
  sproto.add = function(create, options) {
    if ($.isFunction(create)) {
      // create is a function that returns a DOM Element or jQuery object or HTML string
      this.components.push(create);
    } else {
      // create is a name of a variable
      if (!('type' in options)) {
        return;
      }
      var elem, func;
      if (options.type === 'select') {
        func = createSelectComponent;
      } else {
        func = createInputComponent;
      }
      elem = func(create, options);
      this.components.push(elem);
      return elem.find("input, select");
    }
  };
  JSAV.utils.Settings = Settings;
  JSAV.init(function() {
    if (this.options.settings) {
      this.settings = this.options.settings;
    } else {
      this.settings = new Settings($(this.container).find(".jsavsettings").show());
    }
    // set a reference to the JSAV instance for translation support
    this.settings.jsav = this;
  });
}(jQuery));<|endoftext|>


<commit_before>/**
* Generates button objects
* @param {array} collection coll of strings from which to create text buttons
* @return {array} array of messenger button objects
*/
function generateButtonObject(collection) {
  return collection.map((element) => ({
    content_type: 'text',
    title: element,
    payload: element,
  }));
}

/**
* Splits a locale into language and region but only returns language
* @param {string} locale a string of language_region for example: "en_GB"
* @return {string} the language part of a locale
*/
function extractLanguageFromLocale(locale) {
  const [language] = locale.split('_');
  return language;
}

// TO DO: generalise the generateButtonobjec
/**
 * Generate button template for quick Yes/No answers
 * @param {string} text the question
 * @param {string} yesTitle yes text (needed for translations)
 * @param {string} noTitle no text (needed for translations)
 * @param {string} yesPayload payload for a yes answer
 * @param {string} noPayload payload for a yes answer
 * @return {object} a messenger button template object
*/
function generateYesNoButtonTemplate(text,
                                     yesTitle,
                                     noTitle,
                                     yesPayload,
                                     noPayload) {
  return {
    attachment: {
      type: 'template',
      payload: {
        template_type: 'button',
        text,
        buttons: [{
          type: 'postback',
          title: yesTitle,
          payload: yesPayload,
        }, {
          type: 'postback',
          title: noTitle,
          payload: noPayload,
        }],
      },
    },
  };
}

/**
* Generate a messenger quick reply
* @param {string} text the question text
* @param {array} replyArray an array of up to 11 objects that is, the answers
* @return {object} a quick reply object
*/
function generateQuickReply(text, replyArray) {
  return {
    text,
    quick_replies: replyArray,
  };
}

/**
* Handle breaks in the currently running conversation
* @param {object} response the response object
* @param {object} conversation the conversation object
* @param {function} callback default function to call
* @return {function} the function to direct the conversation
*/
function conversationSwitch(response, conversation, callback) {
  if (['restart',
       'hello',
       'hi',
       'start',
       'switch_en',
       'switch_pt',
       'switch_in'].includes(response.text)) {
    conversation.stop('interrupted');
    return conversation.next();
  } else {
    return callback;
  }
}

/**
* Go to the next conversation.
* @param {object} response the response object
* @param {object} conversation the conversation object
* @return {function} calling converationSwitch with conversation.next()
*/
function nextConversation(response, conversation) {
  return conversationSwitch(response, conversation, conversation.next());
}

/**
* Which conversation thread to go to
* @param {string} threadName the name of the thread to go to next
* @return {function} result of calling conversationSwitch with the threadName
*/
function goto(threadName) {
  return (response, conversation) => {
    conversationSwitch(response,
                         conversation,
                         conversation.gotoThread(threadName));
  };
}

/**
* Send a message to a facebookId, they have to have opted in
* @param {object} bot a bot returned from calling `spawn`
* @param {string} facebookId messenger id
* @param {function} messageCallback callback to pass to startConversation
*/
function sendMessage(bot, facebookId, messageCallback) {
  bot.startConversation({user: facebookId, channel: facebookId},
                        messageCallback);
}

module.exports = {
  extractLanguageFromLocale,
  generateButtonObject,
  nextConversation,
  goto,
  generateYesNoButtonTemplate,
  generateQuickReply,
  sendMessage,
};
<commit_msg>Don't call conversation.next on interrupt<commit_after>/**
* Generates button objects
* @param {array} collection coll of strings from which to create text buttons
* @return {array} array of messenger button objects
*/
function generateButtonObject(collection) {
  return collection.map((element) => ({
    content_type: 'text',
    title: element,
    payload: element,
  }));
}

/**
* Splits a locale into language and region but only returns language
* @param {string} locale a string of language_region for example: "en_GB"
* @return {string} the language part of a locale
*/
function extractLanguageFromLocale(locale) {
  const [language] = locale.split('_');
  return language;
}

// TO DO: generalise the generateButtonobjec
/**
 * Generate button template for quick Yes/No answers
 * @param {string} text the question
 * @param {string} yesTitle yes text (needed for translations)
 * @param {string} noTitle no text (needed for translations)
 * @param {string} yesPayload payload for a yes answer
 * @param {string} noPayload payload for a yes answer
 * @return {object} a messenger button template object
*/
function generateYesNoButtonTemplate(text,
                                     yesTitle,
                                     noTitle,
                                     yesPayload,
                                     noPayload) {
  return {
    attachment: {
      type: 'template',
      payload: {
        template_type: 'button',
        text,
        buttons: [{
          type: 'postback',
          title: yesTitle,
          payload: yesPayload,
        }, {
          type: 'postback',
          title: noTitle,
          payload: noPayload,
        }],
      },
    },
  };
}

/**
* Generate a messenger quick reply
* @param {string} text the question text
* @param {array} replyArray an array of up to 11 objects that is, the answers
* @return {object} a quick reply object
*/
function generateQuickReply(text, replyArray) {
  return {
    text,
    quick_replies: replyArray,
  };
}

/**
* Handle breaks in the currently running conversation
* @param {object} response the response object
* @param {object} conversation the conversation object
* @param {function} callback default function to call
* @return {function} the function to direct the conversation
*/
function conversationSwitch(response, conversation, callback) {
  if (['restart',
       'hello',
       'hi',
       'start',
       'switch_en',
       'switch_pt',
       'switch_in'].includes(response.text)) {
    conversation.stop('interrupted');
  } else {
    return callback;
  }
}

/**
* Go to the next conversation.
* @param {object} response the response object
* @param {object} conversation the conversation object
* @return {function} calling converationSwitch with conversation.next()
*/
function nextConversation(response, conversation) {
  return conversationSwitch(response, conversation, conversation.next());
}

/**
* Which conversation thread to go to
* @param {string} threadName the name of the thread to go to next
* @return {function} result of calling conversationSwitch with the threadName
*/
function goto(threadName) {
  return (response, conversation) => {
    conversationSwitch(response,
                         conversation,
                         conversation.gotoThread(threadName));
  };
}

/**
* Send a message to a facebookId, they have to have opted in
* @param {object} bot a bot returned from calling `spawn`
* @param {string} facebookId messenger id
* @param {function} messageCallback callback to pass to startConversation
*/
function sendMessage(bot, facebookId, messageCallback) {
  bot.startConversation({user: facebookId, channel: facebookId},
                        messageCallback);
}

module.exports = {
  extractLanguageFromLocale,
  generateButtonObject,
  nextConversation,
  goto,
  generateYesNoButtonTemplate,
  generateQuickReply,
  sendMessage,
};
<|endoftext|>


<commit_before>/*
 * lib/worker/locator.js: interface for locating Manta objects.
 */

var mod_assert = require('assert');
var mod_events = require('events');
var mod_fs = require('fs');
var mod_util = require('util');

var mod_extsprintf = require('extsprintf');
var mod_jsprim = require('jsprim');
var mod_libmanta = require('libmanta');
var mod_uuid = require('node-uuid');
var mod_vasync = require('vasync');

var mod_mautil = require('../util');

var sprintf = mod_extsprintf.sprintf;
var CVError = mod_mautil.CVError;

/* jsl:import ../errors.js */
require('../errors');

/* Public interface */
exports.createLocator = createLocator;

/*
 * Locators are objects with a single method for locating a set of keys in
 * Manta:
 *
 *	locate(keys, callback)
 *
 * "keys" is an array of Manta object keys.  "callback" is invoked later as
 * callback(err, results), where "results" is an object mapping the input keys
 * to an array of values describing one copy of the object on the storage tier:
 *
 *      objectid		Unique identifier for this object.  This value
 *      			is the same for all copies of this object.
 *
 *	manta_compute_id	Unique identifier for the physical server on
 *				which this copy resides.  This is not
 *				necessarily a hostname, but a static identifier
 *				for the server.
 *
 *	manta_storage_id	Unique identifier for the logical zone on which
 *				this copy resides.
 *
 *	zonename		Unique identifier for the physical zone on
 *				which this copy resides.
 *
 * We currently only implement a Manta-based locator which is used for standard
 * deployments.  This locator takes a ring of Moray shards as input and uses
 * them to locate objects.  For future extension, createLocator is used to
 * create an appropriate locator for a given configuration, which is a subset of
 * the standard job worker configuration with the following properties:
 *
 *    locator	The default value is "manta".  See above.
 *
 *    moray	Moray configuration (see generic worker configuration)
 *
 *	moray.index: electric moray configuration
 *
 * The locator will use the electric moray instance specified by
 * moray.index.{host,port} to find objects.
 */
function createLocator(conf, args)
{
	if (!conf['locator'] || conf['locator'] == 'manta') {
		mod_assert.ok(conf['moray']);
		mod_assert.ok(conf['moray']['index']);
		return (new MantaLocator(conf['moray']['index'],
		    args['log'], args['storage_map']));
	}

	throw (new Error('unsupported value for property "locator": ' +
	    conf['locator']));
}

function MantaLocator(indexconf, log, storage_map)
{
	mod_assert.ok(indexconf);

	var conf = mod_jsprim.deepCopy(indexconf);
	conf['log'] = log;

	this.ml_log = log;
	this.ml_ring = mod_libmanta.createMorayClient(conf);
	this.ml_ops = {};
	this.ml_storage_map = storage_map;
	mod_events.EventEmitter();

	var loc = this;
	var onerr = function (err) {
		loc.ml_log.warn(err, 'locator not yet ready');
	};

	this.ml_ring.once('connect', function () {
		loc.ml_log.info('locator ready');
		loc.ml_ring.removeListener('error', onerr);
		loc.emit('ready');
	});

	this.ml_ring.on('error', onerr);
}

mod_util.inherits(MantaLocator, mod_events.EventEmitter);

/*
 * Guarantees that the shark data always contains:
 *    objectid
 *    zonename
 *    mantaComputeId
 *    mantaStorageId
 */
function populateSharkData(locator, result, shark) {
	var rec = locator.ml_storage_map[shark['manta_storage_id']];
	return ({
		'objectid': result['objectId'],
		'mantaStorageId': rec['manta_storage_id'],
		'mantaComputeId': rec['manta_compute_id'],
		'zonename': rec['zone_uuid']
	});
}

MantaLocator.prototype.locate = function (keys, callback)
{
	/*
	 * For now this operation uses mod_vasync to parallelize one request for
	 * each key.  In the future, this could be a single batch operation.
	 */
	var loc = this;
	var uuid = mod_uuid.v4();
	var ring = this.ml_ring;
	var ops = this.ml_ops;

	this.ml_ops[uuid] = mod_vasync.forEachParallel({
	    'inputs': keys,
	    'func': function (key, subcallback) {
		/* XXX requestid could be jobid, plus phase, plus index? */
		ring.getMetadata({ 'requestId': uuid, 'key': key },
		    function (err, result) {
			if ((err && err['name'] == 'ObjectNotFoundError')) {
				subcallback(new CVError(EM_RESOURCENOTFOUND,
				    'no such object'));
				return;
			}

			if (err) {
				subcallback(new CVError(EM_INTERNAL, err));
				return;
			}

			if (result['type'] != 'object') {
				subcallback(new CVError(EM_INVALIDARGUMENT,
				    'objects of type "%s" are not supported',
				    result['type']));
				return;
			}

			if (!result['sharks'] ||
			    !Array.isArray(result['sharks'])) {
				subcallback(new CVError(EM_INTERNAL,
				    'missing or invalid "sharks" property'));
				return;
			}

			if (result['sharks'].length === 0 &&
			    result['contentLength'] !== 0) {
				subcallback(new CVError(EM_INTERNAL,
				    'no sharks found for non-empty object'));
				return;
			}

			subcallback(null, result['sharks'].map(function (s) {
				return (populateSharkData(loc, result, s));
			}));
		    });
	    }
	}, function (err, result) {
		delete (ops[uuid]);

		var rv = {};

		keys.forEach(function (key, i) {
			if (result['operations'][i]['status'] != 'ok') {
				rv[key] = { 'error':
				    result['operations'][i]['err'] };
				return;
			}

			rv[key] = result['operations'][i]['result'];
		});

		callback(err, rv);
	});
};

MantaLocator.prototype.cleanup = function ()
{
	this.ml_ring.close(function () {});
};
<commit_msg>MANTA-1385 marlin: worker needs to blow up on libmanta init error<commit_after>/*
 * lib/worker/locator.js: interface for locating Manta objects.
 */

var mod_assert = require('assert');
var mod_events = require('events');
var mod_fs = require('fs');
var mod_util = require('util');

var mod_extsprintf = require('extsprintf');
var mod_jsprim = require('jsprim');
var mod_libmanta = require('libmanta');
var mod_uuid = require('node-uuid');
var mod_vasync = require('vasync');

var mod_mautil = require('../util');

var sprintf = mod_extsprintf.sprintf;
var CVError = mod_mautil.CVError;

/* jsl:import ../errors.js */
require('../errors');

/* Public interface */
exports.createLocator = createLocator;

/*
 * Locators are objects with a single method for locating a set of keys in
 * Manta:
 *
 *	locate(keys, callback)
 *
 * "keys" is an array of Manta object keys.  "callback" is invoked later as
 * callback(err, results), where "results" is an object mapping the input keys
 * to an array of values describing one copy of the object on the storage tier:
 *
 *      objectid		Unique identifier for this object.  This value
 *      			is the same for all copies of this object.
 *
 *	manta_compute_id	Unique identifier for the physical server on
 *				which this copy resides.  This is not
 *				necessarily a hostname, but a static identifier
 *				for the server.
 *
 *	manta_storage_id	Unique identifier for the logical zone on which
 *				this copy resides.
 *
 *	zonename		Unique identifier for the physical zone on
 *				which this copy resides.
 *
 * We currently only implement a Manta-based locator which is used for standard
 * deployments.  This locator takes a ring of Moray shards as input and uses
 * them to locate objects.  For future extension, createLocator is used to
 * create an appropriate locator for a given configuration, which is a subset of
 * the standard job worker configuration with the following properties:
 *
 *    locator	The default value is "manta".  See above.
 *
 *    moray	Moray configuration (see generic worker configuration)
 *
 *	moray.index: electric moray configuration
 *
 * The locator will use the electric moray instance specified by
 * moray.index.{host,port} to find objects.
 */
function createLocator(conf, args)
{
	if (!conf['locator'] || conf['locator'] == 'manta') {
		mod_assert.ok(conf['moray']);
		mod_assert.ok(conf['moray']['index']);
		return (new MantaLocator(conf['moray']['index'],
		    args['log'], args['storage_map']));
	}

	throw (new Error('unsupported value for property "locator": ' +
	    conf['locator']));
}

function MantaLocator(indexconf, log, storage_map)
{
	mod_assert.ok(indexconf);

	var conf = mod_jsprim.deepCopy(indexconf);
	conf['log'] = log;

	this.ml_log = log;
	this.ml_ring = mod_libmanta.createMorayClient(conf);
	this.ml_ops = {};
	this.ml_storage_map = storage_map;
	mod_events.EventEmitter();

	var loc = this;

	/*
	 * We don't handle the error event.  libmanta is responsible for
	 * reconnecting, and most other errors are per-request.  A "ring" error
	 * represents some more serious issue that we cannot reasonably handle.
	 */
	this.ml_ring.once('connect', function () {
		loc.ml_log.info('locator ready');
		loc.emit('ready');
	});
}

mod_util.inherits(MantaLocator, mod_events.EventEmitter);

/*
 * Guarantees that the shark data always contains:
 *    objectid
 *    zonename
 *    mantaComputeId
 *    mantaStorageId
 */
function populateSharkData(locator, result, shark) {
	var rec = locator.ml_storage_map[shark['manta_storage_id']];
	return ({
		'objectid': result['objectId'],
		'mantaStorageId': rec['manta_storage_id'],
		'mantaComputeId': rec['manta_compute_id'],
		'zonename': rec['zone_uuid']
	});
}

MantaLocator.prototype.locate = function (keys, callback)
{
	/*
	 * For now this operation uses mod_vasync to parallelize one request for
	 * each key.  In the future, this could be a single batch operation.
	 */
	var loc = this;
	var uuid = mod_uuid.v4();
	var ring = this.ml_ring;
	var ops = this.ml_ops;

	this.ml_ops[uuid] = mod_vasync.forEachParallel({
	    'inputs': keys,
	    'func': function (key, subcallback) {
		/* XXX requestid could be jobid, plus phase, plus index? */
		ring.getMetadata({ 'requestId': uuid, 'key': key },
		    function (err, result) {
			if ((err && err['name'] == 'ObjectNotFoundError')) {
				subcallback(new CVError(EM_RESOURCENOTFOUND,
				    'no such object'));
				return;
			}

			if (err) {
				subcallback(new CVError(EM_INTERNAL, err,
				    'error locating object'));
				return;
			}

			if (result['type'] != 'object') {
				subcallback(new CVError(EM_INVALIDARGUMENT,
				    'objects of type "%s" are not supported',
				    result['type']));
				return;
			}

			if (!result['sharks'] ||
			    !Array.isArray(result['sharks'])) {
				subcallback(new CVError(EM_INTERNAL,
				    'missing or invalid "sharks" property'));
				return;
			}

			if (result['sharks'].length === 0 &&
			    result['contentLength'] !== 0) {
				subcallback(new CVError(EM_INTERNAL,
				    'no sharks found for non-empty object'));
				return;
			}

			subcallback(null, result['sharks'].map(function (s) {
				return (populateSharkData(loc, result, s));
			}));
		    });
	    }
	}, function (err, result) {
		delete (ops[uuid]);

		var rv = {};

		keys.forEach(function (key, i) {
			if (result['operations'][i]['status'] != 'ok') {
				rv[key] = { 'error':
				    result['operations'][i]['err'] };
				return;
			}

			rv[key] = result['operations'][i]['result'];
		});

		callback(err, rv);
	});
};

MantaLocator.prototype.cleanup = function ()
{
	this.ml_ring.close(function () {});
};
<|endoftext|>


<commit_before>export const getBackendEnv = () => {
  const backendEnvElementContent = window.document.getElementById('BACKEND_INJECTED_ENV').textContent

  let backendEnv
  try {
    backendEnv = JSON.parse(backendEnvElementContent)
  } catch(e) {
    if (process.env.NODE_ENV !== 'production') {
      console.warn(
        `Backend injected environment could not be parsed. This is
        normal when running in development mode (yarn start).`
      )
    }
  }
  return backendEnv
}

const env = Object.assign({}, {
  // These may be replaced with values at build!
  API_ROOT: process.env.REACT_APP_API_ROOT,
  AUTH_FACEBOOK_AUTHORIZATION_URL: process.env.AUTH_FACEBOOK_AUTHORIZATION_URL,
  AUTH_FACEBOOK_CLIENT_ID: process.env.REACT_APP_AUTH_FACEBOOK_CLIENT_ID,
  AUTH_GOOGLE_AUTHORIZATION_URL: process.env.REACT_APP_AUTH_GOOGLE_AUTHORIZATION_URL,
  AUTH_GOOGLE_CLIENT_ID: process.env.REACT_APP_AUTH_GOOGLE_CLIENT_ID,
  AUTH_LIU_AUTHORIZATION_URL: process.env.REACT_APP_AUTH_LIU_AUTHORIZATION_URL,
  AUTH_LIU_CLIENT_ID: process.env.REACT_APP_AUTH_LIU_CLIENT_ID,
  AUTH_LIU_RESOURCE: process.env.REACT_APP_AUTH_LIU_RESOURCE,
  OPBEAT_APP_ID: process.env.REACT_APP_OPBEAT_APP_ID,
  OPBEAT_ORG_ID: process.env.REACT_APP_OPBEAT_ORG_ID,
  STRIPE_PUBLIC_KEY: process.env.REACT_APP_STRIPE_PUBLIC_KEY,
  TYPEKIT_ID: process.env.REACT_APP_TYPEKIT_ID
}, getBackendEnv() || {})

// Without trailing slash
const apiRoot = env.API_ROOT || '/api'

const authProviders = {
  facebook: {
    id: 'facebook',
    backendId: 'facebook',
    authUrl: env.AUTH_FACEBOOK_AUTHORIZATION_URL,
    clientId: env.AUTH_FACEBOOK_CLIENT_ID,
    scope: 'public_profile,email',
    scopeParam: 'scope'
  },
  google: {
    id: 'google',
    backendId: 'google-oauth2',
    authUrl: env.AUTH_GOOGLE_AUTHORIZATION_URL,
    clientId: env.AUTH_GOOGLE_CLIENT_ID,
    scope: 'openid email profile',
    scopeParam: 'scope'
  },
  liu: {
    id: 'liu',
    backendId: 'liu',
    authUrl: env.AUTH_LIU_AUTHORIZATION_URL,
    clientId: env.AUTH_LIU_CLIENT_ID,
    scope: env.AUTH_LIU_RESOURCE,
    scopeParam: 'resource'
  }
}

// Number of milliseconds before JWT expiry to issue a refresh
const jwtRefreshMargin = 60 * 1000

const opbeatAppId = env.OPBEAT_APP_ID
const opbeatOrgId = env.OPBEAT_ORG_ID

const persistedStateKey = 'state'
const persistedStateTimestampKey = 'stateTimestamp'
// The threshold for destroying persisted state. If the state is older than
// this (or not set), it will be destroyed. UTC timestamp, use
// (new Date).getTime()
const persistedStateTimestampThreshold = 1481422195713

const stripePublicKey = env.STRIPE_PUBLIC_KEY

const typekitId = env.TYPEKIT_ID

export { apiRoot, authProviders, jwtRefreshMargin, opbeatAppId, opbeatOrgId, persistedStateKey, persistedStateTimestampKey, persistedStateTimestampThreshold, stripePublicKey, typekitId }
<commit_msg>Reset old persisted states<commit_after>export const getBackendEnv = () => {
  const backendEnvElementContent = window.document.getElementById('BACKEND_INJECTED_ENV').textContent

  let backendEnv
  try {
    backendEnv = JSON.parse(backendEnvElementContent)
  } catch(e) {
    if (process.env.NODE_ENV !== 'production') {
      console.warn(
        `Backend injected environment could not be parsed. This is
        normal when running in development mode (yarn start).`
      )
    }
  }
  return backendEnv
}

const env = Object.assign({}, {
  // These may be replaced with values at build!
  API_ROOT: process.env.REACT_APP_API_ROOT,
  AUTH_FACEBOOK_AUTHORIZATION_URL: process.env.AUTH_FACEBOOK_AUTHORIZATION_URL,
  AUTH_FACEBOOK_CLIENT_ID: process.env.REACT_APP_AUTH_FACEBOOK_CLIENT_ID,
  AUTH_GOOGLE_AUTHORIZATION_URL: process.env.REACT_APP_AUTH_GOOGLE_AUTHORIZATION_URL,
  AUTH_GOOGLE_CLIENT_ID: process.env.REACT_APP_AUTH_GOOGLE_CLIENT_ID,
  AUTH_LIU_AUTHORIZATION_URL: process.env.REACT_APP_AUTH_LIU_AUTHORIZATION_URL,
  AUTH_LIU_CLIENT_ID: process.env.REACT_APP_AUTH_LIU_CLIENT_ID,
  AUTH_LIU_RESOURCE: process.env.REACT_APP_AUTH_LIU_RESOURCE,
  OPBEAT_APP_ID: process.env.REACT_APP_OPBEAT_APP_ID,
  OPBEAT_ORG_ID: process.env.REACT_APP_OPBEAT_ORG_ID,
  STRIPE_PUBLIC_KEY: process.env.REACT_APP_STRIPE_PUBLIC_KEY,
  TYPEKIT_ID: process.env.REACT_APP_TYPEKIT_ID
}, getBackendEnv() || {})

// Without trailing slash
const apiRoot = env.API_ROOT || '/api'

const authProviders = {
  facebook: {
    id: 'facebook',
    backendId: 'facebook',
    authUrl: env.AUTH_FACEBOOK_AUTHORIZATION_URL,
    clientId: env.AUTH_FACEBOOK_CLIENT_ID,
    scope: 'public_profile,email',
    scopeParam: 'scope'
  },
  google: {
    id: 'google',
    backendId: 'google-oauth2',
    authUrl: env.AUTH_GOOGLE_AUTHORIZATION_URL,
    clientId: env.AUTH_GOOGLE_CLIENT_ID,
    scope: 'openid email profile',
    scopeParam: 'scope'
  },
  liu: {
    id: 'liu',
    backendId: 'liu',
    authUrl: env.AUTH_LIU_AUTHORIZATION_URL,
    clientId: env.AUTH_LIU_CLIENT_ID,
    scope: env.AUTH_LIU_RESOURCE,
    scopeParam: 'resource'
  }
}

// Number of milliseconds before JWT expiry to issue a refresh
const jwtRefreshMargin = 60 * 1000

const opbeatAppId = env.OPBEAT_APP_ID
const opbeatOrgId = env.OPBEAT_ORG_ID

const persistedStateKey = 'state'
const persistedStateTimestampKey = 'stateTimestamp'
// The threshold for destroying persisted state. If the state is older than
// this (or not set), it will be destroyed. UTC timestamp, use
// (new Date).getTime()
const persistedStateTimestampThreshold = 1511828115632

const stripePublicKey = env.STRIPE_PUBLIC_KEY

const typekitId = env.TYPEKIT_ID

export { apiRoot, authProviders, jwtRefreshMargin, opbeatAppId, opbeatOrgId, persistedStateKey, persistedStateTimestampKey, persistedStateTimestampThreshold, stripePublicKey, typekitId }
<|endoftext|>


<commit_before>'use strict'

var moment = require('moment');
const utils = require('../utils/data.js');

exports.averageAge = function (jiraData, startDate, endDate) {

  var dataset = {};

  // guard against missing arguments
  if (!jiraData || !startDate || !endDate) {
    dataset.error = 'You have not passed in valid arguments';
    return dataset;
  }

  // guard to ensure incoming dates are YYYY-MM-DD strings
  if (startDate instanceof Date)
    startDate = startDate.toISOString().substring(0, 10);
  if (endDate instanceof Date)
    endDate = endDate.toISOString().substring(0, 10);

  var dateRange = utils.datesInRange(startDate, endDate);

  try {

    // pre-process jira data to reduce
    var issues = jiraData.map(issue => {
      var iss = {};
      iss.created = issue.Created.substring(0, 10);
      iss.resolved = utils.resolvedDate(issue);
      return iss;
    });

    // get relevant issues at each date point in range
    dateRange.forEach(date => {

      // get all unresolved & open issues that exist on this date
      var relevantIssues = issues.filter(iss => {
        return ((iss.created <= date) && !(utils.resolvedNow(iss, date)));
      });
      var numIssues = relevantIssues.length; // get number of issues

      // get age of each issue & derive average age of issues
      if (relevantIssues && relevantIssues.length > 1) {
        dataset[date] = relevantIssues.map(iss => {
              var created = moment(iss.created).add(12, 'hours'); // hack as date 1 hour off
              var current = moment(date).add(12, 'hours'); // hack as date 1 hour off
              return current.diff(created, 'days');
            }).reduce((a, b) => {
              return a + b;
            }) / numIssues;
      } else if (relevantIssues && relevantIssues.length === 1) {
        dataset[date] = relevantIssues.map(iss => {
              var created = moment(iss.created).add(12, 'hours'); // hack as date 1 hour off
              var current = moment(date).add(12, 'hours'); // hack as date 1 hour off
              return current.diff(created, 'days');
            }) / numIssues;
      } else {
        dataset[date] = 0;
      }
    });
  } catch (err) {
    dataset.error = err.message;
  }

  return dataset;
};
<commit_msg>updates averageage.js<commit_after>'use strict'

const moment = require('moment');
const utils = require('../utils/data.js');

exports.averageAge = function (jiraData, startDate, endDate) {

  let dataset = {};

  // guard against missing arguments
  if (!jiraData || !startDate || !endDate) {
    dataset.error = 'You have not passed in valid arguments';
    return dataset;
  }

  // guard to ensure incoming dates are YYYY-MM-DD strings
  if (startDate instanceof Date)
    startDate = startDate.toISOString().substring(0, 10);
  if (endDate instanceof Date)
    endDate = endDate.toISOString().substring(0, 10);

  const dateRange = utils.datesInRange(startDate, endDate);

  try {

    // pre-process jira data to reduce
    const issues = jiraData.map(issue => {
      let iss = {};
      iss.created = issue.Created.substring(0, 10);
      iss.resolved = utils.resolvedDate(issue);
      return iss;
    });

    // get relevant issues at each date point in range
    dateRange.forEach(date => {

      // get all unresolved & open issues that exist on this date
      const relevantIssues = issues.filter(iss => {
        return ((iss.created <= date) && !(utils.resolvedNow(iss, date)));
      });
      const numIssues = relevantIssues.length; // get number of issues

      // get age of each issue & derive average age of issues
      if (relevantIssues && relevantIssues.length > 1) {
        dataset[date] = relevantIssues.map(iss => {
              const created = moment(iss.created).add(12, 'hours'); // hack as date 1 hour off
              const current = moment(date).add(12, 'hours'); // hack as date 1 hour off
              return current.diff(created, 'days');
            }).reduce((a, b) => {
              return a + b;
            }) / numIssues;
      } else if (relevantIssues && relevantIssues.length === 1) {
        dataset[date] = relevantIssues.map(iss => {
              const created = moment(iss.created).add(12, 'hours'); // hack as date 1 hour off
              const current = moment(date).add(12, 'hours'); // hack as date 1 hour off
              return current.diff(created, 'days');
            }) / numIssues;
      } else {
        dataset[date] = 0;
      }
    });
  } catch (err) {
    dataset.error = err.message;
  }

  return dataset;
};
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const path = require('path');
const childProcess = require('child_process');
const textUtils = require('./text-utils');

/* globals gT, gIn */

// TODO: сделать так, чтобы тесты работали под правами специально заведеного юзера.
// У этого юзера будет доступ к тестовой директории только на чтение.
// Права на запись у него будут только в его home.
// Перед тестом (после подключения всех require), юзер процесса будет подменяться (process.setuid(id)).
// Весь test suite будет копироваться в директорию юзера и работать оттуда.
// gT_.fileUtilsCheckPath = function(path){
//
// };

/**
 * Checks that file or directory absent by statSync, without checking for catch reason (ENOENT or no).
 *
 * @param fileOrDirPath
 * @returns {boolean}
 */
exports.isAbsent = function isAbsent(fileOrDirPath) {
  try {
    fs.statSync(fileOrDirPath);
  } catch (e) {
    return true;
  }
  return false;
};

exports.isEtalonAbsent = function isEtalonAbsent(jsPath) {
  const etPath = textUtils.jsToEt(jsPath);
  try {
    fs.statSync(etPath);
  } catch (e) {
    return true;
  }
  return false;
};

exports.safeUnlink = function safeUnlink(fileOrDirPath) {
  try {
    fs.unlinkSync(fileOrDirPath);
  } catch (e) {
    // No handling intentionaly.
  }
};

exports.safeReadFile = function safeReadFile(fileOrDirPath) {
  let res = '';
  try {
    res = fs.readFileSync(fileOrDirPath, gT.engineConsts.logEncoding);
  } catch (e) {
    const asd = 5;
    // No handling intentionaly.
  }
  return res;
};

exports.backupDif = function backupDif(fileOrDirPath) {
  try {
    fs.renameSync(fileOrDirPath, `${fileOrDirPath}.old`);
  } catch (e) {
    // No handling intentionaly.
  }
};

exports.rmPngs = function rmPngs(jsPath) {
  const dir = path.dirname(jsPath);
  const start = path.basename(textUtils.changeExt(jsPath, ''));

  fs.readdirSync(dir).forEach((fileName) => {
    const filePath = path.join(dir, fileName);
    if (!exports.isDirectory(filePath)
      && filePath.endsWith('.png')
      && fileName.startsWith(start)) {
      exports.safeUnlink(filePath);
    }
  });
};

exports.rmDir = function rmDir(dir, removeSelf) {
  let files;
  try {
    files = fs.readdirSync(dir);
  } catch (e) {
    return;
  }
  if (files.length > 0) {
    for (let i = 0; i < files.length; i++) {
      const filePath = path.join(dir, files[i]);
      const fdata = fs.lstatSync(filePath);
      try {
        if (fdata.isSymbolicLink()) {
          fs.unlinkSync(filePath);
        }
        if (fdata.isFile()) {
          fs.unlinkSync(filePath);
        }
      } catch (e) {
        gIn.tracer.err(`rmDir: ${gIn.textUtils.excToStr(e)}`);
      }
      if (fdata.isDirectory()) {
        exports.rmDir(filePath, true);
      }
    }
  }
  if (removeSelf) {
    fs.rmdirSync(dir);
  }
};

exports.emptyDir = function emptyDir(dir) {
  exports.rmDir(dir);
};

exports.safeRename = function safeRename(oldPath, newPath) {
  exports.safeUnlink(newPath);
  try {
    fs.renameSync(oldPath, newPath);
  } catch (e) {
    // No handling intentionaly.
  }
};

// Removes file, if exists.
exports.createEmptyFileSync = function createEmptyFileSync(fileOrDirPath) {
  fs.closeSync(fs.openSync(fileOrDirPath, 'w'));
};

exports.createEmptyLog = function createEmptyLog(fileOrDirPath) {
  gIn.logger.logFile = gIn.textUtils.jsToLog(fileOrDirPath);
  exports.createEmptyFileSync(gIn.logger.logFile);
};

exports.fileToStdout = function fileToStdout(file) {
  console.log(fs.readFileSync(file, { encoding: gT.engineConsts.logEncoding }));
};

exports.fileToStderr = function fileToStderr(file) {
  // console.error(fs.readFileSync(file, {encoding: gT.engineConsts.logEncoding}));
  gIn.cLogger.errln(fs.readFileSync(file, { encoding: gT.engineConsts.logEncoding }));
};

exports.saveJson = function saveJson(obj, file) {
  fs.writeFileSync(file, JSON.stringify(obj, null, 2), { encoding: gT.engineConsts.logEncoding });
};

function collectArcPaths(dirInfo, arcPaths) {
  if (!dirInfo.diffed) {
    return;
  }

  // Absense of 'children' property says that it is test and not directory,
  // we should not allow to use this function for not directory.
  for (const curInfo of dirInfo.children) { // eslint-disable-line no-restricted-syntax
    if (Object.prototype.hasOwnProperty.call(curInfo, 'children')) {
      collectArcPaths(curInfo, arcPaths);
    } else if (curInfo.diffed) {
      arcPaths.push(gIn.textUtils.changeExt(curInfo.path, ''));
    }
  }
}

exports.getDirectoryAlias = function getDirectoryAlias(dirPath) {
  const pathArr = dirPath.split(path.sep);
  const last = pathArr.pop();
  if (last !== gT.engineConsts.suiteDirName) {
    throw new Error(`getDirectoryAlias: Incorrect path format: ${dirPath}`);
  }
  const alias = pathArr.pop();
  if (!alias) {
    throw new Error(`getDirectoryAlias: Incorrect path format: ${dirPath}`);
  }
  return alias;
};

exports.archiveSuiteDir = function archiveSuiteDir(dirInfo) {
  if (!gT.cLParams.enableEmail || !gT.suiteConfig.attachArchiveToMail || !gT.suiteConfig.mailRecipientList) {
    return null;
  }

  const alias = exports.getDirectoryAlias(dirInfo.path);

  const arcName = `${alias}_${new Date().toISOString().slice(0, 19).replace(/:/g, '_')}.zip`;

  const suitePathWOSuiteDirName = path.resolve(gT.cLParams.rootDir, dirInfo.path, '..');
  const wholeSuiteArcRelPath = path.relative(suitePathWOSuiteDirName, dirInfo.path);
  const resultArchivePath = path.resolve(suitePathWOSuiteDirName, arcName);

  if (!gT.suiteConfig.attachOnlyDiffs) {
    try {
      childProcess.execSync(
        `cd ${suitePathWOSuiteDirName} && zip -r ${arcName} "${wholeSuiteArcRelPath}"/*`,
        {
          stdio: [null, null, null],
          windowsHide: true,
        }
      );
    } catch (e) {
      gIn.tracer.err(`zip stderr: ${e.stderr.toString()}`);
      gIn.tracer.err(`zip stdout: ${e.stdout.toString()}`);
      throw (new Error('Error with zip (whole)'));
    }
    return resultArchivePath;
  }

  // Only diffs handling.
  const diffedPathsToArc = [];

  collectArcPaths(dirInfo, diffedPathsToArc);

  if (diffedPathsToArc.length === 0) {
    gIn.tracer.msg3('Archieve: No diffs, no archieve');
    return null;
  }

  const diffedRelativePathsToArc = diffedPathsToArc.map((diffedArcPath) => {
    const relativePath = `"${path.relative(suitePathWOSuiteDirName, diffedArcPath)}"*`;
    return relativePath;
  });

  try {
    childProcess.execSync(
      `cd ${suitePathWOSuiteDirName} && zip -r ${arcName} ${diffedRelativePathsToArc.join(' ')}`,
      { stdio: [null, null, null] }
    );
  } catch (e) {
    gIn.tracer.err(`zip stderr: ${e.stderr.toString()}`);
    gIn.tracer.err(`zip stdout: ${e.stdout.toString()}`);
    throw (new Error('Error with zip (diffs only)'));
  }

  return resultArchivePath;
};

exports.isDirectory = function isDirectory(fileOrDirPath) {
  let stat;
  try {
    stat = fs.statSync(fileOrDirPath);
  } catch (e) {
    return false;
  }

  return stat.isDirectory();
};

exports.mkdir = function mkdir(dirPath) {
  try {
    fs.mkdirSync(dirPath, { recursive: true });
  } catch (e) {

  }
};

exports.mkDirRecursive = function mkDirRecursive(targetDir, subDirsArr) {
  let curPath = targetDir;
  subDirsArr.forEach((dir) => {
    curPath = path.join(curPath, dir);
    exports.mkdir(curPath);
  });
};

exports.rmLastDirSep = function rmLastDirSep(dir) {
  if (dir[dir.length - 1] === path.sep) {
    return dir.slice(0, -1);
  }
  return dir;
};

// One of filenames.
exports.whichDirContain = function doesDirContain(base, fileNames, excludeThisBase) {
  const dirList = fs.readdirSync(base);

  for (const name of dirList) {
    const newBase = path.join(base, name);

    if (newBase === excludeThisBase) {
      continue;
    }

    if (fileNames.includes(name)) {
      return base;
    }

    if (fs.statSync(newBase).isDirectory()) {
      const dir = exports.whichDirContain(newBase, fileNames, excludeThisBase);
      if (dir) {
        return dir;
      }
    }
  }

  return null;
};
<commit_msg>Fix for case when node_modules contains tia tests.<commit_after>'use strict';

const fs = require('fs');
const path = require('path');
const childProcess = require('child_process');
const textUtils = require('./text-utils');

/* globals gT, gIn */

// TODO: сделать так, чтобы тесты работали под правами специально заведеного юзера.
// У этого юзера будет доступ к тестовой директории только на чтение.
// Права на запись у него будут только в его home.
// Перед тестом (после подключения всех require), юзер процесса будет подменяться (process.setuid(id)).
// Весь test suite будет копироваться в директорию юзера и работать оттуда.
// gT_.fileUtilsCheckPath = function(path){
//
// };

/**
 * Checks that file or directory absent by statSync, without checking for catch reason (ENOENT or no).
 *
 * @param fileOrDirPath
 * @returns {boolean}
 */
exports.isAbsent = function isAbsent(fileOrDirPath) {
  try {
    fs.statSync(fileOrDirPath);
  } catch (e) {
    return true;
  }
  return false;
};

exports.isEtalonAbsent = function isEtalonAbsent(jsPath) {
  const etPath = textUtils.jsToEt(jsPath);
  try {
    fs.statSync(etPath);
  } catch (e) {
    return true;
  }
  return false;
};

exports.safeUnlink = function safeUnlink(fileOrDirPath) {
  try {
    fs.unlinkSync(fileOrDirPath);
  } catch (e) {
    // No handling intentionaly.
  }
};

exports.safeReadFile = function safeReadFile(fileOrDirPath) {
  let res = '';
  try {
    res = fs.readFileSync(fileOrDirPath, gT.engineConsts.logEncoding);
  } catch (e) {
    const asd = 5;
    // No handling intentionaly.
  }
  return res;
};

exports.backupDif = function backupDif(fileOrDirPath) {
  try {
    fs.renameSync(fileOrDirPath, `${fileOrDirPath}.old`);
  } catch (e) {
    // No handling intentionaly.
  }
};

exports.rmPngs = function rmPngs(jsPath) {
  const dir = path.dirname(jsPath);
  const start = path.basename(textUtils.changeExt(jsPath, ''));

  fs.readdirSync(dir).forEach((fileName) => {
    const filePath = path.join(dir, fileName);
    if (!exports.isDirectory(filePath)
      && filePath.endsWith('.png')
      && fileName.startsWith(start)) {
      exports.safeUnlink(filePath);
    }
  });
};

exports.rmDir = function rmDir(dir, removeSelf) {
  let files;
  try {
    files = fs.readdirSync(dir);
  } catch (e) {
    return;
  }
  if (files.length > 0) {
    for (let i = 0; i < files.length; i++) {
      const filePath = path.join(dir, files[i]);
      const fdata = fs.lstatSync(filePath);
      try {
        if (fdata.isSymbolicLink()) {
          fs.unlinkSync(filePath);
        }
        if (fdata.isFile()) {
          fs.unlinkSync(filePath);
        }
      } catch (e) {
        gIn.tracer.err(`rmDir: ${gIn.textUtils.excToStr(e)}`);
      }
      if (fdata.isDirectory()) {
        exports.rmDir(filePath, true);
      }
    }
  }
  if (removeSelf) {
    fs.rmdirSync(dir);
  }
};

exports.emptyDir = function emptyDir(dir) {
  exports.rmDir(dir);
};

exports.safeRename = function safeRename(oldPath, newPath) {
  exports.safeUnlink(newPath);
  try {
    fs.renameSync(oldPath, newPath);
  } catch (e) {
    // No handling intentionaly.
  }
};

// Removes file, if exists.
exports.createEmptyFileSync = function createEmptyFileSync(fileOrDirPath) {
  fs.closeSync(fs.openSync(fileOrDirPath, 'w'));
};

exports.createEmptyLog = function createEmptyLog(fileOrDirPath) {
  gIn.logger.logFile = gIn.textUtils.jsToLog(fileOrDirPath);
  exports.createEmptyFileSync(gIn.logger.logFile);
};

exports.fileToStdout = function fileToStdout(file) {
  console.log(fs.readFileSync(file, { encoding: gT.engineConsts.logEncoding }));
};

exports.fileToStderr = function fileToStderr(file) {
  // console.error(fs.readFileSync(file, {encoding: gT.engineConsts.logEncoding}));
  gIn.cLogger.errln(fs.readFileSync(file, { encoding: gT.engineConsts.logEncoding }));
};

exports.saveJson = function saveJson(obj, file) {
  fs.writeFileSync(file, JSON.stringify(obj, null, 2), { encoding: gT.engineConsts.logEncoding });
};

function collectArcPaths(dirInfo, arcPaths) {
  if (!dirInfo.diffed) {
    return;
  }

  // Absense of 'children' property says that it is test and not directory,
  // we should not allow to use this function for not directory.
  for (const curInfo of dirInfo.children) { // eslint-disable-line no-restricted-syntax
    if (Object.prototype.hasOwnProperty.call(curInfo, 'children')) {
      collectArcPaths(curInfo, arcPaths);
    } else if (curInfo.diffed) {
      arcPaths.push(gIn.textUtils.changeExt(curInfo.path, ''));
    }
  }
}

exports.getDirectoryAlias = function getDirectoryAlias(dirPath) {
  const pathArr = dirPath.split(path.sep);
  const last = pathArr.pop();
  if (last !== gT.engineConsts.suiteDirName) {
    throw new Error(`getDirectoryAlias: Incorrect path format: ${dirPath}`);
  }
  const alias = pathArr.pop();
  if (!alias) {
    throw new Error(`getDirectoryAlias: Incorrect path format: ${dirPath}`);
  }
  return alias;
};

exports.archiveSuiteDir = function archiveSuiteDir(dirInfo) {
  if (!gT.cLParams.enableEmail || !gT.suiteConfig.attachArchiveToMail || !gT.suiteConfig.mailRecipientList) {
    return null;
  }

  const alias = exports.getDirectoryAlias(dirInfo.path);

  const arcName = `${alias}_${new Date().toISOString().slice(0, 19).replace(/:/g, '_')}.zip`;

  const suitePathWOSuiteDirName = path.resolve(gT.cLParams.rootDir, dirInfo.path, '..');
  const wholeSuiteArcRelPath = path.relative(suitePathWOSuiteDirName, dirInfo.path);
  const resultArchivePath = path.resolve(suitePathWOSuiteDirName, arcName);

  if (!gT.suiteConfig.attachOnlyDiffs) {
    try {
      childProcess.execSync(
        `cd ${suitePathWOSuiteDirName} && zip -r ${arcName} "${wholeSuiteArcRelPath}"/*`,
        {
          stdio: [null, null, null],
          windowsHide: true,
        }
      );
    } catch (e) {
      gIn.tracer.err(`zip stderr: ${e.stderr.toString()}`);
      gIn.tracer.err(`zip stdout: ${e.stdout.toString()}`);
      throw (new Error('Error with zip (whole)'));
    }
    return resultArchivePath;
  }

  // Only diffs handling.
  const diffedPathsToArc = [];

  collectArcPaths(dirInfo, diffedPathsToArc);

  if (diffedPathsToArc.length === 0) {
    gIn.tracer.msg3('Archieve: No diffs, no archieve');
    return null;
  }

  const diffedRelativePathsToArc = diffedPathsToArc.map((diffedArcPath) => {
    const relativePath = `"${path.relative(suitePathWOSuiteDirName, diffedArcPath)}"*`;
    return relativePath;
  });

  try {
    childProcess.execSync(
      `cd ${suitePathWOSuiteDirName} && zip -r ${arcName} ${diffedRelativePathsToArc.join(' ')}`,
      { stdio: [null, null, null] }
    );
  } catch (e) {
    gIn.tracer.err(`zip stderr: ${e.stderr.toString()}`);
    gIn.tracer.err(`zip stdout: ${e.stdout.toString()}`);
    throw (new Error('Error with zip (diffs only)'));
  }

  return resultArchivePath;
};

exports.isDirectory = function isDirectory(fileOrDirPath) {
  let stat;
  try {
    stat = fs.statSync(fileOrDirPath);
  } catch (e) {
    return false;
  }

  return stat.isDirectory();
};

exports.mkdir = function mkdir(dirPath) {
  try {
    fs.mkdirSync(dirPath, { recursive: true });
  } catch (e) {

  }
};

exports.mkDirRecursive = function mkDirRecursive(targetDir, subDirsArr) {
  let curPath = targetDir;
  subDirsArr.forEach((dir) => {
    curPath = path.join(curPath, dir);
    exports.mkdir(curPath);
  });
};

exports.rmLastDirSep = function rmLastDirSep(dir) {
  if (dir[dir.length - 1] === path.sep) {
    return dir.slice(0, -1);
  }
  return dir;
};

// One of filenames.
exports.whichDirContain = function whichDirContain(base, fileNames, excludeThisBase) {
  const dirList = fs.readdirSync(base);

  for (const name of dirList) {
    if (name === 'node_modules') {
      continue;
    }

    const newBase = path.join(base, name);

    if (newBase === excludeThisBase) {
      continue;
    }

    if (fileNames.includes(name)) {
      return base;
    }

    if (fs.statSync(newBase).isDirectory()) {
      const dir = exports.whichDirContain(newBase, fileNames, excludeThisBase);
      if (dir) {
        return dir;
      }
    }
  }

  return null;
};
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var Bluebird = require('bluebird');
var MongoClient = require('mongodb').MongoClient;
var Logger = require('mongodb').Logger;

class MongoDatabase {
  constructor(options) {
    this._options = this._parseOptions(options);
  }

  initialize() {
    return new Bluebird((resolve, reject) => {
      Logger.setLevel(this._options.databaseLogLevel);
      MongoClient.connect(this._options.databaseUrl, (error, db) => {
        if (error) {
          reject('Impossible to connect to database: ' + error.message);
        } else {
          this._mongoDb = db;
          this._options.log.info('Connection to database successful');
          resolve(this);
        }
      });
    });
  }

  close() {
    this._mongoDb.close();
  }

  findAll(collectionName, criteria, options) {
    return new Bluebird((resolve, reject) => {
      var collection = this._mongoDb.collection(collectionName);
      var foundDocuments = collection.find(this._withMongoId(criteria));
      foundDocuments = this._withFindOptionsApplied(foundDocuments, options || {});
      return foundDocuments.toArray((error, docs) => {
        if (error) {
          reject(error);
        } else {
          resolve(this._withIds(docs));
        }
      });
    });
  }

  _withFindOptionsApplied(foundDocuments, options) {
    if (options.orderBy) {
      var sort = {order: 1};
      if (options.orderBy.order === 'desc') {
        sort.order = -1;
      }
      sort[options.orderBy.key] = sort.order;
      return foundDocuments.sort(sort);
    }
    return foundDocuments;
  }

  findFirst(collectionName, criteria) {
    return new Bluebird((resolve, reject) => {
      var collection = this._mongoDb.collection(collectionName);
      collection.findOne(this._withMongoId(criteria), (error, doc) => {
        if (error) {
          reject(error);
        } else {
          resolve(this._withId(doc));
        }
      });
    });
  }

  findNear(collectionName, geolocation, options) {
    return new Bluebird((resolve, reject) => {
      var collection = this._mongoDb.collection(collectionName);
      collection.geoNear(geolocation.longitude, geolocation.latitude, options, (error, response) => {
        if (error) {
          reject(error);
        } else {
          let results = _.map(response.results, result => {
            return {
              distance: result.dis,
              document: this._withId(result.obj)
            };
          });
          resolve(results);
        }
      });
    });
  }

  count(collectionName, criteria) {
    return Bluebird.try(() => {
      var collection = this._mongoDb.collection(collectionName);
      return collection.count(this._withMongoId(criteria));
    });
  }

  add(collectionName, document) {
    return new Bluebird((resolve, reject) => {
      var collection = this._mongoDb.collection(collectionName);
      collection.insertOne(this._withMongoId(document), (error, result) => {
        if (error) {
          reject(error);
        } else {
          resolve(result);
        }
      });
    });
  }

  updateFirst(collectionName, criteria, update) {
    return new Bluebird((resolve, reject) => {
      var collection = this._mongoDb.collection(collectionName);
      collection.updateOne(this._withMongoId(criteria), {$set: update}, (error, result) => {
        if (error) {
          reject(error);
        } else {
          resolve(result);
        }
      });
    });
  }

  deleteFirst(collectionName, criteria) {
    return new Bluebird((resolve, reject) => {
      var collection = this._mongoDb.collection(collectionName);
      collection.deleteOne(this._withMongoId(criteria), (error, result) => {
        if (error) {
          reject(error);
        } else {
          resolve(result);
        }
      });
    });
  }

  _withIds(documents) {
    return _.map(documents, this._withId);
  }

  _withId(document) {
    if (!document) {
      return document;
    }
    return _.omit(_.merge({id: document._id}, document), '_id');
  }

  _withMongoId(criteria) {
    var $criteria = criteria || {};
    if ($criteria.id) {
      return _.omit(_.merge({_id: $criteria.id}, $criteria), 'id');
    }
    return $criteria;
  }

  _parseOptions(options) {
    return _.defaults(options || {}, {
      log: console,
      databaseLogLevel: 'info',
      databaseUrl: 'to define'
    });
  }
}

module.exports = MongoDatabase;
<commit_msg>more promises usage in the driver<commit_after>'use strict';

var _ = require('lodash');
var Bluebird = require('bluebird');
var MongoClient = require('mongodb').MongoClient;
var Logger = require('mongodb').Logger;

class MongoDatabase {
  constructor(options) {
    this._options = this._parseOptions(options);
  }

  initialize() {
    Logger.setLevel(this._options.databaseLogLevel);
    return MongoClient.connect(this._options.databaseUrl, {promiseLibrary: Bluebird})
      .then(db => {
        this._mongoDb = db;
        this._options.log.info('Connection to database successful');
      })
      .return(this)
      .catch(error => {
        throw new Error(`Impossible to connect to database: ${error.message}`);
      });
  }

  close() {
    this._mongoDb.close();
  }

  findAll(collectionName, criteria, options) {
    return Bluebird.try(() => {
      var collection = this._mongoDb.collection(collectionName);
      var foundDocuments = collection.find(this._withMongoId(criteria));
      foundDocuments = this._withFindOptionsApplied(foundDocuments, options || {});
      return foundDocuments.toArray().then(docs => {
        return this._withIds(docs);
      });
    });
  }

  _withFindOptionsApplied(foundDocuments, options) {
    if (options.orderBy) {
      var sort = {order: 1};
      if (options.orderBy.order === 'desc') {
        sort.order = -1;
      }
      sort[options.orderBy.key] = sort.order;
      return foundDocuments.sort(sort);
    }
    return foundDocuments;
  }

  findFirst(collectionName, criteria) {
    var collection = this._mongoDb.collection(collectionName);
    return collection.findOne(this._withMongoId(criteria)).then(doc => {
      return this._withId(doc);
    });
  }

  findNear(collectionName, geolocation, options) {
    var collection = this._mongoDb.collection(collectionName);
    return collection.geoNear(geolocation.longitude, geolocation.latitude, options).then(response => {
      let results = _.map(response.results, result => {
        return {
          distance: result.dis,
          document: this._withId(result.obj)
        };
      });
      return results;
    });
  }

  count(collectionName, criteria) {
    var collection = this._mongoDb.collection(collectionName);
    return collection.count(this._withMongoId(criteria));
  }

  add(collectionName, document) {
    var collection = this._mongoDb.collection(collectionName);
    return collection.insertOne(this._withMongoId(document));
  }

  updateFirst(collectionName, criteria, update) {
    var collection = this._mongoDb.collection(collectionName);
    return collection.updateOne(this._withMongoId(criteria), {$set: update});
  }

  deleteFirst(collectionName, criteria) {
    var collection = this._mongoDb.collection(collectionName);
    return collection.deleteOne(this._withMongoId(criteria));
  }

  _withIds(documents) {
    return _.map(documents, this._withId);
  }

  _withId(document) {
    if (!document) {
      return document;
    }
    return _.omit(_.merge({id: document._id}, document), '_id');
  }

  _withMongoId(criteria) {
    var $criteria = criteria || {};
    if ($criteria.id) {
      return _.omit(_.merge({_id: $criteria.id}, $criteria), 'id');
    }
    return $criteria;
  }

  _parseOptions(options) {
    return _.defaults(options || {}, {
      log: console,
      databaseLogLevel: 'info',
      databaseUrl: 'to define'
    });
  }
}

module.exports = MongoDatabase;
<|endoftext|>


<commit_before>// this is tmp, use react-intl .. later

const I18nDict = {
  posts: '帖子',
  map: '地图',
  news: '动态(wiki)',
  cheatsheet: 'cheatsheet',
  meetups: 'meetups',
  users: '用户',
  videos: '视频',
  repos: '开源项目',
  jobs: '招聘',
}

export const Trans = key => I18nDict[key] || key

export const holder = 1
<commit_msg>chore(tabber): rename "用户" -> '社友'<commit_after>// this is tmp, use react-intl .. later

const I18nDict = {
  posts: '帖子',
  map: '地图',
  news: '动态(wiki)',
  cheatsheet: 'cheatsheet',
  meetups: 'meetups',
  users: '社友',
  videos: '视频',
  repos: '开源项目',
  jobs: '招聘',
}

export const Trans = key => I18nDict[key] || key

export const holder = 1
<|endoftext|>