<commit_before>/* Isso – Ich schrei sonst!
 */
define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n", "app/lib", "app/globals"],
    function($, utils, config, api, jade, i18n, lib, globals) {

    "use strict";

    var Postbox = function(parent) {

        var el = $.htmlify(jade.render("postbox", {
            "author":  JSON.parse(localStorage.getItem("author")),
            "email":   JSON.parse(localStorage.getItem("email")),
            "website": JSON.parse(localStorage.getItem("website"))
        }));

        // callback on success (e.g. to toggle the reply button)
        el.onsuccess = function() {};

        el.validate = function() {
            if (utils.text($(".textarea", this).innerHTML).length < 3 ||
                $(".textarea", this).classList.contains("placeholder"))
            {
                $(".textarea", this).focus();
                return false;
            }
            return true;
        };

        // submit form, initialize optional fields with `null` and reset form.
        // If replied to a comment, remove form completely.
        $("[type=submit]", el).on("click", function() {
            if (! el.validate()) {
                return;
            }

            var author = $("[name=author]", el).value || null,
                email = $("[name=email]", el).value || null,
                website = $("[name=website]", el).value || null;

            localStorage.setItem("author", JSON.stringify(author));
            localStorage.setItem("email", JSON.stringify(email));
            localStorage.setItem("website", JSON.stringify(website));

            api.create($("#isso-thread").getAttribute("data-isso-id"), {
                author: author, email: email, website: website,
                text: utils.text($(".textarea", el).innerHTML),
                parent: parent || null
            }).then(function(comment) {
                $(".textarea", el).innerHTML = "";
                $(".textarea", el).blur();
                insert(comment, true);

                if (parent !== null) {
                    el.onsuccess();
                }
            });
        });

        lib.editorify($(".textarea", el));

        return el;
    };

    var insert_loader = function(comment, lastcreated) {
        var entrypoint;
        if (comment.id === null) {
            entrypoint = $("#isso-root");
            comment.name = 'null';
        } else {
            entrypoint = $("#isso-" + comment.id + " > .text-wrapper > .isso-follow-up");
            comment.name = comment.id;
        }
        var el = $.htmlify(jade.render("comment-loader", {"comment": comment}));

        entrypoint.append(el);

        $("a.load_hidden", el).on("click", function() {
            el.remove();
            api.fetch($("#isso-thread").getAttribute("data-isso-id"),
                config["reveal-on-click"], config["max-comments-nested"],
                comment.id,
                lastcreated).then(
                function(rv) {
                    if (rv.total_replies === 0) {
                        return;
                    }

                    var lastcreated = 0;
                    rv.replies.forEach(function(commentObject) {
                        insert(commentObject, false);
                        if(commentObject.created > lastcreated) {
                            lastcreated = commentObject.created;
                        }
                    });

                    if(rv.hidden_replies > 0) {
                        insert_loader(rv, lastcreated);
                    }
                },
                function(err) {
                    console.log(err);
                });
        });
    };

    var insert = function(comment, scrollIntoView) {
        var el = $.htmlify(jade.render("comment", {"comment": comment}));

        // update datetime every 60 seconds
        var refresh = function() {
            $(".permalink > time", el).textContent = utils.ago(
                globals.offset.localTime(), new Date(parseInt(comment.created, 10) * 1000));
            setTimeout(refresh, 60*1000);
        };

        // run once to activate
        refresh();

        if (config["avatar"]) {
            $("div.avatar > svg", el).replace(lib.identicons.generate(comment.hash, 4, 48));
        }

        var entrypoint;
        if (comment.parent === null) {
            entrypoint = $("#isso-root");
        } else {
            entrypoint = $("#isso-" + comment.parent + " > .text-wrapper > .isso-follow-up");
        }

        entrypoint.append(el);

        if (scrollIntoView) {
            el.scrollIntoView();
        }

        var footer = $("#isso-" + comment.id + " > .text-wrapper > .isso-comment-footer"),
            header = $("#isso-" + comment.id + " > .text-wrapper > .isso-comment-header"),
            text   = $("#isso-" + comment.id + " > .text-wrapper > .text");

        var form = null;  // XXX: probably a good place for a closure
        $("a.reply", footer).toggle("click",
            function(toggler) {
                form = footer.insertAfter(new Postbox(comment.parent === null ? comment.id : comment.parent));
                form.onsuccess = function() { toggler.next(); };
                $(".textarea", form).focus();
                $("a.reply", footer).textContent = i18n.translate("comment-close");
            },
            function() {
                form.remove();
                $("a.reply", footer).textContent = i18n.translate("comment-reply");
            }
        );

        if (config.vote) {
            // update vote counter, but hide if votes sum to 0
            var votes = function (value) {
                var span = $("span.votes", footer);
                if (span === null && value !== 0) {
                    footer.prepend($.new("span.votes", value));
                } else {
                    if (value === 0) {
                        span.remove();
                    } else {
                        span.textContent = value;
                    }
                }
            };

            $("a.upvote", footer).on("click", function () {
                api.like(comment.id).then(function (rv) {
                    votes(rv.likes - rv.dislikes);
                });
            });

            $("a.downvote", footer).on("click", function () {
                api.dislike(comment.id).then(function (rv) {
                    votes(rv.likes - rv.dislikes);
                });
            });
        }

        $("a.edit", footer).toggle("click",
            function(toggler) {
                var edit = $("a.edit", footer);
                var avatar = $(".avatar", el, false)[0];

                edit.textContent = i18n.translate("comment-save");
                edit.insertAfter($.new("a.cancel", i18n.translate("comment-cancel"))).on("click", function() {
                    toggler.canceled = true;
                    toggler.next();
                });

                toggler.canceled = false;
                api.view(comment.id, 1).then(function(rv) {
                    var textarea = lib.editorify($.new("div.textarea"));

                    textarea.innerHTML = utils.detext(rv.text);
                    textarea.focus();

                    text.classList.remove("text");
                    text.classList.add("textarea-wrapper");

                    text.textContent = "";
                    text.append(textarea);
                });

                if (avatar !== null) {
                    avatar.hide();
                }
            },
            function(toggler) {
                var textarea = $(".textarea", text);
                var avatar = $(".avatar", el, false)[0];

                if (! toggler.canceled && textarea !== null) {
                    if (utils.text(textarea.innerHTML).length < 3) {
                        textarea.focus();
                        toggler.wait();
                        return;
                    } else {
                        api.modify(comment.id, {"text": utils.text(textarea.innerHTML)}).then(function(rv) {
                            text.innerHTML = rv.text;
                            comment.text = rv.text;
                        });
                    }
                } else {
                    text.innerHTML = comment.text;
                }

                text.classList.remove("textarea-wrapper");
                text.classList.add("text");

                if (avatar !== null) {
                    avatar.show();
                }

                $("a.cancel", footer).remove();
                $("a.edit", footer).textContent = i18n.translate("comment-edit");
            }
        );

        $("a.delete", footer).toggle("click",
            function(toggler) {
                var del = $("a.delete", footer);
                var state = ! toggler.state;

                del.textContent = i18n.translate("comment-confirm");
                del.on("mouseout", function() {
                    del.textContent = i18n.translate("comment-delete");
                    toggler.state = state;
                    del.onmouseout = null;
                });
            },
            function() {
                var del = $("a.delete", footer);
                api.remove(comment.id).then(function(rv) {
                    if (rv) {
                        el.remove();
                    } else {
                        $("span.note", header).textContent = i18n.translate("comment-deleted");
                        text.innerHTML = "<p> </p>";
                        $("a.edit", footer).remove();
                        $("a.delete", footer).remove();
                    }
                    del.textContent = i18n.translate("comment-delete");
                });
            }
        );

        // remove edit and delete buttons when cookie is gone
        var clear = function(button) {
            if (! utils.cookie("isso-" + comment.id)) {
                if ($(button, footer) !== null) {
                    $(button, footer).remove();
                }
            } else {
                setTimeout(function() { clear(button); }, 15*1000);
            }
        };

        clear("a.edit");
        clear("a.delete");

        // show direct reply to own comment when cookie is max aged
        var show = function(el) {
            if (utils.cookie("isso-" + comment.id)) {
                setTimeout(function() { show(el); }, 15*1000);
            } else {
                footer.append(el);
            }
        };

        if (! config["reply-to-self"] && utils.cookie("isso-" + comment.id)) {
            show($("a.reply", footer).detach());
        }

        if(comment.hasOwnProperty('replies')) {
            var lastcreated = 0;
            comment.replies.forEach(function(replyObject) {
                insert(replyObject, false);
                if(replyObject.created > lastcreated) {
                    lastcreated = replyObject.created;
                }

            });
            if(comment.hidden_replies > 0) {
                insert_loader(comment, lastcreated);
            }

        }

    };

    return {
        insert: insert,
        insert_loader: insert_loader,
        Postbox: Postbox
    };
});
<commit_msg>bug fixed: was trying to remove an unexisting span.votes<commit_after>/* Isso – Ich schrei sonst!
 */
