<commit_before>define([
	"intern!object",
	"intern/chai!assert",
	"dcl/dcl",
	"dojo/_base/declare",
	"delite/register",
	"delite/Widget",
	"delite/StoreMap",
	"dstore/Trackable",
	"dstore/Memory", "dcl/inherited"
], function (registerSuite, assert, dcl, declare, register, Widget, StoreMap, Trackable, Memory) {
	var M = declare([Memory, Trackable], {});

	registerSuite({
		name: "StoreMap",

		Regular: function () {
			var C = register("test-storemap-1", [HTMLElement, Widget, StoreMap], {
				fooAttr: "name",
				barFunc: function (item) {
					return item.firstname;
				}
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo", bar: "1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				myStore.addSync({ id: "fb", name: "FB", firstname: "4" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				assert.deepEqual(store.renderItems[2], { id: "fb", foo: "FB", bar: "4" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3" });
				assert.deepEqual(store.renderItems[1], { id: "fb", foo: "FB", bar: "4" });
			}));
			store.startup();
			var myStore = new M({ data: myData});
			store.store = myStore;
			return d;
		},

		copyAll: function () {
			var C = register("test-storemap-2", [HTMLElement, Widget, StoreMap], {
				copyAllItemProps: true
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo", firstname: "1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", name: "Bar", firstname: "2" });
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo2", firstname: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", name: "Bar", firstname: "2" });
				myStore.addSync({ id: "fb", name: "FB", firstname: "4" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo2", firstname: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", name: "Bar", firstname: "2" });
				assert.deepEqual(store.renderItems[2], { id: "fb", name: "FB", firstname: "4" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo2", firstname: "3" });
				assert.deepEqual(store.renderItems[1], { id: "fb", name: "FB", firstname: "4" });
			}));
			store.startup();
			// use empty model to ease comparison
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;
		},

		InCtor: function () {
			var C = register("test-storemap-3", [HTMLElement, Widget, StoreMap], {
			});
			var d = this.async(2000);
			var store = new C({"fooAttr": "name"});
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar" });
				myStore.putSync({ id: "foo", name: "Foo2" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar" });
				myStore.addSync({ id: "fb", name: "FB" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar" });
				assert.deepEqual(store.renderItems[2], { id: "fb", foo: "FB" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2" });
				assert.deepEqual(store.renderItems[1], { id: "fb", foo: "FB" });
			}));
			store.startup();
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;

		},

		AllowRemap: function () {
			var value = "1";
			var C = register("test-storemap-4", [HTMLElement, Widget, StoreMap], {
				allowRemap: true,
				fooAttr: "name",
				barFunc: function (item) {
					return item.firstname + value;
				}
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert.deepEqual(store.renderItems[0].foo, "Foo");
				assert.deepEqual(store.renderItems[0].bar, "11");
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.deepEqual(store.renderItems[0].foo, "Foo2");
				assert.deepEqual(store.renderItems[0].bar, "31");
				value = 2;
				assert.deepEqual(store.renderItems[0].foo, "Foo2");
				assert.deepEqual(store.renderItems[0].bar, "31");
				store.remap();
				assert.deepEqual(store.renderItems[0].foo, "Foo2");
				assert.deepEqual(store.renderItems[0].bar, "32");
			}));
			store.startup();
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;

		},

		Markup: function () {
			register("test-storemap-5", [HTMLElement, Widget, StoreMap], {
				fooAttr: "name"
			});
			/* global fct:true */
			fct = function () { return "fct"; };
			var tag = "<test-storemap-5 id='ts6' barAttr='firstname' mFunc='fct' " +
				"nFunc='return item.name + item.firstname;'></test-storemap-6>";
			var tagHolder = document.createElement("div");
			tagHolder.innerHTML = tag;
			register.parse(tagHolder);
			var d = this.async(2000);
			var store = tagHolder.children[0];
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo", bar: "1", m: "fct", n: "Foo1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2", m: "fct", n: "Bar2" });
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3", m: "fct", n: "Foo23" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2", m: "fct", n: "Bar2" });
				myStore.addSync({ id: "fb", name: "FB", firstname: "4" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3", m: "fct", n: "Foo23" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2", m: "fct", n: "Bar2" });
				assert.deepEqual(store.renderItems[2], { id: "fb", foo: "FB", bar: "4", m: "fct", n: "FB4" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3", m: "fct", n: "Foo23" });
				assert.deepEqual(store.renderItems[1], { id: "fb", foo: "FB", bar: "4", m: "fct", n: "FB4" });
			}));
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;

		},

		ItemToAndFrom: function () {
			var C = register("test-storemap-6", [HTMLElement, Widget, StoreMap], {
				fooAttr: "name",
				barFunc: function (item) {
					return item.firstname;
				}
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.rejectOnError(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo", bar: "1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				store.renderItemToItem(store.renderItems[0]).then(d.callback(function(item) {
					assert.deepEqual(item, myData[0]);
					var renderItem = store.itemToRenderItem(item);
					assert.deepEqual(renderItem, { id: "foo", foo: "Foo", bar: "1" });
				}));
			}));
			store.startup();
			var myStore = new Memory({ data: myData });
			store.store = myStore;
			return d;
		},

		teardown: function () {
			//container.parentNode.removeChild(container);
		}
	});
});

<commit_msg>fix jshint in test<commit_after>define([
	"intern!object",
	"intern/chai!assert",
	"dcl/dcl",
	"dojo/_base/declare",
	"delite/register",
	"delite/Widget",
	"delite/StoreMap",
	"dstore/Trackable",
	"dstore/Memory", "dcl/inherited"
], function (registerSuite, assert, dcl, declare, register, Widget, StoreMap, Trackable, Memory) {
	var M = declare([Memory, Trackable], {});

	registerSuite({
		name: "StoreMap",

		Regular: function () {
			var C = register("test-storemap-1", [HTMLElement, Widget, StoreMap], {
				fooAttr: "name",
				barFunc: function (item) {
					return item.firstname;
				}
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo", bar: "1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				myStore.addSync({ id: "fb", name: "FB", firstname: "4" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				assert.deepEqual(store.renderItems[2], { id: "fb", foo: "FB", bar: "4" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3" });
				assert.deepEqual(store.renderItems[1], { id: "fb", foo: "FB", bar: "4" });
			}));
			store.startup();
			var myStore = new M({ data: myData});
			store.store = myStore;
			return d;
		},

		copyAll: function () {
			var C = register("test-storemap-2", [HTMLElement, Widget, StoreMap], {
				copyAllItemProps: true
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo", firstname: "1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", name: "Bar", firstname: "2" });
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo2", firstname: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", name: "Bar", firstname: "2" });
				myStore.addSync({ id: "fb", name: "FB", firstname: "4" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo2", firstname: "3" });
				assert.deepEqual(store.renderItems[1], { id: "bar", name: "Bar", firstname: "2" });
				assert.deepEqual(store.renderItems[2], { id: "fb", name: "FB", firstname: "4" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", name: "Foo2", firstname: "3" });
				assert.deepEqual(store.renderItems[1], { id: "fb", name: "FB", firstname: "4" });
			}));
			store.startup();
			// use empty model to ease comparison
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;
		},

		InCtor: function () {
			var C = register("test-storemap-3", [HTMLElement, Widget, StoreMap], {
			});
			var d = this.async(2000);
			var store = new C({"fooAttr": "name"});
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar" });
				myStore.putSync({ id: "foo", name: "Foo2" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar" });
				myStore.addSync({ id: "fb", name: "FB" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar" });
				assert.deepEqual(store.renderItems[2], { id: "fb", foo: "FB" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2" });
				assert.deepEqual(store.renderItems[1], { id: "fb", foo: "FB" });
			}));
			store.startup();
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;

		},

		AllowRemap: function () {
			var value = "1";
			var C = register("test-storemap-4", [HTMLElement, Widget, StoreMap], {
				allowRemap: true,
				fooAttr: "name",
				barFunc: function (item) {
					return item.firstname + value;
				}
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert.deepEqual(store.renderItems[0].foo, "Foo");
				assert.deepEqual(store.renderItems[0].bar, "11");
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.deepEqual(store.renderItems[0].foo, "Foo2");
				assert.deepEqual(store.renderItems[0].bar, "31");
				value = 2;
				assert.deepEqual(store.renderItems[0].foo, "Foo2");
				assert.deepEqual(store.renderItems[0].bar, "31");
				store.remap();
				assert.deepEqual(store.renderItems[0].foo, "Foo2");
				assert.deepEqual(store.renderItems[0].bar, "32");
			}));
			store.startup();
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;

		},

		Markup: function () {
			register("test-storemap-5", [HTMLElement, Widget, StoreMap], {
				fooAttr: "name"
			});
			/* global fct:true */
			fct = function () { return "fct"; };
			var tag = "<test-storemap-5 id='ts6' barAttr='firstname' mFunc='fct' " +
				"nFunc='return item.name + item.firstname;'></test-storemap-6>";
			var tagHolder = document.createElement("div");
			tagHolder.innerHTML = tag;
			register.parse(tagHolder);
			var d = this.async(2000);
			var store = tagHolder.children[0];
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.callback(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo", bar: "1", m: "fct", n: "Foo1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2", m: "fct", n: "Bar2" });
				myStore.putSync({ id: "foo", name: "Foo2", firstname: "3" });
				// this works because put is synchronous & same for add etc...
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3", m: "fct", n: "Foo23" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2", m: "fct", n: "Bar2" });
				myStore.addSync({ id: "fb", name: "FB", firstname: "4" });
				assert.strictEqual(store.renderItems.length, 3);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3", m: "fct", n: "Foo23" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2", m: "fct", n: "Bar2" });
				assert.deepEqual(store.renderItems[2], { id: "fb", foo: "FB", bar: "4", m: "fct", n: "FB4" });
				myStore.removeSync("bar");
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo2", bar: "3", m: "fct", n: "Foo23" });
				assert.deepEqual(store.renderItems[1], { id: "fb", foo: "FB", bar: "4", m: "fct", n: "FB4" });
			}));
			var myStore = new M({ data: myData });
			store.store = myStore;
			return d;

		},

		ItemToAndFrom: function () {
			var C = register("test-storemap-6", [HTMLElement, Widget, StoreMap], {
				fooAttr: "name",
				barFunc: function (item) {
					return item.firstname;
				}
			});
			var d = this.async(2000);
			var store = new C();
			var myData = [
				{ id: "foo", name: "Foo", firstname: "1" },
				{ id: "bar", name: "Bar", firstname: "2" }
			];
			store.on("query-success", d.rejectOnError(function () {
				assert(store.renderItems instanceof Array);
				assert.strictEqual(store.renderItems.length, 2);
				assert.deepEqual(store.renderItems[0], { id: "foo", foo: "Foo", bar: "1" });
				assert.deepEqual(store.renderItems[1], { id: "bar", foo: "Bar", bar: "2" });
				store.renderItemToItem(store.renderItems[0]).then(d.callback(function (item) {
					assert.deepEqual(item, myData[0]);
					var renderItem = store.itemToRenderItem(item);
					assert.deepEqual(renderItem, { id: "foo", foo: "Foo", bar: "1" });
				}));
			}));
			store.startup();
			var myStore = new Memory({ data: myData });
			store.store = myStore;
			return d;
		},

		teardown: function () {
			//container.parentNode.removeChild(container);
		}
	});
});

<|endoftext|>


<commit_before>$(document).ready(function(){

    $("div.issue").each(function(){
        //var vote_form = $('form', this);
        var my_vote = parseInt($("#my_vote", this).val());
        var my_issue = parseInt($("#my_vote_issue", this).val());
        var my_privacy = parseInt($("#my_vote_privacy", this).val());

        render_bars(my_issue, my_vote);

        $("td.for", this).mouseup(function(){
            var old_vote = parseInt($("#my_vote", this).val());
            process_vote(my_issue, old_vote, 1);
        });
        $("td.abstain", this).mouseup(function(){
            var old_vote = parseInt($("#my_vote", this).val());
            process_vote(my_issue, old_vote, 17);
        });
        $("td.against", this).mouseup(function(){
            var old_vote = parseInt($("#my_vote", this).val());
            process_vote(my_issue, old_vote, -1);
        });
    });
    // hide all issue bodies
    $("div.body").each(function() {
	    $(this).hide();
    });
    //hook the mouseup events to each title
    $("div.issue").children("div.title").mouseup(function(){

        //find the body whose title was clicked
        var body = $(this).parent().children("div.body");

        //slide the panel
        body.slideToggle();
    });
});

function process_vote(issue, old_vote, new_vote) {
    if(old_vote == new_vote) {
        return
    }
    if(old_vote!=new_vote) {
        $.post('/ajax/vote/cast/', {
            issue : issue,
            vote : new_vote,
            keep_private : false
        }, function(data) {
            if(data.status=="success"){
            // fix layouts here
                $("#issue"+issue).find("#my_vote").html(""+new_vote);
                render_bars(issue, old_vote, new_vote);
            } else if(data.status=="debug") {
                if(debug) {
                // debug stuff (server errors)
                }
            } else {
                handle_errors(data.error);
            }
        }, "json");
    }
}

function render_bars(issue, old_vote, new_vote){
    $("#issue"+issue).find("td.for").css({'background-color' : '#a1f2a3'});
    $("#issue"+issue).find("td.abstain").css({'background-color' : '#9ef8fb'});
    $("#issue"+issue).find("td.against").css({'background-color' : '#f99f9b'});
    if(new_vote==null) {
        if(old_vote ==1) {
            $("#issue"+issue).find("td.for").css({'background-color' : '#49f24d'});
        }
        if(old_vote >=10) {
            $("#issue"+issue).find("td.abstain").css({'background-color' : '#4cf5fb'});
        }
        if(old_vote ==-1) {
            $("#issue"+issue).find("td.against").css({'background-color' : '#f9645e'});
        }
    }
    var vfor = parseInt($("#issue"+issue).find("td.for > a", this).html());
    var vabs = parseInt($("#issue"+issue).find("td.abstain > a", this).html());
    var vaga = parseInt($("#issue"+issue).find("td.against > a", this).html());
    if(new_vote == 1) {
        $("#issue"+issue).find("td.for").css({'background-color' : '#49f24d'});
        vfor += 1;
        if(old_vote >=10) {
            vabs -= 1;
            $("#issue"+issue).find("td.absstain > a", this).html(""+vabs)
        }
        if(old_vote ==-1) {
            vaga -= 1;
            $("#issue"+issue).find("td.against > a", this).html(""+vaga)
        }
    }
    if(new_vote >= 10) {
        $("#issue"+issue).find("td.abstain").css({'background-color' : '#4cf5fb'});
        vabs += 1;
        if(old_vote ==1) {
            vfor -= 1;
            $("#issue"+issue).find("td.for > a", this).html(""+vfor)
        }
        if(old_vote ==-1) {
            vaga -= 1;
            $("#issue"+issue).find("td.against > a", this).html(""+vaga)
        }
    }
    if(new_vote == -1) {
        $("#issue"+issue).find("td.against").css({'background-color' : '#f9645e'});
        vaga += 1;
        if(old_vote ==1) {
            vfor -= 1;
            $("#issue"+issue).find("td.for > a", this).html(""+vfor)
        }
        if(old_vote >=10) {
            vabs -= 1;
            $("#issue"+issue).find("td.abstain > a", this).html(""+vabs)
        }
    }
    $("#issue"+issue).find("td.for").find("a").html(""+vfor);
    $("#issue"+issue).find("td.abstain").find("a").html(""+vabs);
    $("#issue"+issue).find("td.against").find("a").html(""+vaga);
    var total = vfor + vabs + vaga;
    var per = new Array();
    per['for'] = vfor / total;
    per['abs'] = vabs / total;
    per['aga'] = vaga / total;
    var biggest = "none";
    var big = 0;
    for (var i in per) {
        if(per[i] > big) {
            big = per[i];
            biggest = i;
        }
    }
    for (var i in per) {
        if(per[i] < 0.1) {
            per[biggest] -= (0.1 - per[i]);
            per[i] = 0.1;
        }
    }
    for (var i in per) {
        per[i] = per[i]*100;
    }
    $("#issue"+issue).find("td.for").width(per['for']+"%");
    $("#issue"+issue).find("td.abstain").width(per['abs']+"%");
    $("#issue"+issue).find("td.against").width(per['aga']+"%");
}

function handle_errors(error) {
    if(error == "Must be logged in.") {
        window.location = "/login/";
    }
    if(error == "Must have access token.") {
        window.location = "/oauth/auth/";
    }
}
<commit_msg>fixed the voting<commit_after>$(document).ready(function(){

    $("div.issue").each(function(){
        //var vote_form = $('form', this);
        var my_vote = parseInt($("#my_vote", this).val());
        var my_issue = parseInt($("#my_vote_issue", this).val());
        var my_privacy = parseInt($("#my_vote_privacy", this).val());

        render_bars(my_issue, my_vote);

        $("td.for", this).mouseup(function(){
            process_vote(my_issue, 1);
        });
        $("td.abstain", this).mouseup(function(){
            process_vote(my_issue, 17);
        });
        $("td.against", this).mouseup(function(){
            process_vote(my_issue, -1);
        });
    });
    // hide all issue bodies
    $("div.body").each(function() {
	    $(this).hide();
    });
    //hook the mouseup events to each title
    $("div.issue").children("div.title").mouseup(function(){

        //find the body whose title was clicked
        var body = $(this).parent().children("div.body");

        //slide the panel
        body.slideToggle();
    });
});

function process_vote(issue, new_vote) {
    var old_vote = parseInt($("#issue"+issue).find("#my_vote").val());
    if(old_vote == new_vote) {
        return
    }
    if(old_vote!=new_vote) {
        $.post('/ajax/vote/cast/', {
            issue : issue,
            vote : new_vote,
            keep_private : false
        }, function(data) {
            if(data.status=="success"){
            // fix layouts here
                $("#issue"+issue).find("#my_vote").html(""+new_vote);
                render_bars(issue, new_vote);
            } else if(data.status=="debug") {
                if(debug) {
                // debug stuff (server errors)
                }
            } else {
                handle_errors(data.error);
            }
        }, "json");
    }
}

function render_bars(issue, new_vote){
    var old_vote = parseInt($("#issue"+issue).find("#my_vote").val());
    // reset the bar colors to their unvoted colors
    $("#issue"+issue).find("td.for").css({'background-color' : '#a1f2a3'});
    $("#issue"+issue).find("td.abstain").css({'background-color' : '#9ef8fb'});
    $("#issue"+issue).find("td.against").css({'background-color' : '#f99f9b'});
    if(new_vote==null) {
    // first page render, set voted color, if any
        if(old_vote ==1) {
            $("#issue"+issue).find("td.for").css({'background-color' : '#49f24d'});
        }
        if(old_vote >=10) {
            $("#issue"+issue).find("td.abstain").css({'background-color' : '#4cf5fb'});
        }
        if(old_vote ==-1) {
            $("#issue"+issue).find("td.against").css({'background-color' : '#f9645e'});
        }
    }
    // get vote totals
    var vfor = parseInt($("#issue"+issue).find("td.for > a").html());
    var vabs = parseInt($("#issue"+issue).find("td.abstain > a").html());
    var vaga = parseInt($("#issue"+issue).find("td.against > a").html());
    // update vote totals, subtract from old vote totals and reset colors.
    if(new_vote == 1) {
        $("#issue"+issue).find("td.for").css({'background-color' : '#49f24d'});
        vfor += 1;
        if(old_vote >=10) {
            vabs -= 1;
        }
        if(old_vote ==-1) {
            vaga -= 1;
        }
    }
    if(new_vote >= 10) {
        vabs += 1;
        if(old_vote ==1) {
            vfor -= 1;
        }
        if(old_vote ==-1) {
            vaga -= 1;
        }
    }
    if(new_vote == -1) {
        $("#issue"+issue).find("td.against").css({'background-color' : '#f9645e'});
        vaga += 1;
        if(old_vote ==1) {
            vfor -= 1;
        }
        if(old_vote >=10) {
            vabs -= 1;
        }
    }
    $("#issue"+issue).find("td.for").find("a").html(""+vfor);
    $("#issue"+issue).find("td.abstain").find("a").html(""+vabs);
    $("#issue"+issue).find("td.against").find("a").html(""+vaga);
    $("#issue"+issue).find("#my_vote").val(new_vote);
    var total = vfor + vabs + vaga;
    var per = new Array();
    per['for'] = vfor / total;
    per['abs'] = vabs / total;
    per['aga'] = vaga / total;
    var biggest = "none";
    var big = 0;
    for (var i in per) {
        if(per[i] > big) {
            big = per[i];
            biggest = i;
        }
    }
    for (var i in per) {
        if(per[i] < 0.1) {
            per[biggest] -= (0.1 - per[i]);
            per[i] = 0.1;
        }
    }
    for (var i in per) {
        per[i] = per[i]*100;
    }
    $("#issue"+issue).find("td.for").width(per['for']+"%");
    $("#issue"+issue).find("td.abstain").width(per['abs']+"%");
    $("#issue"+issue).find("td.against").width(per['aga']+"%");
}

function handle_errors(error) {
    if(error == "Must be logged in.") {
        window.location = "/login/";
    }
    if(error == "Must have access token.") {
        window.location = "/oauth/auth/";
    }
}
<|endoftext|>


<commit_before>function(newDoc, oldDoc, userCtx) {
  if (oldDoc){
    if (userCtx.roles.indexOf("_admin") == -1) {
      throw({forbidden: "Only database admins are allowed to update pastes/comments"});
    }
  } else {
    // verify well-formedness

    var malformed = false;
    if (typeof(newDoc.data) != "string") {
      malformed = true;
    } else if (["paste", "comment"].indexOf(newDoc.type) == -1) {
      malformed = true;
    }

    if (malformed == true) {
      throw({forbidden: "The document is malformed. Not accepting this."});
    }
  }
}
<commit_msg>Verify that paste size is < 2MB<commit_after>function(newDoc, oldDoc, userCtx) {
  if (oldDoc){
    if (userCtx.roles.indexOf("_admin") == -1) {
      throw({forbidden: "Only database admins are allowed to update pastes/comments"});
    }
  } else {

    // verify well-formedness
    var malformed = false;
    if (typeof(newDoc.data) != "string") {
      malformed = true;
    } else if (["paste", "comment"].indexOf(newDoc.type) == -1) {
      malformed = true;
    }

    if (malformed == true) {
      throw({forbidden: "The document is malformed. Not accepting this."});
    }

    // verify size
    if (newDoc.data.length * 2 >= 2000000) {
      throw({forbidden: "Paste is limited to 2MB of encrypted data"});
    }
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule DraftEditor.react
 * @typechecks
 * @flow
 */

'use strict';

const DefaultDraftInlineStyle = require('DefaultDraftInlineStyle');
const DraftEditorCompositionHandler = require('DraftEditorCompositionHandler');
const DraftEditorContents = require('DraftEditorContents.react');
const DraftEditorDragHandler = require('DraftEditorDragHandler');
const DraftEditorEditHandler = require('DraftEditorEditHandler');
const DraftEditorPlaceholder = require('DraftEditorPlaceholder.react');
const EditorState = require('EditorState');
const React = require('React');
const ReactDOM = require('ReactDOM');
const Scroll = require('Scroll');
const Style = require('Style');
const UserAgent = require('UserAgent');

const cx = require('cx');
const emptyFunction = require('emptyFunction');
const generateRandomKey = require('generateRandomKey');
const getDefaultKeyBinding = require('getDefaultKeyBinding');
const nullthrows = require('nullthrows');
const getScrollPosition = require('getScrollPosition');

import type {BlockMap} from 'BlockMap';
import type ContentBlock from 'ContentBlock';
import type {DraftEditorModes} from 'DraftEditorModes';
import type {DraftEditorProps} from 'DraftEditorProps';
import type {DraftScrollPosition} from 'DraftScrollPosition';

const isIE = UserAgent.isBrowser('IE');

// IE does not support the `input` event on contentEditable, so we can't
// observe spellcheck behavior.
const allowSpellCheck = !isIE;

// Define a set of handler objects to correspond to each possible `mode`
// of editor behavior.
const handlerMap = {
  'edit': DraftEditorEditHandler,
  'composite': DraftEditorCompositionHandler,
  'drag': DraftEditorDragHandler,
  'cut': null,
  'render': null,
};

type DefaultProps = {
  blockRendererFn?: (block: ContentBlock) => ?Object;
  blockStyleFn?: (type: number) => string,
  keyBindingFn?: (e: SyntheticKeyboardEvent) => ?string,
  readOnly?: boolean,
  spellCheck?: boolean,
  stripPastedStyles?: boolean,
};

type State = {
  containerKey: number,
};

/**
 * `DraftEditor` is the root editor component. It composes a `contentEditable`
 * div, and provides a wide variety of useful function props for managing the
 * state of the editor. See `DraftEditorProps` for details.
 */
class DraftEditor
  extends React.Component<DefaultProps, DraftEditorProps, State> {
  state: State;

  static defaultProps = {
    blockRendererFn: emptyFunction.thatReturnsNull,
    blockStyleFn: emptyFunction.thatReturns(''),
    keyBindingFn: getDefaultKeyBinding,
    readOnly: false,
    spellCheck: false,
    stripPastedStyles: false,
  };

  _blockSelectEvents: boolean;
  _clipboard: ?BlockMap;
  _guardAgainstRender: boolean;
  _handler: ?Object;
  _dragCount: number;
  _editorKey: string;

  /**
   * Define proxies that can route events to the current handler.
   */
  _onBeforeInput: Function;
  _onBlur: Function;
  _onCharacterData: Function;
  _onCompositionEnd: Function;
  _onCompositionStart: Function;
  _onCopy: Function;
  _onCut: Function;
  _onDragEnd: Function;
  _onDragOver: Function;
  _onDragStart: Function;
  _onDrop: Function;
  _onInput: Function;
  _onFocus: Function;
  _onKeyDown: Function;
  _onKeyPress: Function;
  _onKeyUp: Function;
  _onMouseDown: Function;
  _onMouseUp: Function;
  _onPaste: Function;
  _onSelect: Function;

  focus: () => void;
  blur: () => void;
  setMode: (mode: DraftEditorModes) => void;
  exitCurrentMode: () => void;
  restoreEditorDOM: (scrollPosition: DraftScrollPosition) => void;
  setRenderGuard: () => void;
  removeRenderGuard: () => void;
  setClipboard: (clipboard?: BlockMap) => void;
  getClipboard: () => ?BlockMap;
  getEditorKey: () => string;
  update: (editorState: EditorState) => void;
  onDragEnter: () => void;
  onDragLeave: () => void;

  constructor(props: DraftEditorProps) {
    super(props);

    this._blockSelectEvents = false;
    this._clipboard = null;
    this._guardAgainstRender = false;
    this._handler = null;
    this._dragCount = 0;
    this._editorKey = generateRandomKey();

    this._onBeforeInput = this._buildHandler('onBeforeInput');
    this._onBlur = this._buildHandler('onBlur');
    this._onCharacterData = this._buildHandler('onCharacterData');
    this._onCompositionEnd = this._buildHandler('onCompositionEnd');
    this._onCompositionStart = this._buildHandler('onCompositionStart');
    this._onCopy = this._buildHandler('onCopy');
    this._onCut = this._buildHandler('onCut');
    this._onDragEnd = this._buildHandler('onDragEnd');
    this._onDragOver = this._buildHandler('onDragOver');
    this._onDragStart = this._buildHandler('onDragStart');
    this._onDrop = this._buildHandler('onDrop');
    this._onInput = this._buildHandler('onInput');
    this._onFocus = this._buildHandler('onFocus');
    this._onKeyDown = this._buildHandler('onKeyDown');
    this._onKeyPress = this._buildHandler('onKeyPress');
    this._onKeyUp = this._buildHandler('onKeyUp');
    this._onMouseDown = this._buildHandler('onMouseDown');
    this._onMouseUp = this._buildHandler('onMouseUp');
    this._onPaste = this._buildHandler('onPaste');
    this._onSelect = this._buildHandler('onSelect');

    // Manual binding for public and internal methods.
    this.focus = this._focus.bind(this);
    this.blur = this._blur.bind(this);
    this.setMode = this._setMode.bind(this);
    this.exitCurrentMode = this._exitCurrentMode.bind(this);
    this.restoreEditorDOM = this._restoreEditorDOM.bind(this);
    this.setRenderGuard = this._setRenderGuard.bind(this);
    this.removeRenderGuard = this._removeRenderGuard.bind(this);
    this.setClipboard = this._setClipboard.bind(this);
    this.getClipboard = this._getClipboard.bind(this);
    this.getEditorKey = () => this._editorKey;
    this.update = this._update.bind(this);
    this.onDragEnter = this._onDragEnter.bind(this);
    this.onDragLeave = this._onDragLeave.bind(this);

    // See `_restoreEditorDOM()`.
    this.state = {containerKey: 0};
  }

  /**
   * Build a method that will pass the event to the specified handler method.
   * This allows us to look up the correct handler function for the current
   * editor mode, if any has been specified.
   */
  _buildHandler(eventName: string): Function {
    return (e) => {
      if (!this.props.readOnly) {
        const method = this._handler && this._handler[eventName];
        method && method.call(this, e);
      }
    };
  }

  _renderPlaceholder(): ?ReactElement {
    const content = this.props.editorState.getCurrentContent();
    const showPlaceholder = (
      this.props.placeholder &&
      !this.props.editorState.isInCompositionMode() &&
      !content.hasText()
    );

    if (showPlaceholder) {
      return (
        <DraftEditorPlaceholder
          text={nullthrows(this.props.placeholder)}
          editorState={this.props.editorState}
          textAlignment={this.props.textAlignment}
        />
      );
    }
  }

  render(): ReactElement {
    const {readOnly, textAlignment} = this.props;
    const rootClass = cx({
      'DraftEditor/root': true,
      'DraftEditor/alignLeft': textAlignment === 'left',
      'DraftEditor/alignRight': textAlignment === 'right',
      'DraftEditor/alignCenter': textAlignment === 'center',
    });
    const hasContent = this.props.editorState.getCurrentContent().hasText();

    const contentStyle = {
      outline: 'none',
      whiteSpace: 'pre-wrap',
    };

    return (
      <div className={rootClass}>
        {this._renderPlaceholder()}
        <div
          className={cx('DraftEditor/editorContainer')}
          key={'editor' + this.state.containerKey}
          ref="editorContainer">
          <div
            aria-activedescendant={
              readOnly ? null : this.props.ariaActiveDescendantID
            }
            aria-autocomplete={readOnly ? null : this.props.ariaAutoComplete}
            aria-describedby={this.props.ariaDescribedBy}
            aria-expanded={readOnly ? null : this.props.ariaExpanded}
            aria-haspopup={readOnly ? null : this.props.ariaHasPopup}
            aria-label={this.props.ariaLabel}
            aria-owns={readOnly ? null : this.props.ariaOwneeID}
            className={cx('public/DraftEditor/content')}
            contentEditable={!readOnly}
            data-testid={this.props.webDriverTestID}
            onBeforeInput={this._onBeforeInput}
            onBlur={this._onBlur}
            onCompositionEnd={this._onCompositionEnd}
            onCompositionStart={this._onCompositionStart}
            onCopy={this._onCopy}
            onCut={this._onCut}
            onDragEnd={this._onDragEnd}
            onDragEnter={this.onDragEnter}
            onDragLeave={this.onDragLeave}
            onDragOver={this._onDragOver}
            onDragStart={this._onDragStart}
            onDrop={this._onDrop}
            onFocus={this._onFocus}
            onInput={this._onInput}
            onKeyDown={this._onKeyDown}
            onKeyPress={this._onKeyPress}
            onKeyUp={this._onKeyUp}
            onMouseUp={this._onMouseUp}
            onPaste={this._onPaste}
            onSelect={this._onSelect}
            ref="editor"
            role={readOnly ? null : (this.props.role || 'textbox')}
            spellCheck={allowSpellCheck && this.props.spellCheck}
            style={contentStyle}
            suppressContentEditableWarning
            tabIndex={this.props.tabIndex}
            title={hasContent ? null : this.props.placeholder}>
            <DraftEditorContents
              blockRendererFn={nullthrows(this.props.blockRendererFn)}
              blockStyleFn={nullthrows(this.props.blockStyleFn)}
              customStyleMap={
                {...DefaultDraftInlineStyle, ...this.props.customStyleMap}
              }
              editorKey={this._editorKey}
              editorState={this.props.editorState}
            />
          </div>
        </div>
      </div>
    );
  }

  componentDidMount(): void {
    this.setMode('edit');

    /**
     * IE has a hardcoded "feature" that attempts to convert link text into
     * anchors in contentEditable DOM. This breaks the editor's expectations of
     * the DOM, and control is lost. Disable it to make IE behave.
     * See: http://blogs.msdn.com/b/ieinternals/archive/2010/09/15/
     * ie9-beta-minor-change-list.aspx
     */
    if (isIE) {
      document.execCommand('AutoUrlDetect', false, false);
    }
  }

  /**
   * Prevent selection events from affecting the current editor state. This
   * is mostly intended to defend against IE, which fires off `selectionchange`
   * events regardless of whether the selection is set via the browser or
   * programmatically. We only care about selection events that occur because
   * of browser interaction, not re-renders and forced selections.
   */
  componentWillUpdate(): void {
    this._blockSelectEvents = true;
  }

  componentDidUpdate(): void {
    this._blockSelectEvents = false;
  }

  /**
   * Used via `this.focus()`.
   *
   * Force focus back onto the editor node.
   *
   * Forcing focus causes the browser to scroll to the top of the editor, which
   * may be undesirable when the editor is taller than the viewport. To solve
   * this, either use a specified scroll position (in cases like `cut` behavior
   * where it should be restored to a known position) or store the current
   * scroll state and put it back in place after focus has been forced.
   */
  _focus(scrollPosition?: DraftScrollPosition): void {
    const {editorState} = this.props;
    const alreadyHasFocus = editorState.getSelection().getHasFocus();
    const editorNode = ReactDOM.findDOMNode(this.refs.editor);

    const scrollParent = Style.getScrollParent(editorNode);
    const {x, y} = scrollPosition || getScrollPosition(scrollParent);

    editorNode.focus();
    if (scrollParent === window) {
      window.scrollTo(x, y);
    } else {
      Scroll.setTop(scrollParent, y);
    }

    // On Chrome and Safari, calling focus on contenteditable focuses the
    // cursor at the first character. This is something you don't expect when
    // you're clicking on an input element but not directly on a character.
    // Put the cursor back where it was before the blur.
    if (!alreadyHasFocus) {
      this.update(
        EditorState.forceSelection(
          editorState,
          editorState.getSelection()
        )
      );
    }
  }

  _blur(): void {
    ReactDOM.findDOMNode(this.refs.editor).blur();
  }

  /**
   * Used via `this.setMode(...)`.
   *
   * Set the behavior mode for the editor component. This switches the current
   * handler module to ensure that DOM events are managed appropriately for
   * the active mode.
   */
  _setMode(mode: DraftEditorModes): void {
    this._handler = handlerMap[mode];
  }

  _exitCurrentMode(): void {
    this.setMode('edit');
  }

  /**
   * Used via `this.restoreEditorDOM()`.
   *
   * Force a complete re-render of the editor based on the current EditorState.
   * This is useful when we know we are going to lose control of the DOM
   * state (cut command, IME) and we want to make sure that reconciliation
   * occurs on a version of the DOM that is synchronized with our EditorState.
   */
  _restoreEditorDOM(scrollPosition?: DraftScrollPosition): void {
    this.setState({containerKey: this.state.containerKey + 1}, () => {
      this._focus(scrollPosition);
    });
  }

  /**
   * Guard against rendering. Intended for use when we need to manually
   * reset editor contents, to ensure that no outside influences lead to
   * React reconciliation when we are in an uncertain state.
   */
  _setRenderGuard(): void {
    this._guardAgainstRender = true;
  }

  _removeRenderGuard(): void {
    this._guardAgainstRender = false;
  }

  /**
   * Used via `this.setClipboard(...)`.
   *
   * Set the clipboard state for a cut/copy event.
   */
  _setClipboard(clipboard: ?BlockMap): void {
    this._clipboard = clipboard;
  }

  /**
   * Used via `this.getClipboard()`.
   *
   * Retrieve the clipboard state for a cut/copy event.
   */
  _getClipboard(): ?BlockMap {
    return this._clipboard;
  }

  /**
   * Used via `this.update(...)`.
   *
   * Propagate a new `EditorState` object to higher-level components. This is
   * the method by which event handlers inform the `DraftEditor` component of
   * state changes. A component that composes a `DraftEditor` **must** provide
   * an `onChange` prop to receive state updates passed along from this
   * function.
   */
  _update(editorState: EditorState): void {
    this.props.onChange(editorState);
  }

  /**
   * Used in conjunction with `_onDragLeave()`, by counting the number of times
   * a dragged element enters and leaves the editor (or any of its children),
   * to determine when the dragged element absolutely leaves the editor.
   */
  _onDragEnter(): void {
    this._dragCount++;
  }

  /**
   * See `_onDragEnter()`.
   */
  _onDragLeave(): void {
    this._dragCount--;
    if (this._dragCount === 0) {
      this.exitCurrentMode();
    }
  }
}

module.exports = DraftEditor;
<commit_msg>Summary:<commit_after>/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule DraftEditor.react
 * @typechecks
 * @flow
 */

'use strict';

const DefaultDraftInlineStyle = require('DefaultDraftInlineStyle');
const DraftEditorCompositionHandler = require('DraftEditorCompositionHandler');
const DraftEditorContents = require('DraftEditorContents.react');
const DraftEditorDragHandler = require('DraftEditorDragHandler');
const DraftEditorEditHandler = require('DraftEditorEditHandler');
const DraftEditorPlaceholder = require('DraftEditorPlaceholder.react');
const EditorState = require('EditorState');
const React = require('React');
const ReactDOM = require('ReactDOM');
const Scroll = require('Scroll');
const Style = require('Style');
const UserAgent = require('UserAgent');

const cx = require('cx');
const emptyFunction = require('emptyFunction');
const generateRandomKey = require('generateRandomKey');
const getDefaultKeyBinding = require('getDefaultKeyBinding');
const nullthrows = require('nullthrows');
const getScrollPosition = require('getScrollPosition');

import type {BlockMap} from 'BlockMap';
import type ContentBlock from 'ContentBlock';
import type {DraftEditorModes} from 'DraftEditorModes';
import type {DraftEditorProps} from 'DraftEditorProps';
import type {DraftScrollPosition} from 'DraftScrollPosition';

const isIE = UserAgent.isBrowser('IE');

// IE does not support the `input` event on contentEditable, so we can't
// observe spellcheck behavior.
const allowSpellCheck = !isIE;

// Define a set of handler objects to correspond to each possible `mode`
// of editor behavior.
const handlerMap = {
  'edit': DraftEditorEditHandler,
  'composite': DraftEditorCompositionHandler,
  'drag': DraftEditorDragHandler,
  'cut': null,
  'render': null,
};

type DefaultProps = {
  blockRendererFn?: (block: ContentBlock) => ?Object;
  blockStyleFn?: (type: number) => string,
  keyBindingFn?: (e: SyntheticKeyboardEvent) => ?string,
  readOnly?: boolean,
  spellCheck?: boolean,
  stripPastedStyles?: boolean,
};

type State = {
  containerKey: number,
};

/**
 * `DraftEditor` is the root editor component. It composes a `contentEditable`
 * div, and provides a wide variety of useful function props for managing the
 * state of the editor. See `DraftEditorProps` for details.
 */
class DraftEditor
  extends React.Component<DefaultProps, DraftEditorProps, State> {
  state: State;

  static defaultProps = {
    blockRendererFn: emptyFunction.thatReturnsNull,
    blockStyleFn: emptyFunction.thatReturns(''),
    keyBindingFn: getDefaultKeyBinding,
    readOnly: false,
    spellCheck: false,
    stripPastedStyles: false,
  };

  _blockSelectEvents: boolean;
  _clipboard: ?BlockMap;
  _guardAgainstRender: boolean;
  _handler: ?Object;
  _dragCount: number;
  _editorKey: string;

  /**
   * Define proxies that can route events to the current handler.
   */
  _onBeforeInput: Function;
  _onBlur: Function;
  _onCharacterData: Function;
  _onCompositionEnd: Function;
  _onCompositionStart: Function;
  _onCopy: Function;
  _onCut: Function;
  _onDragEnd: Function;
  _onDragOver: Function;
  _onDragStart: Function;
  _onDrop: Function;
  _onInput: Function;
  _onFocus: Function;
  _onKeyDown: Function;
  _onKeyPress: Function;
  _onKeyUp: Function;
  _onMouseDown: Function;
  _onMouseUp: Function;
  _onPaste: Function;
  _onSelect: Function;

  focus: () => void;
  blur: () => void;
  setMode: (mode: DraftEditorModes) => void;
  exitCurrentMode: () => void;
  restoreEditorDOM: (scrollPosition: DraftScrollPosition) => void;
  setRenderGuard: () => void;
  removeRenderGuard: () => void;
  setClipboard: (clipboard?: BlockMap) => void;
  getClipboard: () => ?BlockMap;
  getEditorKey: () => string;
  update: (editorState: EditorState) => void;
  onDragEnter: () => void;
  onDragLeave: () => void;

  constructor(props: DraftEditorProps) {
    super(props);

    this._blockSelectEvents = false;
    this._clipboard = null;
    this._guardAgainstRender = false;
    this._handler = null;
    this._dragCount = 0;
    this._editorKey = generateRandomKey();

    this._onBeforeInput = this._buildHandler('onBeforeInput');
    this._onBlur = this._buildHandler('onBlur');
    this._onCharacterData = this._buildHandler('onCharacterData');
    this._onCompositionEnd = this._buildHandler('onCompositionEnd');
    this._onCompositionStart = this._buildHandler('onCompositionStart');
    this._onCopy = this._buildHandler('onCopy');
    this._onCut = this._buildHandler('onCut');
    this._onDragEnd = this._buildHandler('onDragEnd');
    this._onDragOver = this._buildHandler('onDragOver');
    this._onDragStart = this._buildHandler('onDragStart');
    this._onDrop = this._buildHandler('onDrop');
    this._onInput = this._buildHandler('onInput');
    this._onFocus = this._buildHandler('onFocus');
    this._onKeyDown = this._buildHandler('onKeyDown');
    this._onKeyPress = this._buildHandler('onKeyPress');
    this._onKeyUp = this._buildHandler('onKeyUp');
    this._onMouseDown = this._buildHandler('onMouseDown');
    this._onMouseUp = this._buildHandler('onMouseUp');
    this._onPaste = this._buildHandler('onPaste');
    this._onSelect = this._buildHandler('onSelect');

    // Manual binding for public and internal methods.
    this.focus = this._focus.bind(this);
    this.blur = this._blur.bind(this);
    this.setMode = this._setMode.bind(this);
    this.exitCurrentMode = this._exitCurrentMode.bind(this);
    this.restoreEditorDOM = this._restoreEditorDOM.bind(this);
    this.setRenderGuard = this._setRenderGuard.bind(this);
    this.removeRenderGuard = this._removeRenderGuard.bind(this);
    this.setClipboard = this._setClipboard.bind(this);
    this.getClipboard = this._getClipboard.bind(this);
    this.getEditorKey = () => this._editorKey;
    this.update = this._update.bind(this);
    this.onDragEnter = this._onDragEnter.bind(this);
    this.onDragLeave = this._onDragLeave.bind(this);

    // See `_restoreEditorDOM()`.
    this.state = {containerKey: 0};
  }

  /**
   * Build a method that will pass the event to the specified handler method.
   * This allows us to look up the correct handler function for the current
   * editor mode, if any has been specified.
   */
  _buildHandler(eventName: string): Function {
    return (e) => {
      if (!this.props.readOnly) {
        const method = this._handler && this._handler[eventName];
        method && method.call(this, e);
      }
    };
  }

  _renderPlaceholder(): ?ReactElement {
    const content = this.props.editorState.getCurrentContent();
    const showPlaceholder = (
      this.props.placeholder &&
      !this.props.editorState.isInCompositionMode() &&
      !content.hasText()
    );

    if (showPlaceholder) {
      return (
        <DraftEditorPlaceholder
          text={nullthrows(this.props.placeholder)}
          editorState={this.props.editorState}
          textAlignment={this.props.textAlignment}
        />
      );
    }
  }

  render(): ReactElement {
    const {readOnly, textAlignment} = this.props;
    const rootClass = cx({
      'DraftEditor/root': true,
      'DraftEditor/alignLeft': textAlignment === 'left',
      'DraftEditor/alignRight': textAlignment === 'right',
      'DraftEditor/alignCenter': textAlignment === 'center',
    });
    const hasContent = this.props.editorState.getCurrentContent().hasText();

    const contentStyle = {
      outline: 'none',
      whiteSpace: 'pre-wrap',
      wordWrap: 'break-word',
    };

    return (
      <div className={rootClass}>
        {this._renderPlaceholder()}
        <div
          className={cx('DraftEditor/editorContainer')}
          key={'editor' + this.state.containerKey}
          ref="editorContainer">
          <div
            aria-activedescendant={
              readOnly ? null : this.props.ariaActiveDescendantID
            }
            aria-autocomplete={readOnly ? null : this.props.ariaAutoComplete}
            aria-describedby={this.props.ariaDescribedBy}
            aria-expanded={readOnly ? null : this.props.ariaExpanded}
            aria-haspopup={readOnly ? null : this.props.ariaHasPopup}
            aria-label={this.props.ariaLabel}
            aria-owns={readOnly ? null : this.props.ariaOwneeID}
            className={cx('public/DraftEditor/content')}
            contentEditable={!readOnly}
            data-testid={this.props.webDriverTestID}
            onBeforeInput={this._onBeforeInput}
            onBlur={this._onBlur}
            onCompositionEnd={this._onCompositionEnd}
            onCompositionStart={this._onCompositionStart}
            onCopy={this._onCopy}
            onCut={this._onCut}
            onDragEnd={this._onDragEnd}
            onDragEnter={this.onDragEnter}
            onDragLeave={this.onDragLeave}
            onDragOver={this._onDragOver}
            onDragStart={this._onDragStart}
            onDrop={this._onDrop}
            onFocus={this._onFocus}
            onInput={this._onInput}
            onKeyDown={this._onKeyDown}
            onKeyPress={this._onKeyPress}
            onKeyUp={this._onKeyUp}
            onMouseUp={this._onMouseUp}
            onPaste={this._onPaste}
            onSelect={this._onSelect}
            ref="editor"
            role={readOnly ? null : (this.props.role || 'textbox')}
            spellCheck={allowSpellCheck && this.props.spellCheck}
            style={contentStyle}
            suppressContentEditableWarning
            tabIndex={this.props.tabIndex}
            title={hasContent ? null : this.props.placeholder}>
            <DraftEditorContents
              blockRendererFn={nullthrows(this.props.blockRendererFn)}
              blockStyleFn={nullthrows(this.props.blockStyleFn)}
              customStyleMap={
                {...DefaultDraftInlineStyle, ...this.props.customStyleMap}
              }
              editorKey={this._editorKey}
              editorState={this.props.editorState}
            />
          </div>
        </div>
      </div>
    );
  }

  componentDidMount(): void {
    this.setMode('edit');

    /**
     * IE has a hardcoded "feature" that attempts to convert link text into
     * anchors in contentEditable DOM. This breaks the editor's expectations of
     * the DOM, and control is lost. Disable it to make IE behave.
     * See: http://blogs.msdn.com/b/ieinternals/archive/2010/09/15/
     * ie9-beta-minor-change-list.aspx
     */
    if (isIE) {
      document.execCommand('AutoUrlDetect', false, false);
    }
  }

  /**
   * Prevent selection events from affecting the current editor state. This
   * is mostly intended to defend against IE, which fires off `selectionchange`
   * events regardless of whether the selection is set via the browser or
   * programmatically. We only care about selection events that occur because
   * of browser interaction, not re-renders and forced selections.
   */
  componentWillUpdate(): void {
    this._blockSelectEvents = true;
  }

  componentDidUpdate(): void {
    this._blockSelectEvents = false;
  }

  /**
   * Used via `this.focus()`.
   *
   * Force focus back onto the editor node.
   *
   * Forcing focus causes the browser to scroll to the top of the editor, which
   * may be undesirable when the editor is taller than the viewport. To solve
   * this, either use a specified scroll position (in cases like `cut` behavior
   * where it should be restored to a known position) or store the current
   * scroll state and put it back in place after focus has been forced.
   */
  _focus(scrollPosition?: DraftScrollPosition): void {
    const {editorState} = this.props;
    const alreadyHasFocus = editorState.getSelection().getHasFocus();
    const editorNode = ReactDOM.findDOMNode(this.refs.editor);

    const scrollParent = Style.getScrollParent(editorNode);
    const {x, y} = scrollPosition || getScrollPosition(scrollParent);

    editorNode.focus();
    if (scrollParent === window) {
      window.scrollTo(x, y);
    } else {
      Scroll.setTop(scrollParent, y);
    }

    // On Chrome and Safari, calling focus on contenteditable focuses the
    // cursor at the first character. This is something you don't expect when
    // you're clicking on an input element but not directly on a character.
    // Put the cursor back where it was before the blur.
    if (!alreadyHasFocus) {
      this.update(
        EditorState.forceSelection(
          editorState,
          editorState.getSelection()
        )
      );
    }
  }

  _blur(): void {
    ReactDOM.findDOMNode(this.refs.editor).blur();
  }

  /**
   * Used via `this.setMode(...)`.
   *
   * Set the behavior mode for the editor component. This switches the current
   * handler module to ensure that DOM events are managed appropriately for
   * the active mode.
   */
  _setMode(mode: DraftEditorModes): void {
    this._handler = handlerMap[mode];
  }

  _exitCurrentMode(): void {
    this.setMode('edit');
  }

  /**
   * Used via `this.restoreEditorDOM()`.
   *
   * Force a complete re-render of the editor based on the current EditorState.
   * This is useful when we know we are going to lose control of the DOM
   * state (cut command, IME) and we want to make sure that reconciliation
   * occurs on a version of the DOM that is synchronized with our EditorState.
   */
  _restoreEditorDOM(scrollPosition?: DraftScrollPosition): void {
    this.setState({containerKey: this.state.containerKey + 1}, () => {
      this._focus(scrollPosition);
    });
  }

  /**
   * Guard against rendering. Intended for use when we need to manually
   * reset editor contents, to ensure that no outside influences lead to
   * React reconciliation when we are in an uncertain state.
   */
  _setRenderGuard(): void {
    this._guardAgainstRender = true;
  }

  _removeRenderGuard(): void {
    this._guardAgainstRender = false;
  }

  /**
   * Used via `this.setClipboard(...)`.
   *
   * Set the clipboard state for a cut/copy event.
   */
  _setClipboard(clipboard: ?BlockMap): void {
    this._clipboard = clipboard;
  }

  /**
   * Used via `this.getClipboard()`.
   *
   * Retrieve the clipboard state for a cut/copy event.
   */
  _getClipboard(): ?BlockMap {
    return this._clipboard;
  }

  /**
   * Used via `this.update(...)`.
   *
   * Propagate a new `EditorState` object to higher-level components. This is
   * the method by which event handlers inform the `DraftEditor` component of
   * state changes. A component that composes a `DraftEditor` **must** provide
   * an `onChange` prop to receive state updates passed along from this
   * function.
   */
  _update(editorState: EditorState): void {
    this.props.onChange(editorState);
  }

  /**
   * Used in conjunction with `_onDragLeave()`, by counting the number of times
   * a dragged element enters and leaves the editor (or any of its children),
   * to determine when the dragged element absolutely leaves the editor.
   */
  _onDragEnter(): void {
    this._dragCount++;
  }

  /**
   * See `_onDragEnter()`.
   */
  _onDragLeave(): void {
    this._dragCount--;
    if (this._dragCount === 0) {
      this.exitCurrentMode();
    }
  }
}

module.exports = DraftEditor;
<|endoftext|>


<commit_before>/***
 * This is a "wrapper" of sorts, for the Khan Academy API.
 * Anything relating to the Khan Academy API should be placed in this file.
***/
/* Function wrapper to create KA_API */
window.KA_API = (function() {
    /* jQuery is required for [most] all of these. So if it's object is not found, exit the function. */
    if (!window.jQuery) {
        console.log("Needs jQuery");
        return;
    }

    /* Everything from this namespace will be placed in the object that is returned. */
    return {
        /* Misc data */
        misc: {
            allData: 400000
        },
        /* Khan Academy API Urls */
        urls: {
            /* For getting contests: */
            spotlight: "https://www.khanacademy.org/api/internal/scratchpads/top?casing=camel&topic_id=xffde7c31&sort=4&limit=40000&page=0&lang=en&_=1436581332879",
            /***
             * KA_API.urls.spinoffs()
             * Builds up, and returns, an API url that can be used to fetch spinoffs for a scratchpad.
             * @author Gigabyte Giant (2015)
             * @param {String} programID: The Khan Academy Scratchpad ID of the program that we want to load spinoffs from.
             * @returns {String} apiUrl: An API URL that can be used to fetch the spinoffs for a scratchpad.
            ***/
            spinoffs: function(programID) {
                return "https://www.khanacademy.org/api/internal/scratchpads/{PROGRAM}/top-forks?casing=camel&sort=2&limit=300000&page=0&lang=en".replace("{PROGRAM}", programID);
            },
            /***
             * KA_API.urls.scratchpadInfo()
             * Builds up, and returns an API url that can be used to fetch information about a scratchpad.
             * @author Gigabyte Giant (2015)
             * @param {String} scratchpadId: The Khan Academy Scratchpad ID of the program that we want to load scratchpad info from.
             * @returns {String} apiUrl: An API URL that can be used to fetch the info for a scratchpad.
            ***/
            scratchpadInfo: function(scratchpadId) {
                return "https://www.khanacademy.org/api/labs/scratchpads/{SCRATCHPAD}".replace("{SCRATCHPAD}", scratchpadId);
            }
        },
        /***
         * getContestEntries()
         * Fetches all the entries for a contest.
         * @author Gigabyte Giant, Noble Mushtak (2015)
         * @param {String} contestId: The Khan Academy Scratchpad ID of the contest that we want to load contest entries for.
         * @param {Function} callback(entries): The callback function to invoke once the contests have been loaded from Khan Academy
        ***/
        getContestEntries: function(contestID, callback) {
            /* Declare an empty object that'll be used to store all of the "entries" (spinoffs) that we find. */
            var entries = {};

            /* Send AJAX request for getting all the entries to the desired contest */
            $.ajax({
                type: 'GET',
                url: this.urls.spinoffs(contestID),
                async: true,
                complete: function(apiResponse) {
                    /* When the AJAX request finishes */
                    /* allPrograms is a list of all of the contest entries */
                    var allPrograms = apiResponse.responseJSON.scratchpads;

                    /* Loop through allPrograms */
                    for (var i = 0; i < allPrograms.length; i++) {
                        /* Program ID for this spin-off */
                        var id = allPrograms[i].url.split("/")[5];

                        /* Add JSON object for this spin-off into entries */
                        entries[id] = {
                            /* Program ID */
                            id: id,
                            /* Program Title */
                            name: allPrograms[i].translatedTitle,
                            /* Program Image */
                            thumb: allPrograms[i].thumb,
                            /* Program Scores */
                            scores: {
                                rubric: {
                                    "Level": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "Creativity": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "Clean_Code": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "Overall": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "judgesWhoVoted": [ ]
                                }
                            }
                        };
                    }
                    /* Finally, pass entries into callback */
                    callback(entries);
                }
            });
        },
        /***
         * numberOfEntriesInContest()
         * Calculates the number of entries in a contest.
         * @author Gigabyte Giant (2015)
         * @param {String} contest: The Khan Academy Scratchpad ID of the contest that we want to get the count of entries for.
         * @param {Function} callback(entryCount): The number of entries that this contest has.
        ***/
        numberOfEntriesInContest: function(contest, callback) {
            var numOfEntries = 0;

            $.ajax({
                type: 'GET',
                url: this.urls.spinoffs(contest),
                async: true,
                complete: function(apiResponse) {
                    callback(apiResponse.responseJSON.scratchpads.length);
                }
            });
        },
        /***
         * getContests()
         * Fetches all spotlight programs by Pamela, that contain "Contest" in the title. (This function is run by the KACJS-Angus bot now)
         * @author Gigabyte Giant (2015)
         * @param {Function} callback: The callback function to invoke once we've loaded all of the contest programs.
        ***/
        getContests: function(callback) {
            /* This Bool is true iff the first AJAX request has finished. */
            var apiQueryDone = false;
            /* Any contests that we find, will be put into this object. (We'll also pass this object into callback.) */
            var allContests = {};

            /* Send AJAX request to get all the contests from Khan Academy */
            $.ajax({
                type: 'GET',
                url: this.urls.spotlight,
                async: true,
                complete: function(apiResponse) {
                    /* When the AJAX request finishes */
                    /* allPrograms is a list of all of the contest entries */
                    var allPrograms = apiResponse.responseJSON.scratchpads;

                    var getContestsFunctions = {
                        "fetchContest": function(programID, scratchpad, contests) {
                            $.ajax({
                                type: 'GET',
                                url: KA_API.urls.scratchpadInfo(programID),
                                async: true,
                                complete: function(scratchpadData) {
                                    /* Add in a JSON object for this contest into contests */
                                    contests[programID] = {
                                        /* Program ID */
                                        id: programID,
                                        /* Program Title */
                                        name: scratchpad.translatedTitle,
                                        /* Program Icon */
                                        img: scratchpad.thumb,
                                        /* Contest Description */
                                        desc: scratchpadData.responseJSON.description
                                    };

                                    /* Fetch the contest entries for this contest and when done, set the entries property within the above JSON object. */
                                    window.KA_API.getContestEntries(contests[programID].id, function(entries) {
                                        contests[programID].entries = entries;
                                    });

                                    window.KA_API.numberOfEntriesInContest(contests[programID].id, function(count) {
                                        contests[programID].entryCount = count;
                                    });
                                }
                            });
                        }
                    };

                    /* Loop through allPrograms */
                    for (var i = 0; i < allPrograms.length; i++) {
                        /* For now, let's only accept contests from pamela. Also, all contests must have "Contest" in their title. */
                        if (allPrograms[i].authorNickname.match("pamela") !== null && allPrograms[i].translatedTitle.match("Contest") !== null) {
                            var programID = allPrograms[i].url.split("/")[5];
                            /* Make an empty object in allContests to alert the setTimeout() function below that such an object has yet to be set. */
                            allContests[programID] = {};

                            getContestsFunctions.fetchContest(programID, allPrograms[i], allContests);

                            /* Put this in a function wrapper so the parameters will be saved. */
                            // (function(programID, scratchpad, contests) {
                            //     $.ajax({
                            //         type: 'GET',
                            //         url: KA_API.urls.scratchpadInfo(programID),
                            //         async: true,
                            //         complete: function(scratchpadData) {
                            //             /* Add in a JSON object for this contest into contests */
                            //             contests[programID] = {
                            //                 /* Program ID */
                            //                 id: programID,
                            //                 /* Program Title */
                            //                 name: scratchpad.translatedTitle,
                            //                 /* Program Icon */
                            //                 img: scratchpad.thumb,
                            //                 /* Contest Description */
                            //                 desc: scratchpadData.responseJSON.description
                            //             };
                            //
                            //             /* Fetch the contest entries for this contest and when done, set the entries property within the above JSON object. */
                            //             window.KA_API.getContestEntries(contests[programID].id, function(entries) {
                            //                 contests[programID].entries = entries;
                            //             });
                            //
                            //             window.KA_API.numberOfEntriesInContest(contests[programID].id, function(count) {
                            //                 contests[programID].entryCount = count;
                            //             });
                            //         }
                            //     });
                            // })(programID, allPrograms[i], allContests);
                            /* Pass in the parameters to this function as above. */
                        }
                    }
                    /* Once we're done with the AJAX request, set apiQueryDone to true. */
                    apiQueryDone = true;
                }
            });

            /* Check to see if we're done every second; if we are, invoke the callback function. (Idea from @noble-mushtak) */
            var finishTimeout = setInterval(function() {
                if (apiQueryDone) {
                    /* If the first AJAX request has finished, make sure all of the other AJAX requests have finished. If we find a contest without the entries property, we know their AJAX request has not finished, so we return. */
                    for (var i in allContests) {
                        /* Wrap the body of a for-in loop with an if-statement, to filter unwanted properties. */
                        /* More info: https://jslinterrors.com/the-body-of-a-for-in-should-be-wrapped-in-an-if-statement */
                        if (allContests.hasOwnProperty(i)) {
                            if (!allContests[i].hasOwnProperty("entries") || !allContests[i].hasOwnProperty("entryCount")){
                                return;
                            }
                        }
                    }

                    /* At this point, we have made sure the request has finished, so we make a log in the console, stop looping this asynchronous function, and finally pass contests into callback. */
                    console.log("getContests() finished!");
                    /* Make sure clearTimeout() is called first. We don't know how long callback will take and if callback takes more than a second, then callback will be called multiple times. */
                    clearInterval(finishTimeout);
                    callback(allContests);
                }
            }, 1000);
        }
    };
})();
<commit_msg>Unique variable names in all loops<commit_after>/***
 * This is a "wrapper" of sorts, for the Khan Academy API.
 * Anything relating to the Khan Academy API should be placed in this file.
***/
/* Function wrapper to create KA_API */
window.KA_API = (function() {
    /* jQuery is required for [most] all of these. So if it's object is not found, exit the function. */
    if (!window.jQuery) {
        console.log("Needs jQuery");
        return;
    }

    /* Everything from this namespace will be placed in the object that is returned. */
    return {
        /* Misc data */
        misc: {
            allData: 400000
        },
        /* Khan Academy API Urls */
        urls: {
            /* For getting contests: */
            spotlight: "https://www.khanacademy.org/api/internal/scratchpads/top?casing=camel&topic_id=xffde7c31&sort=4&limit=40000&page=0&lang=en&_=1436581332879",
            /***
             * KA_API.urls.spinoffs()
             * Builds up, and returns, an API url that can be used to fetch spinoffs for a scratchpad.
             * @author Gigabyte Giant (2015)
             * @param {String} programID: The Khan Academy Scratchpad ID of the program that we want to load spinoffs from.
             * @returns {String} apiUrl: An API URL that can be used to fetch the spinoffs for a scratchpad.
            ***/
            spinoffs: function(programID) {
                return "https://www.khanacademy.org/api/internal/scratchpads/{PROGRAM}/top-forks?casing=camel&sort=2&limit=300000&page=0&lang=en".replace("{PROGRAM}", programID);
            },
            /***
             * KA_API.urls.scratchpadInfo()
             * Builds up, and returns an API url that can be used to fetch information about a scratchpad.
             * @author Gigabyte Giant (2015)
             * @param {String} scratchpadId: The Khan Academy Scratchpad ID of the program that we want to load scratchpad info from.
             * @returns {String} apiUrl: An API URL that can be used to fetch the info for a scratchpad.
            ***/
            scratchpadInfo: function(scratchpadId) {
                return "https://www.khanacademy.org/api/labs/scratchpads/{SCRATCHPAD}".replace("{SCRATCHPAD}", scratchpadId);
            }
        },
        /***
         * getContestEntries()
         * Fetches all the entries for a contest.
         * @author Gigabyte Giant, Noble Mushtak (2015)
         * @param {String} contestId: The Khan Academy Scratchpad ID of the contest that we want to load contest entries for.
         * @param {Function} callback(entries): The callback function to invoke once the contests have been loaded from Khan Academy
        ***/
        getContestEntries: function(contestID, callback) {
            /* Declare an empty object that'll be used to store all of the "entries" (spinoffs) that we find. */
            var entries = {};

            /* Send AJAX request for getting all the entries to the desired contest */
            $.ajax({
                type: 'GET',
                url: this.urls.spinoffs(contestID),
                async: true,
                complete: function(apiResponse) {
                    /* When the AJAX request finishes */
                    /* allPrograms is a list of all of the contest entries */
                    var allPrograms = apiResponse.responseJSON.scratchpads;

                    /* Loop through allPrograms */
                    for (var cepInd = 0; cepInd < allPrograms.length; cepInd++) {
                        /* Program ID for this spin-off */
                        var id = allPrograms[cepInd].url.split("/")[5];

                        /* Add JSON object for this spin-off into entries */
                        entries[id] = {
                            /* Program ID */
                            id: id,
                            /* Program Title */
                            name: allPrograms[cepInd].translatedTitle,
                            /* Program Image */
                            thumb: allPrograms[cepInd].thumb,
                            /* Program Scores */
                            scores: {
                                rubric: {
                                    "Level": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "Creativity": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "Clean_Code": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "Overall": {
                                        "rough": 1,
                                        "avg": 1
                                    },
                                    "judgesWhoVoted": [ ]
                                }
                            }
                        };
                    }
                    /* Finally, pass entries into callback */
                    callback(entries);
                }
            });
        },
        /***
         * numberOfEntriesInContest()
         * Calculates the number of entries in a contest.
         * @author Gigabyte Giant (2015)
         * @param {String} contest: The Khan Academy Scratchpad ID of the contest that we want to get the count of entries for.
         * @param {Function} callback(entryCount): The number of entries that this contest has.
        ***/
        numberOfEntriesInContest: function(contest, callback) {
            var numOfEntries = 0;

            $.ajax({
                type: 'GET',
                url: this.urls.spinoffs(contest),
                async: true,
                complete: function(apiResponse) {
                    callback(apiResponse.responseJSON.scratchpads.length);
                }
            });
        },
        /***
         * getContests()
         * Fetches all spotlight programs by Pamela, that contain "Contest" in the title. (This function is run by the KACJS-Angus bot now)
         * @author Gigabyte Giant (2015)
         * @param {Function} callback: The callback function to invoke once we've loaded all of the contest programs.
        ***/
        getContests: function(callback) {
            /* This Bool is true iff the first AJAX request has finished. */
            var apiQueryDone = false;
            /* Any contests that we find, will be put into this object. (We'll also pass this object into callback.) */
            var allContests = {};

            /* Send AJAX request to get all the contests from Khan Academy */
            $.ajax({
                type: 'GET',
                url: this.urls.spotlight,
                async: true,
                complete: function(apiResponse) {
                    /* When the AJAX request finishes */
                    /* allPrograms is a list of all of the contest entries */
                    var allPrograms = apiResponse.responseJSON.scratchpads;

                    var getContestsFunctions = {
                        "fetchContest": function(programID, scratchpad, contests) {
                            $.ajax({
                                type: 'GET',
                                url: KA_API.urls.scratchpadInfo(programID),
                                async: true,
                                complete: function(scratchpadData) {
                                    /* Add in a JSON object for this contest into contests */
                                    contests[programID] = {
                                        /* Program ID */
                                        id: programID,
                                        /* Program Title */
                                        name: scratchpad.translatedTitle,
                                        /* Program Icon */
                                        img: scratchpad.thumb,
                                        /* Contest Description */
                                        desc: scratchpadData.responseJSON.description
                                    };

                                    /* Fetch the contest entries for this contest and when done, set the entries property within the above JSON object. */
                                    window.KA_API.getContestEntries(contests[programID].id, function(entries) {
                                        contests[programID].entries = entries;
                                    });

                                    window.KA_API.numberOfEntriesInContest(contests[programID].id, function(count) {
                                        contests[programID].entryCount = count;
                                    });
                                }
                            });
                        }
                    };

                    /* Loop through allPrograms */
                    for (var ccpInd = 0; ccpInd < allPrograms.length; ccpInd++) {
                        /* For now, let's only accept contests from pamela. Also, all contests must have "Contest" in their title. */
                        if (allPrograms[ccpInd].authorNickname.match("pamela") !== null &&
                            allPrograms[ccpInd].translatedTitle.match("Contest") !== null) {
                            var programID = allPrograms[ccpInd].url.split("/")[5];
                            /* Make an empty object in allContests to alert the setTimeout() function below that such an object has yet to be set. */
                            allContests[programID] = {};

                            getContestsFunctions.fetchContest(programID, allPrograms[ccpInd], allContests);

                            /* Put this in a function wrapper so the parameters will be saved. */
                            // (function(programID, scratchpad, contests) {
                            //     $.ajax({
                            //         type: 'GET',
                            //         url: KA_API.urls.scratchpadInfo(programID),
                            //         async: true,
                            //         complete: function(scratchpadData) {
                            //             /* Add in a JSON object for this contest into contests */
                            //             contests[programID] = {
                            //                 /* Program ID */
                            //                 id: programID,
                            //                 /* Program Title */
                            //                 name: scratchpad.translatedTitle,
                            //                 /* Program Icon */
                            //                 img: scratchpad.thumb,
                            //                 /* Contest Description */
                            //                 desc: scratchpadData.responseJSON.description
                            //             };
                            //
                            //             /* Fetch the contest entries for this contest and when done, set the entries property within the above JSON object. */
                            //             window.KA_API.getContestEntries(contests[programID].id, function(entries) {
                            //                 contests[programID].entries = entries;
                            //             });
                            //
                            //             window.KA_API.numberOfEntriesInContest(contests[programID].id, function(count) {
                            //                 contests[programID].entryCount = count;
                            //             });
                            //         }
                            //     });
                            // })(programID, allPrograms[i], allContests);
                            /* Pass in the parameters to this function as above. */
                        }
                    }
                    /* Once we're done with the AJAX request, set apiQueryDone to true. */
                    apiQueryDone = true;
                }
            });

            /* Check to see if we're done every second; if we are, invoke the callback function. (Idea from @noble-mushtak) */
            var finishTimeout = setInterval(function() {
                if (apiQueryDone) {
                    /* If the first AJAX request has finished, make sure all of the other AJAX requests have finished. If we find a contest without the entries property, we know their AJAX request has not finished, so we return. */
                    for (var c in allContests) {
                        /* Wrap the body of a for-in loop with an if-statement, to filter unwanted properties. */
                        /* More info: https://jslinterrors.com/the-body-of-a-for-in-should-be-wrapped-in-an-if-statement */
                        if (allContests.hasOwnProperty(c)) {
                            if (!allContests[c].hasOwnProperty("entries") || !allContests[c].hasOwnProperty("entryCount")){
                                return;
                            }
                        }
                    }

                    /* At this point, we have made sure the request has finished, so we make a log in the console, stop looping this asynchronous function, and finally pass contests into callback. */
                    console.log("getContests() finished!");
                    /* Make sure clearTimeout() is called first. We don't know how long callback will take and if callback takes more than a second, then callback will be called multiple times. */
                    clearInterval(finishTimeout);
                    callback(allContests);
                }
            }, 1000);
        }
    };
})();
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
'use strict';

var Cluster = require('./cluster.js');
var logger = require('winston');
var parseArgs = require('./parse_args.js');
var express = require('express');
var PythonShell = require('python-shell');

var LEADER_KEY = 'LEADER';

function App(ringpop) {
    this.ringpop = ringpop;
    this.currentLeader = null;
    this.isLeader = false;
}

App.prototype.chooseLeader = function chooseLeader() {
    var newLeader = this.ringpop.lookup(LEADER_KEY);
    if (!this.currentLeader || newLeader !== this.currentLeader) {
        logger.info(this.ringpop.whoami() + ' says new leader is ' + newLeader);

        // This app was not a leader, but now is.
        if (!this.isLeader && newLeader === this.ringpop.whoami()) {
            // Subscribe
            this.isLeader = true;
        }

        // This app was a leader, but now is not.
        if (this.isLeader && newLeader !== this.ringpop.whoami()) {
            // Unsubscribe
            this.isLeader = false;
        }

        this.currentLeader = newLeader;
    }
};

if (require.main === module) {
    var cluster = new Cluster(parseArgs());

    // Launch cluster. If successful, get individual Ringpop instances (nodes)
    // back.
    cluster.launch(function onLaunch(err, ringpops) {
        if (err) {
            logger.error('error:' + err.message);
            process.exit(1);
        }

        // Create an App wrapper around each Ringpop instance. Choose a
        // leader upon instantiation and when a ring change is detected.
        ringpops.forEach(function each(ringpop) {
            var app = new App(ringpop);
            app.chooseLeader();
            ringpop.on('ringChanged', app.chooseLeader.bind(app));
        });

        // These HTTP servers will act as the front-end
        // for the Ringpop cluster.
        ringpops.forEach(function each(ringpop, index) {
            var pyshell = new PythonShell('get_id.py');
            var http = express();

            // Define a single HTTP endpoint that 'handles' or forwards
            http.get('/loc/', function onReq(req, res) {
                var lat = req.params.lat;
                var log = req.params.log;

                if (ringpop.handleOrProxy(key, req, res)) {
                    pyshell.send({'lat':lat, 'log':log});
                    pyshell.on('message', function (message) {
                      // received a message sent from the Python script (a simple "print" statement)
                      console.log('Ringpop ' + ringpop.whoami() + ' handled ' + key);
                      console.log(message);
                    });
                    res.end();
                } else {
                    console.log('Ringpop ' + ringpop.whoami() +
                        ' forwarded ' + key);
                }
            });

            var port = cluster.basePort * 2 + index; // HTTP will need its own port
            http.listen(port, function onListen() {
                console.log('HTTP is listening on ' + port);
            });
        });
    });
}
<commit_msg>--amend<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
'use strict';

var Cluster = require('./cluster.js');
var logger = require('winston');
var parseArgs = require('./parse_args.js');
var express = require('express');
var PythonShell = require('python-shell');

var LEADER_KEY = 'LEADER';

function App(ringpop) {
    this.ringpop = ringpop;
    this.currentLeader = null;
    this.isLeader = false;
}

App.prototype.chooseLeader = function chooseLeader() {
    var newLeader = this.ringpop.lookup(LEADER_KEY);
    if (!this.currentLeader || newLeader !== this.currentLeader) {
        logger.info(this.ringpop.whoami() + ' says new leader is ' + newLeader);

        // This app was not a leader, but now is.
        if (!this.isLeader && newLeader === this.ringpop.whoami()) {
            // Subscribe
            this.isLeader = true;
        }

        // This app was a leader, but now is not.
        if (this.isLeader && newLeader !== this.ringpop.whoami()) {
            // Unsubscribe
            this.isLeader = false;
        }

        this.currentLeader = newLeader;
    }
};

if (require.main === module) {
    var cluster = new Cluster(parseArgs());

    // Launch cluster. If successful, get individual Ringpop instances (nodes)
    // back.
    cluster.launch(function onLaunch(err, ringpops) {
        if (err) {
            logger.error('error:' + err.message);
            process.exit(1);
        }

        // Create an App wrapper around each Ringpop instance. Choose a
        // leader upon instantiation and when a ring change is detected.
        ringpops.forEach(function each(ringpop) {
            var app = new App(ringpop);
            app.chooseLeader();
            ringpop.on('ringChanged', app.chooseLeader.bind(app));
        });

        // These HTTP servers will act as the front-end
        // for the Ringpop cluster.
        ringpops.forEach(function each(ringpop, index) {
            var pyshell = new PythonShell('../../../uber/get_id.py');
            var http = express();

            // Define a single HTTP endpoint that 'handles' or forwards
            http.get('/loc/', function onReq(req, res) {
                var lat = req.params.lat;
                var log = req.params.log;

                if (ringpop.handleOrProxy(key, req, res)) {
                    pyshell.send({'lat':lat, 'log':log});
                    pyshell.on('message', function (message) {
                      // received a message sent from the Python script (a simple "print" statement)
                      console.log('Ringpop ' + ringpop.whoami() + ' handled ' + key);
                      console.log(message);
                    });
                    res.end();
                } else {
                    console.log('Ringpop ' + ringpop.whoami() +
                        ' forwarded ' + key);
                }
            });

            var port = cluster.basePort * 2 + index; // HTTP will need its own port
            http.listen(port, function onListen() {
                console.log('HTTP is listening on ' + port);
            });
        });
    });
}
<|endoftext|>