define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n", "app/lib", "app/globals"],
    function($, utils, config, api, jade, i18n, lib, globals) {

    "use strict";

    var Postbox = function(parent) {

        var el = $.htmlify(jade.render("postbox", {
            "author":  JSON.parse(localStorage.getItem("author")),
            "email":   JSON.parse(localStorage.getItem("email")),
            "website": JSON.parse(localStorage.getItem("website"))
        }));

        // callback on success (e.g. to toggle the reply button)
        el.onsuccess = function() {};

        el.validate = function() {
            if (utils.text($(".textarea", this).innerHTML).length < 3 ||
                $(".textarea", this).classList.contains("placeholder"))
            {
                $(".textarea", this).focus();
                return false;
            }
            return true;
        };

        // submit form, initialize optional fields with `null` and reset form.
        // If replied to a comment, remove form completely.
        $("[type=submit]", el).on("click", function() {
            if (! el.validate()) {
                return;
            }

            var author = $("[name=author]", el).value || null,
                email = $("[name=email]", el).value || null,
                website = $("[name=website]", el).value || null;

            localStorage.setItem("author", JSON.stringify(author));
            localStorage.setItem("email", JSON.stringify(email));
            localStorage.setItem("website", JSON.stringify(website));

            api.create($("#isso-thread").getAttribute("data-isso-id"), {
                author: author, email: email, website: website,
                text: utils.text($(".textarea", el).innerHTML),
                parent: parent || null
            }).then(function(comment) {
                $(".textarea", el).innerHTML = "";
                $(".textarea", el).blur();
                insert(comment, true);

                if (parent !== null) {
                    el.onsuccess();
                }
            });
        });

        lib.editorify($(".textarea", el));

        return el;
    };

    var insert_loader = function(comment, lastcreated) {
        var entrypoint;
        if (comment.id === null) {
            entrypoint = $("#isso-root");
            comment.name = 'null';
        } else {
            entrypoint = $("#isso-" + comment.id + " > .text-wrapper > .isso-follow-up");
            comment.name = comment.id;
        }
        var el = $.htmlify(jade.render("comment-loader", {"comment": comment}));

        entrypoint.append(el);

        $("a.load_hidden", el).on("click", function() {
            el.remove();
            api.fetch($("#isso-thread").getAttribute("data-isso-id"),
                config["reveal-on-click"], config["max-comments-nested"],
                comment.id,
                lastcreated).then(
                function(rv) {
                    if (rv.total_replies === 0) {
                        return;
                    }

                    var lastcreated = 0;
                    rv.replies.forEach(function(commentObject) {
                        insert(commentObject, false);
                        if(commentObject.created > lastcreated) {
                            lastcreated = commentObject.created;
                        }
                    });

                    if(rv.hidden_replies > 0) {
                        insert_loader(rv, lastcreated);
                    }
                },
                function(err) {
                    console.log(err);
                });
        });
    };

    var insert = function(comment, scrollIntoView) {
        var el = $.htmlify(jade.render("comment", {"comment": comment}));

        // update datetime every 60 seconds
        var refresh = function() {
            $(".permalink > time", el).textContent = utils.ago(
                globals.offset.localTime(), new Date(parseInt(comment.created, 10) * 1000));
            setTimeout(refresh, 60*1000);
        };

        // run once to activate
        refresh();

        if (config["avatar"]) {
            $("div.avatar > svg", el).replace(lib.identicons.generate(comment.hash, 4, 48));
        }

        var entrypoint;
        if (comment.parent === null) {
            entrypoint = $("#isso-root");
        } else {
            entrypoint = $("#isso-" + comment.parent + " > .text-wrapper > .isso-follow-up");
        }

        entrypoint.append(el);

        if (scrollIntoView) {
            el.scrollIntoView();
        }

        var footer = $("#isso-" + comment.id + " > .text-wrapper > .isso-comment-footer"),
            header = $("#isso-" + comment.id + " > .text-wrapper > .isso-comment-header"),
            text   = $("#isso-" + comment.id + " > .text-wrapper > .text");

        var form = null;  // XXX: probably a good place for a closure
        $("a.reply", footer).toggle("click",
            function(toggler) {
                form = footer.insertAfter(new Postbox(comment.parent === null ? comment.id : comment.parent));
                form.onsuccess = function() { toggler.next(); };
                $(".textarea", form).focus();
                $("a.reply", footer).textContent = i18n.translate("comment-close");
            },
            function() {
                form.remove();
                $("a.reply", footer).textContent = i18n.translate("comment-reply");
            }
        );

        if (config.vote) {
            // update vote counter, but hide if votes sum to 0
            var votes = function (value) {
                var span = $("span.votes", footer);
                if (span === null) {
                    if (value !== 0) {
                        footer.prepend($.new("span.votes", value));
                    }
                } else {
                    if (value === 0) {
                        span.remove();
                    } else {
                        span.textContent = value;
                    }
                }
            };

            $("a.upvote", footer).on("click", function () {
                api.like(comment.id).then(function (rv) {
                    votes(rv.likes - rv.dislikes);
                });
            });

            $("a.downvote", footer).on("click", function () {
                api.dislike(comment.id).then(function (rv) {
                    votes(rv.likes - rv.dislikes);
                });
            });
        }

        $("a.edit", footer).toggle("click",
            function(toggler) {
                var edit = $("a.edit", footer);
                var avatar = $(".avatar", el, false)[0];

                edit.textContent = i18n.translate("comment-save");
                edit.insertAfter($.new("a.cancel", i18n.translate("comment-cancel"))).on("click", function() {
                    toggler.canceled = true;
                    toggler.next();
                });

                toggler.canceled = false;
                api.view(comment.id, 1).then(function(rv) {
                    var textarea = lib.editorify($.new("div.textarea"));

                    textarea.innerHTML = utils.detext(rv.text);
                    textarea.focus();

                    text.classList.remove("text");
                    text.classList.add("textarea-wrapper");

                    text.textContent = "";
                    text.append(textarea);
                });

                if (avatar !== null) {
                    avatar.hide();
                }
            },
            function(toggler) {
                var textarea = $(".textarea", text);
                var avatar = $(".avatar", el, false)[0];

                if (! toggler.canceled && textarea !== null) {
                    if (utils.text(textarea.innerHTML).length < 3) {
                        textarea.focus();
                        toggler.wait();
                        return;
                    } else {
                        api.modify(comment.id, {"text": utils.text(textarea.innerHTML)}).then(function(rv) {
                            text.innerHTML = rv.text;
                            comment.text = rv.text;
                        });
                    }
                } else {
                    text.innerHTML = comment.text;
                }

                text.classList.remove("textarea-wrapper");
                text.classList.add("text");

                if (avatar !== null) {
                    avatar.show();
                }

                $("a.cancel", footer).remove();
                $("a.edit", footer).textContent = i18n.translate("comment-edit");
            }
        );

        $("a.delete", footer).toggle("click",
            function(toggler) {
                var del = $("a.delete", footer);
                var state = ! toggler.state;

                del.textContent = i18n.translate("comment-confirm");
                del.on("mouseout", function() {
                    del.textContent = i18n.translate("comment-delete");
                    toggler.state = state;
                    del.onmouseout = null;
                });
            },
            function() {
                var del = $("a.delete", footer);
                api.remove(comment.id).then(function(rv) {
                    if (rv) {
                        el.remove();
                    } else {
                        $("span.note", header).textContent = i18n.translate("comment-deleted");
                        text.innerHTML = "<p> </p>";
                        $("a.edit", footer).remove();
                        $("a.delete", footer).remove();
                    }
                    del.textContent = i18n.translate("comment-delete");
                });
            }
        );

        // remove edit and delete buttons when cookie is gone
        var clear = function(button) {
            if (! utils.cookie("isso-" + comment.id)) {
                if ($(button, footer) !== null) {
                    $(button, footer).remove();
                }
            } else {
                setTimeout(function() { clear(button); }, 15*1000);
            }
        };

        clear("a.edit");
        clear("a.delete");

        // show direct reply to own comment when cookie is max aged
        var show = function(el) {
            if (utils.cookie("isso-" + comment.id)) {
                setTimeout(function() { show(el); }, 15*1000);
            } else {
                footer.append(el);
            }
        };

        if (! config["reply-to-self"] && utils.cookie("isso-" + comment.id)) {
            show($("a.reply", footer).detach());
        }

        if(comment.hasOwnProperty('replies')) {
            var lastcreated = 0;
            comment.replies.forEach(function(replyObject) {
                insert(replyObject, false);
                if(replyObject.created > lastcreated) {
                    lastcreated = replyObject.created;
                }

            });
            if(comment.hidden_replies > 0) {
                insert_loader(comment, lastcreated);
            }

        }

    };

    return {
        insert: insert,
        insert_loader: insert_loader,
        Postbox: Postbox
    };
});
<|endoftext|>


<commit_before> /*
 * MIT license (full version in LICENSE file).
 * Copyright 2013 sebastianstenzel.de
 *
 * Minimum requirements: Javascript 1.4
 */
var Proxy = function(proxyTarget) {
	this.proxyTarget = proxyTarget;
	this.proxyMethod = null;
	this.proxyMethodArguments = null;
	this.proxyChain = false;
	this.proxyChainShouldInvoke = false;

	var createProxyMethod = function(method) {
		return function() {
			this.proxyMethod = method;
			this.proxyMethodArguments = arguments;
		};
	};

	for (key in proxyTarget) {
		if (typeof proxyTarget[key] === 'function' && this[key] === undefined) {
			this[key] = createProxyMethod(proxyTarget[key]);
		}
	}

	this.proxyInvoke = function(bind) {
		if (!bind) {
			bind = this.proxyTarget;
		}
		if (this.proxyMethod == null && this.proxyChain) {
			this.proxyChainShouldInvoke = true;
		} else {
			this.proxyMethod.apply(bind, this.proxyMethodArguments);
		}
		if (bind instanceof Proxy && bind.proxyChainShouldInvoke) {
			bind.proxyInvoke();
		}
	}

	if (proxyTarget instanceof Proxy) {
		proxyTarget.proxyChain = true;
	}
};<commit_msg>Simplified Proxy and added support for proper chaining.<commit_after> /*
 * MIT license (full version in LICENSE file).
 * Copyright 2013 sebastianstenzel.de
 *
 * Minimum requirements: Javascript 1.4
 */

 Object.prototype.getInvocationProxy = function() {
	if (!(this.invocationProxy instanceof Proxy)) {
		this.invocationProxy = new Proxy(this);
	}
	return this.invocationProxy;
};

var Proxy = function(proxyTarget) {
	this.proxyMethodNameQueue = [];
	this.proxyArgumentsQueue = [];

	var createProxyMethod = function(key) {
		return function() {
			console.log("[SCHEDULE] " + proxyTarget + "." + key + "()");
			this.proxyMethodNameQueue.push(key);
			this.proxyArgumentsQueue.push(arguments);
			return this;
		};
	};

	this.proxyInvoke = function() {
		if (this.proxyMethodNameQueue.length == 0) {
			throw "proxyQueueEmptyError";
		}
		var methodName = this.proxyMethodNameQueue.shift();
		var args = this.proxyArgumentsQueue.shift();
		var method = proxyTarget[methodName];

		console.log("[INVOKE] " + proxyTarget + "." + methodName + "()");

		method.apply(proxyTarget, args);
	}

	var reservedNames = ["proxyMethodQueue", "proxyArgumentsQueue", "proxyInvoke"];
	for (key in proxyTarget) {
		if (typeof proxyTarget[key] === 'function' && reservedNames.indexOf(key) === -1) {
			this[key] = createProxyMethod(key);
		}
	}
};

// MSIE:
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function(obj, start) {
		 for (var i = (start || 0), j = this.length; i < j; i++) {
			 if (this[i] === obj) { return i; }
		 }
		 return -1;
	}
}
<|endoftext|>


<commit_before>enchant();

window.onload = function() {
  var game_ = new Game(320, 320);
  game_.fps = 24;
  game_.preload('./javascripts/enchant_js-0.8.3/images/start.png', './javascripts/enchant_js-0.8.3/images/gameover.png', './javascripts/enchant_js-0.8.3/images/chara1.png', './images/bg1.png', './images/bg2.png', './javascripts/enchant_js-0.8.3/images/chara6.png');

  game_.onload = function() {
    var createStartScene = function() {
      var scene = new Scene();
      scene.backgroundColor = '#fcc800';

      var startImage = new Sprite(236, 48);
      startImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/start.png'];
      startImage.x = 42;
      startImage.y = 50;
      scene.addChild(startImage);

      // 説明ラベル設定
      var selectStage1 = new Label('ステージA'); // ラベルを作る
      selectStage1.width = 320;
      selectStage1.textAlign = 'center';                 // 文字を中央寄せ
      selectStage1.color = '#ffffff';                    // 文字を白色に
      selectStage1.x = 0;                                // 横位置調整
      selectStage1.y = 222;                              // 縦位置調整
      selectStage1.font = '14px sans-serif';             // 28pxのゴシック体にする
      scene.addChild(selectStage1);                      // シーンに追加

      selectStage1.addEventListener(Event.TOUCH_START, function(e) {
          game_.replaceScene(createGameScene());
      });

      return scene;
    };

    var createGameScene = function() {
        var scroll = 0;

        var GROUND_LINE = 250;
        var SCROLL_SPEED = 5;

        var scene = new Scene();
        scene.backgroundColor = '#8cc820';

        var bg1 = new Sprite(320, 320);
        bg1.image = game_.assets['./images/bg1.png'];
        bg1.x = 0;
        bg1.y = 0;
        scene.addChild(bg1);

        var bg2 = new Sprite(320, 320);
        bg2.image = game_.assets['./images/bg2.png'];
        bg2.x = 320;
        bg2.y = 0;
        scene.addChild(bg2);

        var charactorPosition = 0;
        var charactor = new Charactor(game_.assets['./javascripts/enchant_js-0.8.3/images/chara1.png']);
        scene.addChild(charactor);

        var charactorHit = new Sprite(1, 1);
        charactorHit.x = charactor.x + charactor.width / 2;
        charactorHit.y = charactor.y + charactor.height / 2;
        scene.addChild(charactorHit);

        var answers = [];
        for(var i = 0; i < 3; i++) {
          var answer = new Label('Answer' + (i + 1));
          answer.width = 100;
          answer.height = 32;
          answer.backgroundColor = '#000';
          answer.textAlign = 'center';
          answer.color = '#ffffff';
          answer.x = -answer.width;
          answer.y = 50 + (i * 100);
          answer.font = '14px sans-serif';
          answers.push(answer);
          scene.addChild(answer);
        }

        var charactorDead = function() {
          charactor.dead;
          game_.pushScene(createGameoverScene(scroll));
        }

        scene.addEventListener(Event.ENTER_FRAME, function(){
            scroll += SCROLL_SPEED;

            if (scroll % 640 === 0) {
              for(var i = 0; i < 3; i++) {
                answers[i].x = 320;
              }
            }

            for(var i = 0; i < 3; i++) {
              if (answers[i].x > -answers[i].width) {
                answers[i].x -= SCROLL_SPEED;
                if (answers[i].intersect(charactorHit)) {
                  charactorDead();
                }
              }
            }

            charactor.updateFrame();

            charactorHit.x = charactor.x + charactor.width/2;
            charactorHit.y = charactor.y + charactor.height/2;

            bg1.x -= SCROLL_SPEED;
            bg2.x -= SCROLL_SPEED;

            if (bg1.x <= -320) {
                bg1.x = 320;
            }
            if (bg2.x <= -320) {
                bg2.x = 320;
            }
        });

        scene.addEventListener(Event.TOUCH_START, function(e){
          if (charactorPosition == 0) {
            charactor.tl.moveTo(80, 50, 12);
          }
          else if (charactorPosition == 1) {
            charactor.tl.moveTo(80, 150, 12);
          }
          else if (charactorPosition == 2) {
            charactor.tl.moveTo(80, 250, 12);
          }
          charactorPosition += 1;
          charactorPosition = charactorPosition % 3;
        });

        return scene;
    }

    var createCorrectAnswerScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var correctAnswerImage = new Sprite(189, 97);
      correctAnswerImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/clear.png'];
      correctAnswerImage.x = 66;
      correctAnswerImage.y = 170;
      scene.addChild(correctAnswerImage);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createStartScene());
      });

      return scene;
    };

    var createGameoverScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var gameoverImage = new Sprite(189, 97);
      gameoverImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/gameover.png'];
      gameoverImage.x = 66;
      gameoverImage.y = 170;
      scene.addChild(gameoverImage);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createStartScene());
      });

      return scene;
    };

    game_.replaceScene(createStartScene());
  }

  game_.start();
}
<commit_msg>呼出しが一箇所なのでcharactorDead関数を削除<commit_after>enchant();

window.onload = function() {
  var game_ = new Game(320, 320);
  game_.fps = 24;
  game_.preload('./javascripts/enchant_js-0.8.3/images/start.png', './javascripts/enchant_js-0.8.3/images/gameover.png', './javascripts/enchant_js-0.8.3/images/chara1.png', './images/bg1.png', './images/bg2.png', './javascripts/enchant_js-0.8.3/images/chara6.png');

  game_.onload = function() {
    var createStartScene = function() {
      var scene = new Scene();
      scene.backgroundColor = '#fcc800';

      var startImage = new Sprite(236, 48);
      startImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/start.png'];
      startImage.x = 42;
      startImage.y = 50;
      scene.addChild(startImage);

      // 説明ラベル設定
      var selectStage1 = new Label('ステージA'); // ラベルを作る
      selectStage1.width = 320;
      selectStage1.textAlign = 'center';                 // 文字を中央寄せ
      selectStage1.color = '#ffffff';                    // 文字を白色に
      selectStage1.x = 0;                                // 横位置調整
      selectStage1.y = 222;                              // 縦位置調整
      selectStage1.font = '14px sans-serif';             // 28pxのゴシック体にする
      scene.addChild(selectStage1);                      // シーンに追加

      selectStage1.addEventListener(Event.TOUCH_START, function(e) {
          game_.replaceScene(createGameScene());
      });

      return scene;
    };

    var createGameScene = function() {
        var scroll = 0;

        var GROUND_LINE = 250;
        var SCROLL_SPEED = 5;

        var scene = new Scene();
        scene.backgroundColor = '#8cc820';

        var bg1 = new Sprite(320, 320);
        bg1.image = game_.assets['./images/bg1.png'];
        bg1.x = 0;
        bg1.y = 0;
        scene.addChild(bg1);

        var bg2 = new Sprite(320, 320);
        bg2.image = game_.assets['./images/bg2.png'];
        bg2.x = 320;
        bg2.y = 0;
        scene.addChild(bg2);

        var charactorPosition = 0;
        var charactor = new Charactor(game_.assets['./javascripts/enchant_js-0.8.3/images/chara1.png']);
        scene.addChild(charactor);

        var charactorHit = new Sprite(1, 1);
        charactorHit.x = charactor.x + charactor.width / 2;
        charactorHit.y = charactor.y + charactor.height / 2;
        scene.addChild(charactorHit);

        var answers = [];
        for(var i = 0; i < 3; i++) {
          var answer = new Label('Answer' + (i + 1));
          answer.width = 100;
          answer.height = 32;
          answer.backgroundColor = '#000';
          answer.textAlign = 'center';
          answer.color = '#ffffff';
          answer.x = -answer.width;
          answer.y = 50 + (i * 100);
          answer.font = '14px sans-serif';
          answers.push(answer);
          scene.addChild(answer);
        }

        scene.addEventListener(Event.ENTER_FRAME, function(){
            scroll += SCROLL_SPEED;

            if (scroll % 640 === 0) {
              for(var i = 0; i < 3; i++) {
                answers[i].x = 320;
              }
            }

            for(var i = 0; i < 3; i++) {
              if (answers[i].x > -answers[i].width) {
                answers[i].x -= SCROLL_SPEED;
                if (answers[i].intersect(charactorHit)) {
                  charactor.dead;
                  game_.pushScene(createGameoverScene(scroll));
                }
              }
            }

            charactor.updateFrame();

            charactorHit.x = charactor.x + charactor.width/2;
            charactorHit.y = charactor.y + charactor.height/2;

            bg1.x -= SCROLL_SPEED;
            bg2.x -= SCROLL_SPEED;

            if (bg1.x <= -320) {
                bg1.x = 320;
            }
            if (bg2.x <= -320) {
                bg2.x = 320;
            }
        });

        scene.addEventListener(Event.TOUCH_START, function(e){
          if (charactorPosition == 0) {
            charactor.tl.moveTo(80, 50, 12);
          }
          else if (charactorPosition == 1) {
            charactor.tl.moveTo(80, 150, 12);
          }
          else if (charactorPosition == 2) {
            charactor.tl.moveTo(80, 250, 12);
          }
          charactorPosition += 1;
          charactorPosition = charactorPosition % 3;
        });

        return scene;
    }

    var createCorrectAnswerScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var correctAnswerImage = new Sprite(189, 97);
      correctAnswerImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/clear.png'];
      correctAnswerImage.x = 66;
      correctAnswerImage.y = 170;
      scene.addChild(correctAnswerImage);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createStartScene());
      });

      return scene;
    };

    var createGameoverScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var gameoverImage = new Sprite(189, 97);
      gameoverImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/gameover.png'];
      gameoverImage.x = 66;
      gameoverImage.y = 170;
      scene.addChild(gameoverImage);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createStartScene());
      });

      return scene;
    };

    game_.replaceScene(createStartScene());
  }

  game_.start();
}
<|endoftext|>


<commit_before>/**
 * @file MOZTracker main script
 * @author Matt Schoen
 * @version 0.1
 *
 * @namespace moztrack
 */
var moztrack = moztrack || {};
(function(){
	var progress;

	var startDate = new Date(2015, 5, 27, 12);
	var endDate = new Date(2015, 7, 6, 19, 17);

	var episodeNum = 2659;

	var showStartDate = new Date(1999, 0, 11);
	var showEndDate = new Date(2015, 8, 6);

	var showTime, realTime;

	var perEpisode = 28;
	var perEpisodeOverride = 24;
	moztrack.startOverride = new Date(2015,5,26,3,24);

	$(document).ready(function() {
		//Setup local DB
		moztrack.createLocalStorage();

		//Create tracker
		var content = document.getElementById("moztrack-content");
		progress = Div();

		content.appendChild(Div({}, ["Stream started at " + startDate]));
		content.appendChild(Div({}, ["Stream ends at " + endDate]));
		content.appendChild(Div({}, ["Stream duration: " + (endDate - startDate) / (60 * 60 * 1000), " hours"]));
		content.appendChild(Div({}, ["Episode count: " + episodeNum]));
		perEpisode = (endDate - startDate) / episodeNum;
		content.appendChild(Div({}, ["Computed " + ((perEpisode / (60 * 1000)) +  " minutes per episode")]));
		content.appendChild(Div({}, ["Episode length overridden to: " + perEpisodeOverride]));
		content.appendChild(Div({}, ["Using start time: " + moztrack.startOverride]));

		showTime = Input({},[],{"onkeyup":function(){
			var date = new Date(this.value);
			moztrack.getRealTime(date);
		}});

		realTime = Input({},[],{"onkeyup":function(){
			var date = new Date(this.value);
			moztrack.getShowTime(date);
		}});

		content.appendChild(Div({"style":"margin-top:25px;"},[
			Div({},[
				"Show Time: ", showTime,
				" Real Time: ", realTime
			]),
			Elm("button", {"type":"button"}, ["Reset Local Storage"], {"onclick":moztrack.clearLocalStorage}),
			progress
		]));

		//start on a delay for the first load
		setTimeout(function(){
			realTime.value = new Date().format("Y-m-d H:i:s");
			moztrack.getShowTime(new Date());
		}, 1500);
	});
	moztrack.createLocalStorage = function(){
		moztrack.db = openDatabase('moztrack', '0.1', 'Daily show episodes', 1*1024*1024);
		//If table exists, assume we've done this before
		doIfTableNotExist(moztrack.db, "episodes", function(){
			moztrack.db.transaction(function(tx){
				tx.executeSql('CREATE TABLE IF NOT EXISTS "episodes" ("id" INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL  UNIQUE , "guest" VARCHAR, "date" DATETIME NOT NULL, "realtime" DATETIME NOT NULL );', [],
					function(tx, results){
						//TODO: non-blocking solution
						$.ajax({
							//Use absolute URL for local testing, Chrome blocks ajax requests to file:///
							"url":"http://mtschoen.github.io/month-of-zen-tracker/episodes/all.json",
							//"url":"http://mtschoen.github.io/month-of-zen-tracker/episodes/" + 1999 + ".json",
							//"url":"episodes/" + episodeLists[i] + ".json",
							"crossDomain":true,
							"async":false,
							"success":function(data){
								episodeNum = data.length - 1;	//-1 for sept11
								var perEpisode = (endDate - startDate) / episodeNum;

								perEpisode = perEpisodeOverride * 60 * 1000;
								var streamTime = 0;
								var query = "INSERT INTO episodes (guest,date,realtime) VALUES (?,?,?)";
								episodeNum = 0;
								console.log(moztrack.startOverride);
								for(var j in data){
									//Skip over the unaired sept11 episode
									if(data[j].extra == "sept11")
										continue;
									episodeNum++;
									data[j].date = new Date(data[j].date);

									progress.innerHTML = j;

									var realtime = moztrack.startOverride.getTime() + streamTime;
									tx.executeSql(query, [data[j].guest, data[j].date.getTime(), realtime],
										function(tx, results){

										},
										function(tx, error){
											console.log(error);
											console.log(query);
											console.log(data[j].guest, data[j].date.format("Y-m-d"));
											throw error;
										}
									);
									streamTime += perEpisode;
								}
							}
						});
					},
					function(tx, error){
						console.log(error);
						throw error;
					}
				);
			});
		});
	};
	moztrack.clearLocalStorage = function(){
		moztrack.db.transaction(function(tx){
			tx.executeSql("DROP TABLE IF EXISTS episodes;",[],
			function(){

			},
			function(tx, error){
				throw error;
			});
		});
		moztrack.createLocalStorage();
		setTimeout(function(){
			realTime.value = new Date().format("Y-m-d H:i:s");
			moztrack.getShowTime(new Date());
		}, 1000);
		ga('send', 'event', 'clearStorage');
	};

	function doIfTableNotExist(db, table, callback){
		db.transaction(function(tx){
			tx.executeSql("SELECT COUNT(*) FROM sqlite_master WHERE type = ? AND name = ?", ["table", table],
				function(tx, results){
					if(results.rows[0]["COUNT(*)"] == 0)
						callback();
				},
				function(tx, error){
					throw error;
				}
			);
		});
	}

	moztrack.getRealTime = function(date){
		if(date < showStartDate || date > showEndDate){
			console.log("Invalid show date: ", date, showStartDate);
			ga('send', 'event', 'getRealTimeInvalid', date.format("Y-m-d H:i:s"));
			return;
		}
		ga('send', 'event', 'getRealTime', date.format("Y-m-d"));
		ga('send', 'event', 'getRealTimeFull', date.format("Y-m-d H:i:s"));
		moztrack.db.transaction(function(tx){
			tx.executeSql("SELECT * FROM episodes WHERE date >= ? ORDER BY date LIMIT 1", [date.getTime()],
			function(tx, results){
				if(results.rows.length > 0){
					progress.innerHTML = "Guest: " + results.rows[0].guest;
					realTime.value = new Date(results.rows[0].realtime).format("Y-m-d H:i:s");
				} else {
					console.log("no results?");
				}
			},
			function(tx, error){
				console.log(error);
				throw error;
			});
		});
	};
	moztrack.getShowTime = function(date){
		if(date < startDate || date > endDate){
			console.log("Invalid real date");
			ga('send', 'event', 'getShowTimeInvalid', date.format("Y-m-d H:i:s"));
			return;
		}
		ga('send', 'event', 'getShowTimeFull', date.format("Y-m-d H:i:s"));
		moztrack.db.transaction(function(tx){
			tx.executeSql("SELECT * FROM episodes WHERE realtime <= ? ORDER BY date DESC LIMIT 1", [date.getTime()],
			function(tx, results){
				if(results.rows.length > 0){
					progress.innerHTML = "Guest: " + results.rows[0].guest;
					showTime.value = new Date(results.rows[0].date).format("Y-m-d");
				} else {
					console.log("no results?");
				}
			},
			function(tx, error){
				console.log(error);
				throw error;
			});
		});
	};
})();
<commit_msg>Update main.js<commit_after>/**
 * @file MOZTracker main script
 * @author Matt Schoen
 * @version 0.1
 *
 * @namespace moztrack
 */
var moztrack = moztrack || {};
(function(){
	var progress;

	var startDate = new Date(2015, 5, 27, 12);
	var endDate = new Date(2015, 7, 6, 19, 17);

	var episodeNum = 2659;

	var showStartDate = new Date(1999, 0, 11);
	var showEndDate = new Date(2015, 8, 6);

	var showTime, realTime;

	var perEpisode = 28;
	var perEpisodeOverride = 24;
	moztrack.startOverride = new Date(2015,5,26,3,20);

	$(document).ready(function() {
		//Setup local DB
		moztrack.createLocalStorage();

		//Create tracker
		var content = document.getElementById("moztrack-content");
		progress = Div();

		content.appendChild(Div({}, ["Stream started at " + startDate]));
		content.appendChild(Div({}, ["Stream ends at " + endDate]));
		content.appendChild(Div({}, ["Stream duration: " + (endDate - startDate) / (60 * 60 * 1000), " hours"]));
		content.appendChild(Div({}, ["Episode count: " + episodeNum]));
		perEpisode = (endDate - startDate) / episodeNum;
		content.appendChild(Div({}, ["Computed " + ((perEpisode / (60 * 1000)) +  " minutes per episode")]));
		content.appendChild(Div({}, ["Episode length overridden to: " + perEpisodeOverride]));
		content.appendChild(Div({}, ["Using start time: " + moztrack.startOverride]));

		showTime = Input({},[],{"onkeyup":function(){
			var date = new Date(this.value);
			moztrack.getRealTime(date);
		}});

		realTime = Input({},[],{"onkeyup":function(){
			var date = new Date(this.value);
			moztrack.getShowTime(date);
		}});

		content.appendChild(Div({"style":"margin-top:25px;"},[
			Div({},[
				"Show Time: ", showTime,
				" Real Time: ", realTime
			]),
			Elm("button", {"type":"button"}, ["Reset Local Storage"], {"onclick":moztrack.clearLocalStorage}),
			progress
		]));

		//start on a delay for the first load
		setTimeout(function(){
			realTime.value = new Date().format("Y-m-d H:i:s");
			moztrack.getShowTime(new Date());
		}, 1500);
	});
	moztrack.createLocalStorage = function(){
		moztrack.db = openDatabase('moztrack', '0.1', 'Daily show episodes', 1*1024*1024);
		//If table exists, assume we've done this before
		doIfTableNotExist(moztrack.db, "episodes", function(){
			moztrack.db.transaction(function(tx){
				tx.executeSql('CREATE TABLE IF NOT EXISTS "episodes" ("id" INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL  UNIQUE , "guest" VARCHAR, "date" DATETIME NOT NULL, "realtime" DATETIME NOT NULL );', [],
					function(tx, results){
						//TODO: non-blocking solution
						$.ajax({
							//Use absolute URL for local testing, Chrome blocks ajax requests to file:///
							"url":"http://mtschoen.github.io/month-of-zen-tracker/episodes/all.json",
							//"url":"http://mtschoen.github.io/month-of-zen-tracker/episodes/" + 1999 + ".json",
							//"url":"episodes/" + episodeLists[i] + ".json",
							"crossDomain":true,
							"async":false,
							"success":function(data){
								episodeNum = data.length - 1;	//-1 for sept11
								var perEpisode = (endDate - startDate) / episodeNum;

								perEpisode = perEpisodeOverride * 60 * 1000;
								var streamTime = 0;
								var query = "INSERT INTO episodes (guest,date,realtime) VALUES (?,?,?)";
								episodeNum = 0;
								console.log(moztrack.startOverride);
								for(var j in data){
									//Skip over the unaired sept11 episode
									if(data[j].extra == "sept11")
										continue;
									episodeNum++;
									data[j].date = new Date(data[j].date);

									progress.innerHTML = j;

									var realtime = moztrack.startOverride.getTime() + streamTime;
									tx.executeSql(query, [data[j].guest, data[j].date.getTime(), realtime],
										function(tx, results){

										},
										function(tx, error){
											console.log(error);
											console.log(query);
											console.log(data[j].guest, data[j].date.format("Y-m-d"));
											throw error;
										}
									);
									streamTime += perEpisode;
								}
							}
						});
					},
					function(tx, error){
						console.log(error);
						throw error;
					}
				);
			});
		});
	};
	moztrack.clearLocalStorage = function(){
		moztrack.db.transaction(function(tx){
			tx.executeSql("DROP TABLE IF EXISTS episodes;",[],
			function(){

			},
			function(tx, error){
				throw error;
			});
		});
		moztrack.createLocalStorage();
		setTimeout(function(){
			realTime.value = new Date().format("Y-m-d H:i:s");
			moztrack.getShowTime(new Date());
		}, 1000);
		ga('send', 'event', 'clearStorage');
	};

	function doIfTableNotExist(db, table, callback){
		db.transaction(function(tx){
			tx.executeSql("SELECT COUNT(*) FROM sqlite_master WHERE type = ? AND name = ?", ["table", table],
				function(tx, results){
					if(results.rows[0]["COUNT(*)"] == 0)
						callback();
				},
				function(tx, error){
					throw error;
				}
			);
		});
	}

	moztrack.getRealTime = function(date){
		if(date < showStartDate || date > showEndDate){
			console.log("Invalid show date: ", date, showStartDate);
			ga('send', 'event', 'getRealTimeInvalid', date.format("Y-m-d H:i:s"));
			return;
		}
		ga('send', 'event', 'getRealTime', date.format("Y-m-d"));
		ga('send', 'event', 'getRealTimeFull', date.format("Y-m-d H:i:s"));
		moztrack.db.transaction(function(tx){
			tx.executeSql("SELECT * FROM episodes WHERE date >= ? ORDER BY date LIMIT 1", [date.getTime()],
			function(tx, results){
				if(results.rows.length > 0){
					progress.innerHTML = "Guest: " + results.rows[0].guest;
					realTime.value = new Date(results.rows[0].realtime).format("Y-m-d H:i:s");
				} else {
					console.log("no results?");
				}
			},
			function(tx, error){
				console.log(error);
				throw error;
			});
		});
	};
	moztrack.getShowTime = function(date){
		if(date < startDate || date > endDate){
			console.log("Invalid real date");
			ga('send', 'event', 'getShowTimeInvalid', date.format("Y-m-d H:i:s"));
			return;
		}
		ga('send', 'event', 'getShowTimeFull', date.format("Y-m-d H:i:s"));
		moztrack.db.transaction(function(tx){
			tx.executeSql("SELECT * FROM episodes WHERE realtime <= ? ORDER BY date DESC LIMIT 1", [date.getTime()],
			function(tx, results){
				if(results.rows.length > 0){
					progress.innerHTML = "Guest: " + results.rows[0].guest;
					showTime.value = new Date(results.rows[0].date).format("Y-m-d");
				} else {
					console.log("no results?");
				}
			},
			function(tx, error){
				console.log(error);
				throw error;
			});
		});
	};
})();
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2013 Bryan Allred <bryan.allred@gmail.com>
//
// 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.

// Check to see if a string starts with the given search criteria.
// @param {String} search
// @return {Boolean} a value indicating whether the string starts with the search criteria
String.prototype.startsWith = function (search) {
    return this.indexOf(search) == 0;
};

// Check to see if a string ends with the given search criteria.
// @param {String} search
// @return {Boolean} a value indicating whether the string ends with the search criteria
String.prototype.endsWith = function (search) {
    return original.lastIndexOf(search) == original.length - search.length;
};

(function ($) {
    // Declared outside of scope to maintain an accurate count.
    var uniqueId = 0;

    // Provide a unique identifier to an element if one has not already been assigned.
    // @return {Object} modified jQuery objects
    $.fn.analyticsUniqueId = function () {
        if (this.length == 0) {
            return;
        }

        return this.each(function () {
            if (!$(this).attr("id")) {
                $(this).attr("id", "analytics-id-" + ++uniqueId);
            }
        });
    };
})(jQuery);

(function ($) {
    // Default settings which may be extended upon.
    var settings = {
        attributes: [],
        assignTo: ["a", "input[type='submit']"],
        captureOnce: false,
        client: null,
        exclude: ".analytics-exclude",
        url: null
    };

    // Walk the tree of a given node.
    // @param {Object} element
    // @return {Array} path
    function walkTree(element) {
        var tree = [];
        var tagName = $(element).prop("tagName");

        if (tagName != undefined) {
            var parent = $(element).parent();
            if (parent != undefined) {
                $.each(walkTree($(element).parent()), function (i, node) {
                    tree.push(node);
                });
            }

            var tagId = $(element).analyticsUniqueId().attr("id");
            tree.push(tagName + '[id="' + tagId + '"]');
        }

        return tree;
    };

    // Identify the path to the node.
    // @param {Object} node
    function identifyPath(node) {
        // Assign identification to all relevant elements.
        walkTree(node);
    };

    // Initiate a trace on click.
    // @param {Object} e
    function initiateTrace(e) {
        // Locally scope this variable.
        $this = $(this);

        if (settings.url && !$this.is(".analytics-captured") && !$this.is(settings.exclude)) {
            // // We prevent the default action to allow the background call to succeed.
            // e.preventDefault();

            // Initialize the data to be collected.
            var data = {
                id: walkTree($this).join('.')
            };

            // Attach the client identifier if found.
            if (settings.client) {
                data["client"] = settings.client
            }

            // Assign any "data-analytics-" attributes.
            var dataAttributes = $this.data();
            for (var attribute in dataAttributes) {
                if (attribute.startsWith("analytics")) {
                    var cleanName = attribute.replace(/analytics/g, '').toLowerCase();
                    data[cleanName] = dataAttributes[attribute];
                }
            }

            // Assign the custom attributes requested to be collected.
            $.each($(settings.attributes), function (i, attribute) {
                data[attribute] = $this.attr(attribute);
            });

            // Send the analytics.
            $.ajax({
                type: "POST",
                url: settings.url,
                contentType: "application/x-www-form-urlencoded",
                data: data
            })
            .always(function () {
                if (settings.captureOnce) {
                    $this.addClass("analytics-captured");
                }
            });
        }
    };

    // Plug-in function providing easy access to analytics.
    // @param {Object} options
    // @returns {Object} modified jQuery objects
    $.fn.analytics = function (options) {
        if ($(this).length == 0) {
            return;
        }

        // Configure the default settings.
        settings = $.extend({}, settings, options);

        // Declare the selector to be used.
        var selector = settings.assignTo.join(",");

        return this.each(function () {
            // Itereate through all elements given on initiation.
            $(this).find(selector).andSelf().filter(selector)
            .each(function () {
                identifyPath($(this));
                $(this).on("click", initiateTrace);
            });
        })
        .on("DOMNodeInserted", function (e) {
            // This will capture any dynamically generated content.
            $(e.target).find(selector).andSelf().filter(selector)
            .each(function () {
                identifyPath($(this));
                $(this).on("click", initiateTrace);
            });
        });
    };
})(jQuery);<commit_msg>Fixed chaining of nodes in tree and excluded HTML and BODY from unique identification<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2013 Bryan Allred <bryan.allred@gmail.com>
//
// 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.

// Check to see if a string starts with the given search criteria.
// @param {String} search
// @return {Boolean} a value indicating whether the string starts with the search criteria
String.prototype.startsWith = function (search) {
    return this.indexOf(search) == 0;
};

// Check to see if a string ends with the given search criteria.
// @param {String} search
// @return {Boolean} a value indicating whether the string ends with the search criteria
String.prototype.endsWith = function (search) {
    return original.lastIndexOf(search) == original.length - search.length;
};

(function ($) {
    // Declared outside of scope to maintain an accurate count.
    var uniqueId = 0;

    // Provide a unique identifier to an element if one has not already been assigned.
    // @return {Object} modified jQuery objects
    $.fn.analyticsUniqueId = function () {
        if (this.length == 0) {
            return;
        }

        return this.each(function () {
            if (!$(this).attr("id")) {
                $(this).attr("id", "analytics-id-" + ++uniqueId);
            }
        });
    };
})(jQuery);

(function ($) {
    // Default settings which may be extended upon.
    var settings = {
        attributes: [],
        assignTo: ["a", "input[type='submit']"],
        captureOnce: false,
        client: null,
        exclude: ".analytics-exclude",
        url: null
    };

    // Walk the tree of a given node.
    // @param {Object} element
    // @return {Array} path
    function walkTree(element) {
        var tree = [];
        var tagName = $(element).prop("tagName");

        if (tagName != undefined) {
            var parent = $(element).parent();
            if (parent != undefined) {
                $.each(walkTree($(element).parent()), function (i, node) {
                    tree.push(node);
                });
            }

            if (tagName == "HTML" || tagName == "BODY") {
                tree.push(tagName);
            }
            else {
                var tagId = $(element).analyticsUniqueId().attr("id");
                tree.push(tagName + '[id="' + tagId + '"]');
            }
        }

        return tree;
    };

    // Identify the path to the node.
    // @param {Object} node
    function identifyPath(node) {
        // Assign identification to all relevant elements.
        walkTree(node);
    };

    // Initiate a trace on click.
    // @param {Object} e
    function initiateTrace(e) {
        // Locally scope this variable.
        $this = $(this);

        if (settings.url && !$this.is(".analytics-captured") && !$this.is(settings.exclude)) {
            // // We prevent the default action to allow the background call to succeed.
            // e.preventDefault();

            // Initialize the data to be collected.
            var data = {
                id: walkTree($this).join(' ')
            };

            // Attach the client identifier if found.
            if (settings.client) {
                data["client"] = settings.client
            }

            // Assign any "data-analytics-" attributes.
            var dataAttributes = $this.data();
            for (var attribute in dataAttributes) {
                if (attribute.startsWith("analytics")) {
                    var cleanName = attribute.replace(/analytics/g, '').toLowerCase();
                    data[cleanName] = dataAttributes[attribute];
                }
            }

            // Assign the custom attributes requested to be collected.
            $.each($(settings.attributes), function (i, attribute) {
                data[attribute] = $this.attr(attribute);
            });

            // Send the analytics.
            $.ajax({
                type: "POST",
                url: settings.url,
                contentType: "application/x-www-form-urlencoded",
                data: data
            })
            .always(function () {
                if (settings.captureOnce) {
                    $this.addClass("analytics-captured");
                }
            });
        }
    };

    // Plug-in function providing easy access to analytics.
    // @param {Object} options
    // @returns {Object} modified jQuery objects
    $.fn.analytics = function (options) {
        if ($(this).length == 0) {
            return;
        }

        // Configure the default settings.
        settings = $.extend({}, settings, options);

        // Declare the selector to be used.
        var selector = settings.assignTo.join(",");

        return this.each(function () {
            // Itereate through all elements given on initiation.
            $(this).find(selector).andSelf().filter(selector)
            .each(function () {
                identifyPath($(this));
                $(this).on("click", initiateTrace);
            });
        })
        .on("DOMNodeInserted", function (e) {
            // This will capture any dynamically generated content.
            $(e.target).find(selector).andSelf().filter(selector)
            .each(function () {
                identifyPath($(this));
                $(this).on("click", initiateTrace);
            });
        });
    };
})(jQuery);<|endoftext|>


<commit_before>/**
 * fluidinfo.js - a small and simple client for Fluidinfo written in Javascript.
 *
 * @author onigiri, ntoll & barshirtcliff
 * @version 0.1
 * @constructor
 * @param options {Object} Contains various parameters for the call.
 * returns {Object} An object through which one interacts with Fluidinfo.
 */
fluidinfo = function(options) {
    session = new Object();

    if(options) {
      if(options.instance) {
        switch(options.instance.toLowerCase()) {
          case "main":
            session.baseURL = "https://fluiddb.fluidinfo.com/";
            break;
          case "sandbox":
            session.baseURL = "https://sandbox.fluidinfo.com/";
            break;
          default:
              // validate the bespoke instance
              var urlRegex = /^(http|https):\/\/[\w\-_\.]+\/$/;
              if(urlRegex.exec(options.instance)) {
                session.baseURL = options.instance;
              } else {
                throw {
                  name: "ValueError",
                  message: "The URL must start with http[s]:// and have a trailing slash ('/') to be valid. E.g. https://localhost/"
                };
              }
        }
      }

      if((options.username != undefined) && (options.password != undefined)) {
        session.authorizationToken = Base64.encode(options.username + ":" + options.password);
      }
    }

    // Catch-all to make sure the library defaults to the main instance
    if(session.baseURL === undefined){
      session.baseURL = "https://fluiddb.fluidinfo.com/";
    }

    var utils = new Object();

    /**
     * Magic voodoo used to identify an array (potentially a Fluidinfo set).
     * Taken from page 61 of Doug Crockford's "Javascript: The Good Parts".
     *
     * @param value A value that might be an array.
     * @return {boolean} An indication if the value is an array.
     */
    utils.isArray = function(value) {
      return Object.prototype.toString.apply(value) === "[object Array]";
    };

    /**
     * Given a path that is expressed as an array of path elements, will
     * return the correctly encoded URL. e.g. ["a", "b", "c"] -> "/a/b/c"
     *
     * @param value {Array} A list of values to be appropriately encoded between
     * '/' values.
     * @return {string} The appropriately encoded URL.
     */
    utils.encodeURL = function(path) {
      var result = "";
      for(i=0; i<path.length; i++) {
        result += "/" + encodeURIComponent(path[i]);
      }
      return result.slice(1); // chops the leading slash
    };

    /**
     * Checks the passed value to discover if it's a Fluidinfo "primitive"
     * type. See
     * the Fluidinfo docs for more
     * information on Fluidinfo's types.
     *
     * @param value A value whose "type" needs identifying
     * @return A boolean indication if the value is a Fluidinfo primitive type.
     */
    utils.isPrimitive = function(value) {
      // check the easy type matches first
      var valueType = typeof(value);
      var primitiveTypes = ["number", "string", "boolean"];
      for(i=0; i<primitiveTypes.length; i++) {
        if(valueType === primitiveTypes[i]) {
          return true;
        }
      }
      // A null value is also a primitive
      if(value===null) {
        return true;
      }
      // check for an array (potential set) and validate it only contains
      // strings (currently multi-type arrays are not allowed)
      if(utils.isArray(value)) {
        for(i=0; i<value.length; i++) {
          memberType = typeof(value[i]);
          if(memberType !== "string") {
            return false;
          }
        }
        return true;
      }
      // value hasn't matched any of the primitive checks
      return false;
    }

    /**
     * Given the options describing a request, will return the most appropriate
     * MIME to set as the value for the Content-Type header
     *
     * @param options {Object} Contains various parameters describing the
     * request.
     * @return the most appropriate MIME to use of null if not appropriate /
     * required.
     */
    utils.detectContentType = function(options) {
      // a "PUT" to objects/ or about/ endpoints means dealing with the MIME
      // of a tag-value. If no MIME type is passed in the options objects
      // then check if the value is a Fluidinfo primitive type. Otherwise
      // complain.
      var result = null;
      if(options.type==="PUT" && (options.url.match("^objects\/") || options.url.match("^about\/"))) {
        if(options.contentType){
          result = options.contentType;
        } else if (api.utils.isPrimitive(options.data)) {
          result = "application/vnd.fluiddb.value+json";
        } else {
          throw { name: "ValueError", message: "Must supply Content-Type"};
        }
      } else if (options.data) {
        // all other requests to the API that have payloads will be passing JSON
        result = "application/json";
      }
      return result;
    }

    var api = new Object();

    api.utils = utils;

    /**
     * Makes an appropriate AJAX request to Fluidinfo
     *
     * @private
     * @param options {Object} Contains various parameters for the call.
     */
    api.ajax = function(options){
      options.contentType = api.utils.detectContentType(options);
      if(api.utils.isArray(options.url)) {
        options.url = api.utils.encodeURL(options.url);
      }
      options.url = session.baseURL+options.url;
      options.async = options.async || true;
      options.beforeSend = function(xhrObj){
        if(session.authorizationToken != undefined){
          xhrObj.setRequestHeader("Authorization","Basic " + session.authorizationToken);
        };
        if(options.contentType) {
          xhrObj.setRequestHeader("Content-Type", options.contentType);
        }
      };
      options.processData = false;
      $.ajax(options);
    }

    /**
     * Makes an HTTP GET call to the Fluidinfo API
     *
     */
    api.get = function(options){
      options.type = "GET";
      options.data = null;
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP POST call to the Fluidinfo API
     *
     */
    api.post = function(options){
      options.type = "POST";
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP PUT call to the Fluidinfo API
     *
     */
    api.put = function(options){
      options.type = "PUT";
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP DELETE call to the Fluidinfo API
     *
     */
    api.delete = function(options){
      options.type = "DELETE";
      options.data = null;
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP HEAD call to the Fluidinfo API
     *
     */
    api.head = function(options){
      options.type = "HEAD";
      options.data = null;
      session.api.ajax(options);
    }

    session.api = api;

    return session;
}

/**
 * Taken from http://www.webtoolkit.info/javascript-base64.html
*/
var Base64 = {
	// private property
	_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
	// public method for encoding
	encode : function (input) {
		var output = "";
		var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
		var i = 0;
		input = Base64._utf8_encode(input);
		while (i < input.length) {
			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);
			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;
			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
			} else if (isNaN(chr3)) {
				enc4 = 64;
			}
			output = output +
			this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
			this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
		}
		return output;
	},
	// private method for UTF-8 encoding
	_utf8_encode : function (string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";
		for (var n = 0; n < string.length; n++) {
			var c = string.charCodeAt(n);
			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}
		}
		return utftext;
	}
}
<commit_msg>**TRIVIAL** moves the base64 encoding code within the fluidinfo function to address issue #8.<commit_after>/**
 * fluidinfo.js - a small and simple client for Fluidinfo written in Javascript.
 *
 * @author onigiri, ntoll & barshirtcliff
 * @version 0.1
 * @constructor
 * @param options {Object} Contains various parameters for the call.
 * returns {Object} An object through which one interacts with Fluidinfo.
 */
fluidinfo = function(options) {
    /**
     * Encodes strings into base64. Adapted from:
     * http://www.webtoolkit.info/javascript-base64.html
    */
    var Base64 = {
        // private property
        _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        // public method for encoding
        encode : function (input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;
            input = Base64._utf8_encode(input);
            while (i < input.length) {
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);
                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;
                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }
                output = output +
                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
            }
            return output;
        },
        // private method for UTF-8 encoding
        _utf8_encode : function (string) {
            string = string.replace(/\r\n/g,"\n");
            var utftext = "";
            for (var n = 0; n < string.length; n++) {
                var c = string.charCodeAt(n);
                if (c < 128) {
                    utftext += String.fromCharCode(c);
                }
                else if((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                }
                else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }
            }
            return utftext;
        }
    }

    // The object to eventually be returned
    session = new Object();

    if(options) {
      if(options.instance) {
        switch(options.instance.toLowerCase()) {
          case "main":
            session.baseURL = "https://fluiddb.fluidinfo.com/";
            break;
          case "sandbox":
            session.baseURL = "https://sandbox.fluidinfo.com/";
            break;
          default:
              // validate the bespoke instance
              var urlRegex = /^(http|https):\/\/[\w\-_\.]+\/$/;
              if(urlRegex.exec(options.instance)) {
                session.baseURL = options.instance;
              } else {
                throw {
                  name: "ValueError",
                  message: "The URL must start with http[s]:// and have a trailing slash ('/') to be valid. E.g. https://localhost/"
                };
              }
        }
      }

      if((options.username != undefined) && (options.password != undefined)) {
        session.authorizationToken = Base64.encode(options.username + ":" + options.password);
      }
    }

    // Catch-all to make sure the library defaults to the main instance
    if(session.baseURL === undefined){
      session.baseURL = "https://fluiddb.fluidinfo.com/";
    }

    var utils = new Object();

    /**
     * Magic voodoo used to identify an array (potentially a Fluidinfo set).
     * Taken from page 61 of Doug Crockford's "Javascript: The Good Parts".
     *
     * @param value A value that might be an array.
     * @return {boolean} An indication if the value is an array.
     */
    utils.isArray = function(value) {
      return Object.prototype.toString.apply(value) === "[object Array]";
    };

    /**
     * Given a path that is expressed as an array of path elements, will
     * return the correctly encoded URL. e.g. ["a", "b", "c"] -> "/a/b/c"
     *
     * @param value {Array} A list of values to be appropriately encoded between
     * '/' values.
     * @return {string} The appropriately encoded URL.
     */
    utils.encodeURL = function(path) {
      var result = "";
      for(i=0; i<path.length; i++) {
        result += "/" + encodeURIComponent(path[i]);
      }
      return result.slice(1); // chops the leading slash
    };

    /**
     * Checks the passed value to discover if it's a Fluidinfo "primitive"
     * type. See
     * the Fluidinfo docs for more
     * information on Fluidinfo's types.
     *
     * @param value A value whose "type" needs identifying
     * @return A boolean indication if the value is a Fluidinfo primitive type.
     */
    utils.isPrimitive = function(value) {
      // check the easy type matches first
      var valueType = typeof(value);
      var primitiveTypes = ["number", "string", "boolean"];
      for(i=0; i<primitiveTypes.length; i++) {
        if(valueType === primitiveTypes[i]) {
          return true;
        }
      }
      // A null value is also a primitive
      if(value===null) {
        return true;
      }
      // check for an array (potential set) and validate it only contains
      // strings (currently multi-type arrays are not allowed)
      if(utils.isArray(value)) {
        for(i=0; i<value.length; i++) {
          memberType = typeof(value[i]);
          if(memberType !== "string") {
            return false;
          }
        }
        return true;
      }
      // value hasn't matched any of the primitive checks
      return false;
    }

    /**
     * Given the options describing a request, will return the most appropriate
     * MIME to set as the value for the Content-Type header
     *
     * @param options {Object} Contains various parameters describing the
     * request.
     * @return the most appropriate MIME to use of null if not appropriate /
     * required.
     */
    utils.detectContentType = function(options) {
      // a "PUT" to objects/ or about/ endpoints means dealing with the MIME
      // of a tag-value. If no MIME type is passed in the options objects
      // then check if the value is a Fluidinfo primitive type. Otherwise
      // complain.
      var result = null;
      if(options.type==="PUT" && (options.url.match("^objects\/") || options.url.match("^about\/"))) {
        if(options.contentType){
          result = options.contentType;
        } else if (api.utils.isPrimitive(options.data)) {
          result = "application/vnd.fluiddb.value+json";
        } else {
          throw { name: "ValueError", message: "Must supply Content-Type"};
        }
      } else if (options.data) {
        // all other requests to the API that have payloads will be passing JSON
        result = "application/json";
      }
      return result;
    }

    var api = new Object();

    api.utils = utils;

    /**
     * Makes an appropriate AJAX request to Fluidinfo
     *
     * @private
     * @param options {Object} Contains various parameters for the call.
     */
    api.ajax = function(options){
      options.contentType = api.utils.detectContentType(options);
      if(api.utils.isArray(options.url)) {
        options.url = api.utils.encodeURL(options.url);
      }
      options.url = session.baseURL+options.url;
      options.async = options.async || true;
      options.beforeSend = function(xhrObj){
        if(session.authorizationToken != undefined){
          xhrObj.setRequestHeader("Authorization","Basic " + session.authorizationToken);
        };
        if(options.contentType) {
          xhrObj.setRequestHeader("Content-Type", options.contentType);
        }
      };
      options.processData = false;
      $.ajax(options);
    }

    /**
     * Makes an HTTP GET call to the Fluidinfo API
     *
     */
    api.get = function(options){
      options.type = "GET";
      options.data = null;
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP POST call to the Fluidinfo API
     *
     */
    api.post = function(options){
      options.type = "POST";
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP PUT call to the Fluidinfo API
     *
     */
    api.put = function(options){
      options.type = "PUT";
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP DELETE call to the Fluidinfo API
     *
     */
    api.delete = function(options){
      options.type = "DELETE";
      options.data = null;
      session.api.ajax(options);
    }

    /**
     * Makes an HTTP HEAD call to the Fluidinfo API
     *
     */
    api.head = function(options){
      options.type = "HEAD";
      options.data = null;
      session.api.ajax(options);
    }

    session.api = api;

    return session;
}
<|endoftext|>