<commit_before>/* FileSaver.js
 * A saveAs() FileSaver implementation.
 * 2013-01-23
 *
 * By Eli Grey, http://eligrey.com
 * License: X11/MIT
 *   See LICENSE.md
 */

/*global self */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
  plusplus: true */

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

var saveAs = saveAs
  || (navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
  || (function(view) {
	"use strict";
	var
		  doc = view.document
		  // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
		, get_URL = function() {
			return view.URL || view.webkitURL || view;
		}
		, URL = view.URL || view.webkitURL || view
		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
		, can_use_save_link =  !view.externalHost && "download" in save_link
		, click = function(node) {
			var event = doc.createEvent("MouseEvents");
			event.initMouseEvent(
				"click", true, false, view, 0, 0, 0, 0, 0
				, false, false, false, false, 0, null
			);
			node.dispatchEvent(event);
		}
		, webkit_req_fs = view.webkitRequestFileSystem
		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
		, throw_outside = function (ex) {
			(view.setImmediate || view.setTimeout)(function() {
				throw ex;
			}, 0);
		}
		, force_saveable_type = "application/octet-stream"
		, fs_min_size = 0
		, deletion_queue = []
		, process_deletion_queue = function() {
			var i = deletion_queue.length;
			while (i--) {
				var file = deletion_queue[i];
				if (typeof file === "string") { // file is an object URL
					URL.revokeObjectURL(file);
				} else { // file is a File
					file.remove();
				}
			}
			deletion_queue.length = 0; // clear queue
		}
		, dispatch = function(filesaver, event_types, event) {
			event_types = [].concat(event_types);
			var i = event_types.length;
			while (i--) {
				var listener = filesaver["on" + event_types[i]];
				if (typeof listener === "function") {
					try {
						listener.call(filesaver, event || filesaver);
					} catch (ex) {
						throw_outside(ex);
					}
				}
			}
		}
		, FileSaver = function(blob, name) {
			// First try a.download, then web filesystem, then object URLs
			var
				  filesaver = this
				, type = blob.type
				, blob_changed = false
				, object_url
				, target_view
				, get_object_url = function() {
					var object_url = get_URL().createObjectURL(blob);
					deletion_queue.push(object_url);
					return object_url;
				}
				, dispatch_all = function() {
					dispatch(filesaver, "writestart progress write writeend".split(" "));
				}
				// on any filesys errors revert to saving with object URLs
				, fs_error = function() {
					// don't create more object URLs than needed
					if (blob_changed || !object_url) {
						object_url = get_object_url(blob);
					}
					if (target_view) {
						target_view.location.href = object_url;
					} else {
                        window.open(object_url, "_blank");
                    }
					filesaver.readyState = filesaver.DONE;
					dispatch_all();
				}
				, abortable = function(func) {
					return function() {
						if (filesaver.readyState !== filesaver.DONE) {
							return func.apply(this, arguments);
						}
					};
				}
				, create_if_not_found = {create: true, exclusive: false}
				, slice
			;
			filesaver.readyState = filesaver.INIT;
			if (!name) {
				name = "download";
			}
			if (can_use_save_link) {
				object_url = get_object_url(blob);
				save_link.href = object_url;
				save_link.download = name;
				click(save_link);
				filesaver.readyState = filesaver.DONE;
				dispatch_all();
				return;
			}
			// Object and web filesystem URLs have a problem saving in Google Chrome when
			// viewed in a tab, so I force save with application/octet-stream
			// http://code.google.com/p/chromium/issues/detail?id=91158
			if (view.chrome && type && type !== force_saveable_type) {
				slice = blob.slice || blob.webkitSlice;
				blob = slice.call(blob, 0, blob.size, force_saveable_type);
				blob_changed = true;
			}
			// Since I can't be sure that the guessed media type will trigger a download
			// in WebKit, I append .download to the filename.
			// https://bugs.webkit.org/show_bug.cgi?id=65440
			if (webkit_req_fs && name !== "download") {
				name += ".download";
			}
			if (type === force_saveable_type || webkit_req_fs) {
				target_view = view;
			}
			if (!req_fs) {
				fs_error();
				return;
			}
			fs_min_size += blob.size;
			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
					var save = function() {
						dir.getFile(name, create_if_not_found, abortable(function(file) {
							file.createWriter(abortable(function(writer) {
								writer.onwriteend = function(event) {
									target_view.location.href = file.toURL();
									deletion_queue.push(file);
									filesaver.readyState = filesaver.DONE;
									dispatch(filesaver, "writeend", event);
								};
								writer.onerror = function() {
									var error = writer.error;
									if (error.code !== error.ABORT_ERR) {
										fs_error();
									}
								};
								"writestart progress write abort".split(" ").forEach(function(event) {
									writer["on" + event] = filesaver["on" + event];
								});
								writer.write(blob);
								filesaver.abort = function() {
									writer.abort();
									filesaver.readyState = filesaver.DONE;
								};
								filesaver.readyState = filesaver.WRITING;
							}), fs_error);
						}), fs_error);
					};
					dir.getFile(name, {create: false}, abortable(function(file) {
						// delete file if it already exists
						file.remove();
						save();
					}), abortable(function(ex) {
						if (ex.code === ex.NOT_FOUND_ERR) {
							save();
						} else {
							fs_error();
						}
					}));
				}), fs_error);
			}), fs_error);
		}
		, FS_proto = FileSaver.prototype
		, saveAs = function(blob, name) {
			return new FileSaver(blob, name);
		}
	;
	FS_proto.abort = function() {
		var filesaver = this;
		filesaver.readyState = filesaver.DONE;
		dispatch(filesaver, "abort");
	};
	FS_proto.readyState = FS_proto.INIT = 0;
	FS_proto.WRITING = 1;
	FS_proto.DONE = 2;

	FS_proto.error =
	FS_proto.onwritestart =
	FS_proto.onprogress =
	FS_proto.onwrite =
	FS_proto.onabort =
	FS_proto.onerror =
	FS_proto.onwriteend =
		null;

	view.addEventListener("unload", process_deletion_queue, false);
	return saveAs;
}(self));

if (typeof module !== 'undefined') module.exports = saveAs;
<commit_msg>adapt FileSaver.js to allow including it as a content script in Firefox for Android add-ons<commit_after>/* FileSaver.js
 * A saveAs() FileSaver implementation.
 * 2013-01-23
 *
 * By Eli Grey, http://eligrey.com
 * License: X11/MIT
 *   See LICENSE.md
 */

/*global self */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
  plusplus: true */

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

var saveAs = saveAs
  || (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
  || (function(view) {
	"use strict";
	var
		  doc = view.document
		  // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
		, get_URL = function() {
			return view.URL || view.webkitURL || view;
		}
		, URL = view.URL || view.webkitURL || view
		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
		, can_use_save_link =  !view.externalHost && "download" in save_link
		, click = function(node) {
			var event = doc.createEvent("MouseEvents");
			event.initMouseEvent(
				"click", true, false, view, 0, 0, 0, 0, 0
				, false, false, false, false, 0, null
			);
			node.dispatchEvent(event);
		}
		, webkit_req_fs = view.webkitRequestFileSystem
		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
		, throw_outside = function (ex) {
			(view.setImmediate || view.setTimeout)(function() {
				throw ex;
			}, 0);
		}
		, force_saveable_type = "application/octet-stream"
		, fs_min_size = 0
		, deletion_queue = []
		, process_deletion_queue = function() {
			var i = deletion_queue.length;
			while (i--) {
				var file = deletion_queue[i];
				if (typeof file === "string") { // file is an object URL
					URL.revokeObjectURL(file);
				} else { // file is a File
					file.remove();
				}
			}
			deletion_queue.length = 0; // clear queue
		}
		, dispatch = function(filesaver, event_types, event) {
			event_types = [].concat(event_types);
			var i = event_types.length;
			while (i--) {
				var listener = filesaver["on" + event_types[i]];
				if (typeof listener === "function") {
					try {
						listener.call(filesaver, event || filesaver);
					} catch (ex) {
						throw_outside(ex);
					}
				}
			}
		}
		, FileSaver = function(blob, name) {
			// First try a.download, then web filesystem, then object URLs
			var
				  filesaver = this
				, type = blob.type
				, blob_changed = false
				, object_url
				, target_view
				, get_object_url = function() {
					var object_url = get_URL().createObjectURL(blob);
					deletion_queue.push(object_url);
					return object_url;
				}
				, dispatch_all = function() {
					dispatch(filesaver, "writestart progress write writeend".split(" "));
				}
				// on any filesys errors revert to saving with object URLs
				, fs_error = function() {
					// don't create more object URLs than needed
					if (blob_changed || !object_url) {
						object_url = get_object_url(blob);
					}
					if (target_view) {
						target_view.location.href = object_url;
					} else {
                        window.open(object_url, "_blank");
                    }
					filesaver.readyState = filesaver.DONE;
					dispatch_all();
				}
				, abortable = function(func) {
					return function() {
						if (filesaver.readyState !== filesaver.DONE) {
							return func.apply(this, arguments);
						}
					};
				}
				, create_if_not_found = {create: true, exclusive: false}
				, slice
			;
			filesaver.readyState = filesaver.INIT;
			if (!name) {
				name = "download";
			}
			if (can_use_save_link) {
				object_url = get_object_url(blob);
				// FF for Android has a nasty garbage collection mechanism
				// that turns all objects that are not pure javascript into 'deadObject'
				// this means `doc` and `save_link` are unusable and need to be recreated
				// `view` is usable though:
				doc = view.document;
				save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
				save_link.href = object_url;
				save_link.download = name;
				var event = doc.createEvent("MouseEvents");
				event.initMouseEvent(
					"click", true, false, view, 0, 0, 0, 0, 0
					, false, false, false, false, 0, null
				);
				save_link.dispatchEvent(event);
				filesaver.readyState = filesaver.DONE;
				dispatch_all();
				return;
			}
			// Object and web filesystem URLs have a problem saving in Google Chrome when
			// viewed in a tab, so I force save with application/octet-stream
			// http://code.google.com/p/chromium/issues/detail?id=91158
			if (view.chrome && type && type !== force_saveable_type) {
				slice = blob.slice || blob.webkitSlice;
				blob = slice.call(blob, 0, blob.size, force_saveable_type);
				blob_changed = true;
			}
			// Since I can't be sure that the guessed media type will trigger a download
			// in WebKit, I append .download to the filename.
			// https://bugs.webkit.org/show_bug.cgi?id=65440
			if (webkit_req_fs && name !== "download") {
				name += ".download";
			}
			if (type === force_saveable_type || webkit_req_fs) {
				target_view = view;
			}
			if (!req_fs) {
				fs_error();
				return;
			}
			fs_min_size += blob.size;
			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
					var save = function() {
						dir.getFile(name, create_if_not_found, abortable(function(file) {
							file.createWriter(abortable(function(writer) {
								writer.onwriteend = function(event) {
									target_view.location.href = file.toURL();
									deletion_queue.push(file);
									filesaver.readyState = filesaver.DONE;
									dispatch(filesaver, "writeend", event);
								};
								writer.onerror = function() {
									var error = writer.error;
									if (error.code !== error.ABORT_ERR) {
										fs_error();
									}
								};
								"writestart progress write abort".split(" ").forEach(function(event) {
									writer["on" + event] = filesaver["on" + event];
								});
								writer.write(blob);
								filesaver.abort = function() {
									writer.abort();
									filesaver.readyState = filesaver.DONE;
								};
								filesaver.readyState = filesaver.WRITING;
							}), fs_error);
						}), fs_error);
					};
					dir.getFile(name, {create: false}, abortable(function(file) {
						// delete file if it already exists
						file.remove();
						save();
					}), abortable(function(ex) {
						if (ex.code === ex.NOT_FOUND_ERR) {
							save();
						} else {
							fs_error();
						}
					}));
				}), fs_error);
			}), fs_error);
		}
		, FS_proto = FileSaver.prototype
		, saveAs = function(blob, name) {
			return new FileSaver(blob, name);
		}
	;
	FS_proto.abort = function() {
		var filesaver = this;
		filesaver.readyState = filesaver.DONE;
		dispatch(filesaver, "abort");
	};
	FS_proto.readyState = FS_proto.INIT = 0;
	FS_proto.WRITING = 1;
	FS_proto.DONE = 2;

	FS_proto.error =
	FS_proto.onwritestart =
	FS_proto.onprogress =
	FS_proto.onwrite =
	FS_proto.onabort =
	FS_proto.onerror =
	FS_proto.onwriteend =
		null;

	view.addEventListener("unload", process_deletion_queue, false);
	return saveAs;
}(typeof self !== 'undefined' ? self : typeof this !== 'undefined' && this.content ? this.content : null));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window

if (typeof module !== 'undefined') module.exports = saveAs;
<|endoftext|>


<commit_before>/**
 * Cloud9 Python support
 *
 * @copyright 2015, Ajax.org B.V.
 */
define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "language", "jsonalyzer", "settings",
        "preferences", "preferences.experimental", "c9"
    ];
    main.provides = ["language.python"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var language = imports.language;
        var jsonalyzer = imports["jsonalyzer"];
        var experimental = imports["preferences.experimental"];
        var prefs = imports.preferences;
        var settings = imports.settings;
        var c9 = imports.c9;
        var plugin = new Plugin("Ajax.org", main.consumes);
        var jediServer = require("text!./server/jedi_server.py").replace(/ {4}/g, " ");
        var launchCommand = require("text!./server/launch_command.sh").replace(/ +/g, " ");

        var enabled = experimental.addExperiment("python_worker", false, "Language/Python Code Completion");

        plugin.on("load", function() {
            jsonalyzer.registerWorkerHandler("plugins/c9.ide.language.python/worker/python_jsonalyzer");

            prefs.add({
                "Project": {
                    "Language Support" : {
                        position: 800,
                        "Python Version" : {
                            position: 300,
                            type: "dropdown",
                            path: "project/python/@version",
                            items: [
                                { caption: "Python 2", value: "python2" },
                                { caption: "Python 3", value: "python3" },
                            ]
                        },
                    }
                }
            }, plugin);

            settings.on("read", function(e) {
                settings.setDefaults("project/python", [
                    ["version", "python2"]
                ]);
            }, plugin);

            if (!enabled)
                return;

            language.registerLanguageHandler("plugins/c9.ide.language.python/worker/python_completer", function(err, handler) {
                if (err) return console.error(err);
                language.registerLanguageHandler("plugins/c9.ide.language.python/worker/python_linter", function(err, handler) {
                    if (err) return console.error(err);
                    var version = settings.get("project/python/@version");
                    handler.emit("set_python_version", version);
                    handler.emit("set_python_scripts", { jediServer: jediServer, launchCommand: launchCommand, ssh: c9.ssh });

                    settings.on("project/python", function(e) {
                        handler.emit("set_python_version", version);
                    }, plugin);
                });
            });
        });

        plugin.on("unload", function() {
            jsonalyzer.unregisterWorkerHandler("plugins/c9.ide.language.python/worker/python_jsonalyzer");
            language.unregisterLanguageHandler("plugins/c9.ide.language.python/worker/python_completer");
            language.unregisterLanguageHandler("plugins/c9.ide.language.python/worker/python_linter");
        });

        /** @ignore */
        register(null, {
            "language.python": plugin
        });
    }
});<commit_msg>Fix event emitter invocation regression<commit_after>/**
 * Cloud9 Python support
 *
 * @copyright 2015, Ajax.org B.V.
 */
define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "language", "jsonalyzer", "settings",
        "preferences", "preferences.experimental", "c9"
    ];
    main.provides = ["language.python"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var language = imports.language;
        var jsonalyzer = imports["jsonalyzer"];
        var experimental = imports["preferences.experimental"];
        var prefs = imports.preferences;
        var settings = imports.settings;
        var c9 = imports.c9;
        var plugin = new Plugin("Ajax.org", main.consumes);
        var jediServer = require("text!./server/jedi_server.py").replace(/ {4}/g, " ");
        var launchCommand = require("text!./server/launch_command.sh").replace(/ +/g, " ");

        var enabled = experimental.addExperiment("python_worker", false, "Language/Python Code Completion");

        plugin.on("load", function() {
            jsonalyzer.registerWorkerHandler("plugins/c9.ide.language.python/worker/python_jsonalyzer");

            prefs.add({
                "Project": {
                    "Language Support" : {
                        position: 800,
                        "Python Version" : {
                            position: 300,
                            type: "dropdown",
                            path: "project/python/@version",
                            items: [
                                { caption: "Python 2", value: "python2" },
                                { caption: "Python 3", value: "python3" },
                            ]
                        },
                    }
                }
            }, plugin);

            settings.on("read", function(e) {
                settings.setDefaults("project/python", [
                    ["version", "python2"]
                ]);
            }, plugin);

            if (!enabled)
                return;

            language.registerLanguageHandler("plugins/c9.ide.language.python/worker/python_completer", function(err, handler) {
                if (err) return console.error(err);
                language.registerLanguageHandler("plugins/c9.ide.language.python/worker/python_linter", function(err, handler2) {
                    if (err) return console.error(err);
                    var version = settings.get("project/python/@version");
                    handler.emit("set_python_version", version);
                    handler.emit("set_python_scripts", { jediServer: jediServer, launchCommand: launchCommand, ssh: c9.ssh });

                    settings.on("project/python", function(e) {
                        handler.emit("set_python_version", version);
                    }, plugin);
                });
            });
        });

        plugin.on("unload", function() {
            jsonalyzer.unregisterWorkerHandler("plugins/c9.ide.language.python/worker/python_jsonalyzer");
            language.unregisterLanguageHandler("plugins/c9.ide.language.python/worker/python_completer");
            language.unregisterLanguageHandler("plugins/c9.ide.language.python/worker/python_linter");
        });

        /** @ignore */
        register(null, {
            "language.python": plugin
        });
    }
});<|endoftext|>


<commit_before>// vim:ts=4:sts=4:sw=4:
// this module can be loaded both as a CommonJS module and
// as a browser script.  If included as a script, it constructs
// a Q_COMM global property with its API and requires
// Q and UUID to be provided before its execution
// by the epynomous scripts/modules.
(function (definition) {
    var global = this;
    // RequireJS
    if (typeof define === "function") {
        define(function (require, exports) {
            definition(require, exports);
        });
    // CommonJS
    } else if (typeof exports === "object") {
        definition(require, exports);
    // <script>
    } else {
        definition(
            function (id) {
                return global[id.toUpperCase()];
            },
            Q_COMM = {}
        );
    }
})
(function (require, exports) {

var Q = require("q");
var UUID = require("uuid");

function debug() {
    //typeof console !== "undefined" && console.log.apply(console, arguments);
}

var rootId = "";

var has = Object.prototype.hasOwnProperty;

/**
 * @param connection
 * @param local
 */
exports.Connection = Connection;
function Connection(connection, local, options) {
    connection = adapt(connection);
    options = options || {};
    var makeId = options.makeId || function () {
        return UUID.generate();
    };
    var locals = Lru(options.max || Infinity);

    var debugKey = Math.floor(Math.random() * 256).toString(16);
    function _debug() {
        debug.apply(null, [debugKey].concat(Array.prototype.slice.call(arguments)));
    }

    // message reciever loop
    Q.when(connection.get(), get).end();
    function get(message) {
        _debug("message receiver loop", message);
        Q.when(connection.get(), get).end();
        receive(message);
    }

    // message receiver
    function receive(message) {
        message = JSON.parse(message);
        _debug(message);
        if (!receivers[message.type])
            return; // ignore bad message types
        if (!locals.has(message.to))
            return; // ignore messages to non-existant or forgotten promises
        receivers[message.type](message);
    }

    // message receiver handlers by message type
    var receivers = {
        "resolve": function (message) {
            if (locals.has(message.to)) {
                resolveLocal(message.to, decode(message.resolution));
            }
        },
        // a "send" message forwards messages from a remote
        // promise to a local promise.
        "send": function (message) {

            // forward the message to the local promise,
            // which will return a response promise
            var local = locals.get(message.to).promise;
            var response = Q.send.apply(
                void 0,
                [local, message.op].concat(decode(message.args))
            );

            // connect the local response promise with the
            // remote response promise:

            // determine whether the response promise
            // resolves to a "ref" promise or a "master"
            // promise.  "ref" promises get resolved and can
            // be observed remotely with a "when" call, but
            // "master" promises only forward their messages.
            // "master" promises are distinguishable from other
            // promises because they respond to an "isMaster"
            // message with a resolution instead of a
            // rejection.
            var isMaster = Q.send(response, 'isMaster');
            Q.when(isMaster, function () {
                // if it is a def, it will respond, don't
                // set up a when listener on the other side,
                // just instruct the other peer to forward
                // messages to our local response promise.
                connection.put(JSON.stringify({
                    "type": "resolve",
                    "to": message.from,
                    "resolution": encode(response)
                }));
            }, function () {
                // if the value is ever resolved, send the
                // fully resolved value across the wire
                Q.when(response, function (resolution) {
                    connection.put(JSON.stringify({
                        "type": "resolve",
                        "to": message.from,
                        "resolution": encode(resolution)
                    }));
                }, function (reason) {
                    // otherwise, transmit a rejection
                    connection.put(JSON.stringify({
                        "type": "resolve",
                        "to": message.from,
                        "resolution": {"!": encode(reason)}
                    }));
                });
            })
            .end();

        }
    }

    // construct a local promise, such that it can
    // be resolved later by a remote message
    function makeLocal(id) {
        if (locals.has(id)) {
            return locals.get(id).promise;
        } else {
            var deferred = Q.defer();
            locals.set(id, deferred);
            return deferred.promise;
        }
    }

    // a utility for resolving the local promise
    // for a given identifier.
    function resolveLocal(id, value) {
        _debug('resolve local', id, value);
        locals.get(id).resolve(value);
    }

    // makes a promise that will send all of its events to a
    // remote object.
    function makeRemote(id) {
        return Q.makePromise({
        }, function (op) {
            var localId = makeId();
            var response = makeLocal(localId);
            var args = Array.prototype.slice.call(arguments, 1);
            _debug('sending ' + op, args);
            connection.put(JSON.stringify({
                "type": "send",
                "to": id,
                "from": localId,
                "op": op,
                "args": encode(args)
            }));
            return response;
        });
    }

    // serializes an object tree, encoding promises such
    // that JSON.stringify on the result will produce
    // "QSON": serialized promise objects.
    function encode(object) {
        if (Q.isPromise(object)) {
            var id = makeId();
            makeLocal(id);
            resolveLocal(id, object);
            return {"@": id};
        } else if (Array.isArray(object)) {
            return object.map(encode);
        } else if (typeof object === "object") {
            var result = {};
            for (var name in object) {
                if (has.call(object, name)) {
                    result[name] = encode(object[name]);
                }
            }
            return result;
        } else {
            return object;
        }
    }

    // decodes QSON
    function decode(object) {
        if (!object) {
            return object;
        } else if (object['!']) {
            return Q.reject(object['!']);
        } else if (object['@']) {
            return makeRemote(object['@']);
        } else if (Array.isArray(object)) {
            return object.map(decode);
        } else if (typeof object === 'object') {
            var newObject = {};
            for (var key in object) {
                if (has.call(object, key)) {
                    var newKey = key;
                    /* TODO mirror in encode
                    if (/^[!@]+$/.exec(key))
                        newKey = key.substring(1);
                    */
                    newObject[newKey] = decode(object[key]);
                }
            }
            return newObject;
        } else {
            return object;
        }
    }

    // a peer-to-peer promise connection is symmetric: both
    // the local and remote side have a "root" promise
    // object. On each side, the respective remote object is
    // returned, and the object passed as an argument to
    // Connection is used as the local object.  The identifier of
    // the root object is an empty-string by convention.
    // All other identifiers are numbers.
    makeLocal(rootId);
    resolveLocal(rootId, local);
    return makeRemote(rootId);

}

// Coerces a Worker to a Connection
// Idempotent: Passes Connections through unaltered
function adapt(port) {
    var send = port.postMessage || port.send;
    if (!send)
        return port; // it's presumably a connection object already
    // Message ports have a start method; call it to make sure
    // that messages get sent.
    port.start && port.start();
    // onmessage is one thing common between WebSocket and
    // WebWorker message ports.
    var queue = Queue();
    port.onmessage = function (event) {
        queue.put(event.data);
    };
    var close = function () {
        port.close && port.close();
        return queue.close();
    };
    if (port.send) {
        // WebSockets have a "send" method, indicating
        // that we cannot send until the connection has
        // opened.  We change the send method into a
        // promise for the send method, resolved after
        // the connection opens, rejected if it closes
        // before it opens.
        var deferred = Q.defer();
        send = deferred.promise;
        port.addEventListener("open", function () {
            deferred.resolve(port.send);
        });
        port.addEventListener("close", function () {
            queue.close();
            deferred.reject("Connection closed.");
        });
    }
    return {
        "get": queue.get,
        "put": function (message) {
            return Q.invoke(send, "call", port, message);
        },
        "close": close,
        "closed": queue.closed
    };
}

exports.Queue = Queue;
function Queue() {
    var ends = Q.defer();
    var closed = Q.defer();
    return {
        "put": function (value) {
            var next = Q.defer();
            ends.resolve({
                "head": value,
                "tail": next.promise
            });
            ends.resolve = next.resolve;
        },
        "get": function () {
            var result = Q.get(ends.promise, "head");
            ends.promise = Q.get(ends.promise, "tail");
            return Q.when(result, null, function (reason) {
                closed.resolve();
                return Q.reject(reason);
            });
        },
        "closed": closed.promise,
        "close": function (reason) {
            var end = {"head": Q.reject(reason)};
            end.tail = end;
            ends.resolve(end);
            return closed.promise;
        }
    };
}

// Least recently used. Caches up to maximum number of key: value pairs.
// Has a similar API to WeakMap, but avoids leaking by dropping, which
// will lead to broken promises on the remote side
var hasOwn = Object.prototype.hasOwnProperty;
function Lru(maxLength) {
    if (!maxLength)
        throw new Error("LRU cache must be constructed with a maximum length.");
    var map = {};
    var length = 0;

    var head = {};
    head.next = head;
    head.prev = head;
    function remove(node) {
        delete map[node.key];
        node.prev.next = node.next;
        node.next.prev = node.prev;
        length--;
    }
    function insert(node) {
        map[node.key] = node;
        var prev = head.prev;
        head.prev = node;
        node.prev = prev;
        prev.next = node;
        node.next = head;
        length++;
        if (length > maxLength)
            remove(head.next);
    }

    function get(key) {
        if (!hasOwn.call(map, key))
            throw new ValueError("LRU cache does not contain that key.");
        var node = map[key];
        remove(node);
        insert(node);
        return node.value;
    }
    function set(key, value) {
        var node;
        if (map[key]) {
            node = map[key];
            node.value = value;
            remove(node);
        } else {
            node = {};
            node.key = key;
            node.value = value;
        }
        insert(node);
    }
    function del(key) {
        var node = map[key];
        delete map[key];
        remove(node);
    }
    function has(key) {
        return hasOwn.call(map, key);
    }

    function toSource() {
        return '[LRU ' + length + ' ' +
            Object.keys(map).map(function (key) {
                var node = map[key];
                return (
                    (node.prev.key || '@') +
                    '<-' + key + ':' + node.value + '->' +
                    (node.next.key || '@')
                );
            }).join(' ') + ']';
    }

    return {
        "get": get,
        "set": set,
        "delete": del,
        "has": has,
        "toSource": toSource,
        "toString": toString
    }
}

// boilerplate that permits this module to be used as a
// <script> in less-than-ideal situations.
});
<commit_msg>Fix zero padding in debug identifiers.<commit_after>// vim:ts=4:sts=4:sw=4:
// this module can be loaded both as a CommonJS module and
// as a browser script.  If included as a script, it constructs
// a Q_COMM global property with its API and requires
// Q and UUID to be provided before its execution
// by the epynomous scripts/modules.
(function (definition) {
    var global = this;
    // RequireJS
    if (typeof define === "function") {
        define(function (require, exports) {
            definition(require, exports);
        });
    // CommonJS
    } else if (typeof exports === "object") {
        definition(require, exports);
    // <script>
    } else {
        definition(
            function (id) {
                return global[id.toUpperCase()];
            },
            Q_COMM = {}
        );
    }
})
(function (require, exports) {

var Q = require("q");
var UUID = require("uuid");

function debug() {
    //typeof console !== "undefined" && console.log.apply(console, arguments);
}

var rootId = "";

var has = Object.prototype.hasOwnProperty;

/**
 * @param connection
 * @param local
 */
exports.Connection = Connection;
function Connection(connection, local, options) {
    connection = adapt(connection);
    options = options || {};
    var makeId = options.makeId || function () {
        return UUID.generate();
    };
    var locals = Lru(options.max || Infinity);

    var debugKey = Math.random().toString(16).slice(2, 4);
    function _debug() {
        debug.apply(null, [debugKey].concat(Array.prototype.slice.call(arguments)));
    }

    // message reciever loop
    Q.when(connection.get(), get).end();
    function get(message) {
        _debug("message receiver loop", message);
        Q.when(connection.get(), get).end();
        receive(message);
    }

    // message receiver
    function receive(message) {
        message = JSON.parse(message);
        _debug(message);
        if (!receivers[message.type])
            return; // ignore bad message types
        if (!locals.has(message.to))
            return; // ignore messages to non-existant or forgotten promises
        receivers[message.type](message);
    }

    // message receiver handlers by message type
    var receivers = {
        "resolve": function (message) {
            if (locals.has(message.to)) {
                resolveLocal(message.to, decode(message.resolution));
            }
        },
        // a "send" message forwards messages from a remote
        // promise to a local promise.
        "send": function (message) {

            // forward the message to the local promise,
            // which will return a response promise
            var local = locals.get(message.to).promise;
            var response = Q.send.apply(
                void 0,
                [local, message.op].concat(decode(message.args))
            );

            // connect the local response promise with the
            // remote response promise:

            // determine whether the response promise
            // resolves to a "ref" promise or a "master"
            // promise.  "ref" promises get resolved and can
            // be observed remotely with a "when" call, but
            // "master" promises only forward their messages.
            // "master" promises are distinguishable from other
            // promises because they respond to an "isMaster"
            // message with a resolution instead of a
            // rejection.
            var isMaster = Q.send(response, 'isMaster');
            Q.when(isMaster, function () {
                // if it is a def, it will respond, don't
                // set up a when listener on the other side,
                // just instruct the other peer to forward
                // messages to our local response promise.
                connection.put(JSON.stringify({
                    "type": "resolve",
                    "to": message.from,
                    "resolution": encode(response)
                }));
            }, function () {
                // if the value is ever resolved, send the
                // fully resolved value across the wire
                Q.when(response, function (resolution) {
                    connection.put(JSON.stringify({
                        "type": "resolve",
                        "to": message.from,
                        "resolution": encode(resolution)
                    }));
                }, function (reason) {
                    // otherwise, transmit a rejection
                    connection.put(JSON.stringify({
                        "type": "resolve",
                        "to": message.from,
                        "resolution": {"!": encode(reason)}
                    }));
                });
            })
            .end();

        }
    }

    // construct a local promise, such that it can
    // be resolved later by a remote message
    function makeLocal(id) {
        if (locals.has(id)) {
            return locals.get(id).promise;
        } else {
            var deferred = Q.defer();
            locals.set(id, deferred);
            return deferred.promise;
        }
    }

    // a utility for resolving the local promise
    // for a given identifier.
    function resolveLocal(id, value) {
        _debug('resolve local', id, value);
        locals.get(id).resolve(value);
    }

    // makes a promise that will send all of its events to a
    // remote object.
    function makeRemote(id) {
        return Q.makePromise({
        }, function (op) {
            var localId = makeId();
            var response = makeLocal(localId);
            var args = Array.prototype.slice.call(arguments, 1);
            _debug('sending ' + op, args);
            connection.put(JSON.stringify({
                "type": "send",
                "to": id,
                "from": localId,
                "op": op,
                "args": encode(args)
            }));
            return response;
        });
    }

    // serializes an object tree, encoding promises such
    // that JSON.stringify on the result will produce
    // "QSON": serialized promise objects.
    function encode(object) {
        if (Q.isPromise(object)) {
            var id = makeId();
            makeLocal(id);
            resolveLocal(id, object);
            return {"@": id};
        } else if (Array.isArray(object)) {
            return object.map(encode);
        } else if (typeof object === "object") {
            var result = {};
            for (var name in object) {
                if (has.call(object, name)) {
                    result[name] = encode(object[name]);
                }
            }
            return result;
        } else {
            return object;
        }
    }

    // decodes QSON
    function decode(object) {
        if (!object) {
            return object;
        } else if (object['!']) {
            return Q.reject(object['!']);
        } else if (object['@']) {
            return makeRemote(object['@']);
        } else if (Array.isArray(object)) {
            return object.map(decode);
        } else if (typeof object === 'object') {
            var newObject = {};
            for (var key in object) {
                if (has.call(object, key)) {
                    var newKey = key;
                    /* TODO mirror in encode
                    if (/^[!@]+$/.exec(key))
                        newKey = key.substring(1);
                    */
                    newObject[newKey] = decode(object[key]);
                }
            }
            return newObject;
        } else {
            return object;
        }
    }

    // a peer-to-peer promise connection is symmetric: both
    // the local and remote side have a "root" promise
    // object. On each side, the respective remote object is
    // returned, and the object passed as an argument to
    // Connection is used as the local object.  The identifier of
    // the root object is an empty-string by convention.
    // All other identifiers are numbers.
    makeLocal(rootId);
    resolveLocal(rootId, local);
    return makeRemote(rootId);

}

// Coerces a Worker to a Connection
// Idempotent: Passes Connections through unaltered
function adapt(port) {
    var send = port.postMessage || port.send;
    if (!send)
        return port; // it's presumably a connection object already
    // Message ports have a start method; call it to make sure
    // that messages get sent.
    port.start && port.start();
    // onmessage is one thing common between WebSocket and
    // WebWorker message ports.
    var queue = Queue();
    port.onmessage = function (event) {
        queue.put(event.data);
    };
    var close = function () {
        port.close && port.close();
        return queue.close();
    };
    if (port.send) {
        // WebSockets have a "send" method, indicating
        // that we cannot send until the connection has
        // opened.  We change the send method into a
        // promise for the send method, resolved after
        // the connection opens, rejected if it closes
        // before it opens.
        var deferred = Q.defer();
        send = deferred.promise;
        port.addEventListener("open", function () {
            deferred.resolve(port.send);
        });
        port.addEventListener("close", function () {
            queue.close();
            deferred.reject("Connection closed.");
        });
    }
    return {
        "get": queue.get,
        "put": function (message) {
            return Q.invoke(send, "call", port, message);
        },
        "close": close,
        "closed": queue.closed
    };
}

exports.Queue = Queue;
function Queue() {
    var ends = Q.defer();
    var closed = Q.defer();
    return {
        "put": function (value) {
            var next = Q.defer();
            ends.resolve({
                "head": value,
                "tail": next.promise
            });
            ends.resolve = next.resolve;
        },
        "get": function () {
            var result = Q.get(ends.promise, "head");
            ends.promise = Q.get(ends.promise, "tail");
            return Q.when(result, null, function (reason) {
                closed.resolve();
                return Q.reject(reason);
            });
        },
        "closed": closed.promise,
        "close": function (reason) {
            var end = {"head": Q.reject(reason)};
            end.tail = end;
            ends.resolve(end);
            return closed.promise;
        }
    };
}

// Least recently used. Caches up to maximum number of key: value pairs.
// Has a similar API to WeakMap, but avoids leaking by dropping, which
// will lead to broken promises on the remote side
var hasOwn = Object.prototype.hasOwnProperty;
function Lru(maxLength) {
    if (!maxLength)
        throw new Error("LRU cache must be constructed with a maximum length.");
    var map = {};
    var length = 0;

    var head = {};
    head.next = head;
    head.prev = head;
    function remove(node) {
        delete map[node.key];
        node.prev.next = node.next;
        node.next.prev = node.prev;
        length--;
    }
    function insert(node) {
        map[node.key] = node;
        var prev = head.prev;
        head.prev = node;
        node.prev = prev;
        prev.next = node;
        node.next = head;
        length++;
        if (length > maxLength)
            remove(head.next);
    }

    function get(key) {
        if (!hasOwn.call(map, key))
            throw new ValueError("LRU cache does not contain that key.");
        var node = map[key];
        remove(node);
        insert(node);
        return node.value;
    }
    function set(key, value) {
        var node;
        if (map[key]) {
            node = map[key];
            node.value = value;
            remove(node);
        } else {
            node = {};
            node.key = key;
            node.value = value;
        }
        insert(node);
    }
    function del(key) {
        var node = map[key];
        delete map[key];
        remove(node);
    }
    function has(key) {
        return hasOwn.call(map, key);
    }

    function toSource() {
        return '[LRU ' + length + ' ' +
            Object.keys(map).map(function (key) {
                var node = map[key];
                return (
                    (node.prev.key || '@') +
                    '<-' + key + ':' + node.value + '->' +
                    (node.next.key || '@')
                );
            }).join(' ') + ']';
    }

    return {
        "get": get,
        "set": set,
        "delete": del,
        "has": has,
        "toSource": toSource,
        "toString": toString
    }
}

// boilerplate that permits this module to be used as a
// <script> in less-than-ideal situations.
});
<|endoftext|>


<commit_before>// GENIUS -> https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
var typeOf = function(obj) {
  return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}

// SET-UP
function rabbak(selector) {
  if (selector.charAt(0) === "#") {
    selector = selector.slice(1, selector.length);
    this.target = document.getElementById(selector);
  } else if (selector.charAt(0) === ".") {
    selector = selector.slice(1, selector.length);
    this.target = document.getElementsByClassName(selector);
  }
}

///////// FUNCTIONS /////////
// OBJECT OUTER WIDTH
rabbak.prototype.outerWidth = function(includeMargin) {
  if (typeOf(this.target) === "htmlcollection") {
    var widths = [];
    for (i = 0; i < this.target.length; i++) {
      if (includeMargin == null || false) {
        widths.push(this.target[i].getBoundingClientRect().width);
      } else if (includeMargin) {
        var elementHorizontalMargin = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-right'));
        widths.push(this.target[i].getBoundingClientRect().width + elementHorizontalMargin);
      }
    }
    return widths;
  } else {
    if (includeMargin == null || false) {
      return this.target.getBoundingClientRect().width;
    } else if (includeMargin) {
      var elementHorizontalMargin = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-right'));
      return this.target.getBoundingClientRect().width + elementHorizontalMargin;
    }
  }
}
// OBJECT OUTER HEIGHT
rabbak.prototype.outerHeight = function(includeMargin) {
  if (typeOf(this.target) === "htmlcollection") {
    var heights = [];
    for (i = 0; i < this.target.length; i++) {
      if (includeMargin == null || false) {
        heights.push(this.target[i].getBoundingClientRect().height);
      } else if (includeMargin) {
        var elementVerticalMargin = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-bottom'));
        heights.push(this.target[i].getBoundingClientRect().height + elementVerticalMargin);
      }
    }
    return heights;
  } else {
    if (includeMargin == null || false) {
      return this.target.getBoundingClientRect().height;
    } else if (includeMargin) {
      var elementVerticalMargin = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-bottom'));
      return this.target.getBoundingClientRect().height + elementVerticalMargin;
    }
  }
}
// OBJECT INNER WIDTH
rabbak.prototype.innerWidth = function() {
  if (typeOf(this.target) === "htmlcollection") {
    var widths = [];
    for (i = 0; i < this.target.length; i++) {
      var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-right'));
      widths.push(this.target[i].getBoundingClientRect().width - elementHorizontalBorder);
    }
    return widths;
  } else {
    var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-right'));
    return this.target.offsetWidth - elementHorizontalBorder;
  }
}
// OBJECT INNER HEIGHT
rabbak.prototype.innerHeight = function() {
  if (typeOf(this.target) === "htmlcollection") {
    var heights = [];
    for (i = 0; i < this.target.length; i++) {
      var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-bottom'));
      heights.push(this.target[i].getBoundingClientRect().height - elementVerticalBorder);
    }
    return heights;
  } else {
    var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-bottom'));
    return this.target.offsetHeight - elementVerticalBorder;
  }
}
// OBJECT WIDTH
rabbak.prototype.width = function() {
  if (typeOf(this.target) === "htmlcollection") {
    var widths = [];
    for (i = 0; i < this.target.length; i++) {
      var elementHorizontalPadding = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-right'));
      var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-right'));
      widths.push(this.target[i].getBoundingClientRect().width - elementHorizontalPadding - elementHorizontalBorder);
    }
    return widths;
  } else {
    var elementHorizontalPadding = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-right'));
    var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-right'));
    return this.target.getBoundingClientRect().width - elementHorizontalPadding - elementHorizontalBorder;
  }
}
// OBJECT HEIGHT
rabbak.prototype.height = function() {
  if (typeOf(this.target) === "htmlcollection") {
    var heights = [];
    for (i = 0; i < this.target.length; i++) {
      var elementVerticalPadding = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-bottom'));
      var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-bottom'));
      heights.push(this.target[i].getBoundingClientRect().height - elementVerticalPadding - elementVerticalBorder);
    }
    return heights;
  } else {
    var elementVerticalPadding = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-bottom'));
    var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-bottom'));
    return this.target.getBoundingClientRect().height - elementVerticalPadding - elementVerticalBorder;
  }
}
// ADD CLASS
rabbak.prototype.addClass = function(className) {
  if (typeOf(this.target) === "htmlcollection") {
    for (i = 0; i < this.target.length; i++) {
      this.target[i].classList.add(className)
    }
    return;
  }
  this.target.classList.add(className);
}
<commit_msg>Tidy code<commit_after>// GENIUS -> https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
var typeOf = function (obj) {
  return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}
// SET-UP
function rabbak (selector) {
  if (selector.charAt(0) === '#') {
    selector = selector.slice(1, selector.length);
    this.target = document.getElementById(selector);
  } else if (selector.charAt(0) === '.') {
    selector = selector.slice(1, selector.length);
    this.target = document.getElementsByClassName(selector);
  }
}

// FUNCTIONS
// OBJECT OUTER WIDTH
rabbak.prototype.outerWidth = function(includeMargin) {
  if (typeOf(this.target) === 'htmlcollection') {
    var widths = [];
    for (var i = 0; i < this.target.length; i++) {
      if (includeMargin == null || false) {
        widths.push(this.target[i].getBoundingClientRect().width);
      } else if (includeMargin) {
        var elementHorizontalMargin = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-right'));
        widths.push(this.target[i].getBoundingClientRect().width + elementHorizontalMargin);
      }
    }
    return widths;
  } else {
    if (includeMargin == null || false) {
      return this.target.getBoundingClientRect().width;
    } else if (includeMargin) {
      var elementHorizontalMargin = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-right'));
      return this.target.getBoundingClientRect().width + elementHorizontalMargin;
    }
  }
}
// OBJECT OUTER HEIGHT
rabbak.prototype.outerHeight = function (includeMargin) {
  if (typeOf(this.target) === 'htmlcollection') {
    var heights = [];
    for (var i = 0; i < this.target.length; i++) {
      if (includeMargin == null || false) {
        heights.push(this.target[i].getBoundingClientRect().height);
      } else if (includeMargin) {
        var elementVerticalMargin = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('margin-bottom'));
        heights.push(this.target[i].getBoundingClientRect().height + elementVerticalMargin);
      }
    }
    return heights;
  } else {
    if (includeMargin == null || false) {
      return this.target.getBoundingClientRect().height;
    } else if (includeMargin) {
      var elementVerticalMargin = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('margin-bottom'));
      return this.target.getBoundingClientRect().height + elementVerticalMargin;
    }
  }
}
// OBJECT INNER WIDTH
rabbak.prototype.innerWidth = function() {
  if (typeOf(this.target) === 'htmlcollection') {
    var widths = [];
    for (var i = 0; i < this.target.length; i++) {
      var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-right'));
      widths.push(this.target[i].getBoundingClientRect().width - elementHorizontalBorder);
    }
    return widths;
  } else {
    var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-right'));
    return this.target.offsetWidth - elementHorizontalBorder;
  }
}
// OBJECT INNER HEIGHT
rabbak.prototype.innerHeight = function() {
  if (typeOf(this.target) === 'htmlcollection') {
    var heights = [];
    for (var i = 0; i < this.target.length; i++) {
      var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-bottom'));
      heights.push(this.target[i].getBoundingClientRect().height - elementVerticalBorder);
    }
    return heights;
  } else {
    var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-bottom'));
    return this.target.offsetHeight - elementVerticalBorder;
  }
}
// OBJECT WIDTH
rabbak.prototype.width = function() {
  if (typeOf(this.target) === 'htmlcollection') {
    var widths = [];
    for (var i = 0; i < this.target.length; i++) {
      var elementHorizontalPadding = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-right'));
      var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-right'));
      widths.push(this.target[i].getBoundingClientRect().width - elementHorizontalPadding - elementHorizontalBorder);
    }
    return widths;
  } else {
    var elementHorizontalPadding = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-right'));
    var elementHorizontalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-left')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-right'));
    return this.target.getBoundingClientRect().width - elementHorizontalPadding - elementHorizontalBorder;
  }
}
// OBJECT HEIGHT
rabbak.prototype.height = function() {
  if (typeOf(this.target) === 'htmlcollection') {
    var heights = [];
    for (var i = 0; i < this.target.length; i++) {
      var elementVerticalPadding = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('padding-bottom'));
      var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target[i], null).getPropertyValue('border-bottom'));
      heights.push(this.target[i].getBoundingClientRect().height - elementVerticalPadding - elementVerticalBorder);
    }
    return heights;
  } else {
    var elementVerticalPadding = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('padding-bottom'));
    var elementVerticalBorder = parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-top')) + parseFloat(window.getComputedStyle(this.target, null).getPropertyValue('border-bottom'));
    return this.target.getBoundingClientRect().height - elementVerticalPadding - elementVerticalBorder;
  }
}
// ADD CLASS
rabbak.prototype.addClass = function (className) {
  if (typeOf(this.target) === 'htmlcollection') {
    for (var i = 0; i < this.target.length; i++) {
      this.target[i].classList.add(className)
    }
    return;
  }
  this.target.classList.add(className);
}
<|endoftext|>


<commit_before>/**
 * Description
 * @method name
 * @param {} sdfsd
 * @return sdfsd
 */
function name(sdfsd){
    return sdfsd;
}
<commit_msg>init test<commit_after>/**
 * Updating the description, making sure the same code isnt duplicated.
 * @method name
 * @param {} sdfsd
 * @return sdfsd
 */
function name(sdfsd){
    return sdfsd;
}
<|endoftext|>


<commit_before>/**
 * Cobalt Fragment type
 * A Fragment is a combination of plain text and a list of annotations.
 * The list of annotations will always be sorted by start and then end position.
 * A fragment is immutable, all operations on it return a new fragment.
 */

import CobaltAnnotationList from './cobalt.annotationlist.js';
import cobalt from './cobalt.js';

/**
 * Non public text api.
 */
var cobaltText = {
    delete: function(text, range) {
        range.ranges.reverse().forEach(function(re) {
            text = text.slice(0, re.start) + text.slice(re.end);
        });
        return text;
    },
    copy: function(text, range) {
        var result = '';
        range.ranges.forEach(function(re) {
            result += text.slice(re.start, re.end);
        });
        return result;
    },
    insert: function(text, insert, position) {
        return text.slice(0,position) + insert + text.slice(position);
    }
};

/**
 * Non public annotations api.
 */
var cobaltAnnotations = {
    delete: function(annotations, range) {
        var result = [];
        annotations.forEach(function(ann) {
            ann = ann.delete(range);
            if ( ann ) {
                result.push(ann);
            }
        });
        return new CobaltAnnotationList(result);
    },
    copy: function(annotations, range) {
        var result = [];
        annotations.forEach(function(ann) {
            if ( ann.range.overlaps(range) ) {
                var copyAnn = cobalt.annotation( ann.range.intersect(range), ann.tag );
                result.push( copyAnn );
            }
        });
        return new CobaltAnnotationList(result);
    },
    insert: function(annotations, position, size ) {
        var result = [];
        var insertedRange = cobalt.range(position, position+size);
        annotations.forEach(function(ann) {
            var foo = ann.range.insert( insertedRange );
            result.push( cobalt.annotation( foo, ann.tag ) );
        });
        return new CobaltAnnotationList(result);
    }
};

function parseFragment(fragment) {
    var text = '', annotations = '';
    if (fragment) {
        var info = cobalt.mime.decode( fragment );
        info.parts.forEach( function(part) {
            switch( part.headers['content-type'] ) {
                case 'text/plain' :
                    text = part.message;
                  break;
                case 'text/hope' :
                    annotations = part.message;
                break;
            }
        });
    }
    return { text: text, annotations: annotations };
}

/**
 * Cobalt Fragment type and public api.
 */
class CobaltFragment {
    constructor( text, annotations ) {
        if ( text instanceof CobaltFragment ) {
            return text;
        }
        if ( typeof annotations == 'undefined') {
            var result = parseFragment(text);
            if ( result.text || result.annotations) {
                text = result.text;
                annotations = result.annotations;
            }
        }
        this.text = '' + text;
        this.annotations = new CobaltAnnotationList( annotations );
        Object.freeze(this);
    }

    get length() {
        return this.text.length;
    }

    /**
     * Returns a new fragment where the given range is deleted (cut).
     */
    delete( range ) {
        range = cobalt.range(range);
        return new CobaltFragment(
            cobaltText.delete( this.text, range ),
            cobaltAnnotations.delete( this.annotations, range )
        );
    }

    /**
     * Returns a new fragment with only the parts that overlap the given range.
     * The new fragment adds all text slices matching the range together as one
     * single text. All annotations are moved/cut to match this new text.
     */
    copy( range ) {
        range = cobalt.range(range);
        var annCopy = cobaltAnnotations.copy(
                this.annotations,
                range
            );
        // now move everything to the left;
        var inverseRange = range.invert(this.text.length);
        annCopy = annCopy.delete(range.invert(this.text.length));
        return new CobaltFragment(
            cobaltText.copy( this.text, range ),
            annCopy
        );
    }

    /**
     * Returns a combined new fragment with the inserted fragment text and annotations
     * inserted at the given position.
     */
    insert( range, fragment ) {
        range = cobalt.range(range);
        fragment = new CobaltFragment(fragment);
        var result = this.delete(range);
        if (!fragment || !fragment.text.length) {
            return result;
        }
        return new CobaltFragment(
            cobaltText.insert( result.text, fragment.text, range.start ),
            cobaltAnnotations
            .insert( result.annotations, range.start, fragment.text.length )
            .apply( cobaltAnnotations.insert( fragment.annotations, 0, range.start ) )
        );
    }

    /**
     * Returns a new range, with the given range/tag or annotation or annotationlist applied
     */
    apply( range, tag ) {
        range = cobalt.range(range);
        return new CobaltFragment(
            this.text,
            this.annotations.apply( range, tag )
        );
    }

    /**
     * Returns a new range, with the given range/tag or annotation or annotationlist removed
     */
    remove( range, tag ) {
        range = cobalt.range(range);
        return new CobaltFragment(
            this.text,
            this.annotations.remove( range, tag )
        );
    }

    /**
     * Search through the text using a regular expression or string. Returns a range object
     * encompassing all matches.
     */
    search( searchRe ) {
        function escapeRegExp(str) {
            return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
        }
        if ( typeof(searchRe) === 'string' ) {
            searchRe = new RegExp(escapeRegExp(searchRe), 'g');
        }
        var result = [];
        if (this.text.length) {
            var match, lastIndex;
            while (match = searchRe.exec(this.text)) {
                result.push([match.index, searchRe.lastIndex]);
                if (lastIndex==searchRe.lastIndex) {
                    searchRe.lastIndex++;
                }
                lastIndex = searchRe.lastIndex;
            }
        }
        return cobalt.range(result);
    }

    /**
     * Search through the annotations using a query language approximately like css selectors.
     * Returns a range object encompassing all matched ranges.
     */
    query( selector ) {
        return this.annotations.query(selector, this.text.length);
    }

    /*
     * Returns a mime encoded string with the text and annotations.
     */
    toString() {
        return cobalt.mime.encode( [
            'Content-type: text/plain\n\n' + this.text,
            'Content-type: text/cobalt\n\n' + this.annotations
        ]);
    }

    /**
     * Parses a mime encoded cobalt document, and returns a new CobaltFragment
     * TODO: allow mimeDoc as single parameter of cobaltDocument(), not fragment.
     * cobaltDocument can allow more than one cobaltFragment or other types of
     * subdocuments.
     */
    parse(mimeDoc) {
        var mime = cobalt.mime.decode(mimeDoc);
        var text = '', annotations = '';
        mime.parts.forEach(p => {
            if (p.headers && p.headers['content-type']) {
                if (p.headers['content-type']=='text/plain') {
                    text = p.message;
                } else if (p.headers['content-type']=='text/cobalt') {
                    annotations = p.message;
                }
            }
        });
        return new CobaltFragment(text, annotations);
    }
};

export default function(text, annotations) {
    if ( text instanceof CobaltFragment ) {
        // because CobaltFragment is immutable, we can just return it.
        return text;
    } else {
        return new CobaltFragment(text, annotations);
    }
};
<commit_msg>fixed parsing mime encoded fragment in the constructor, removed spurious parse method<commit_after>/**
 * Cobalt Fragment type
 * A Fragment is a combination of plain text and a list of annotations.
 * The list of annotations will always be sorted by start and then end position.
 * A fragment is immutable, all operations on it return a new fragment.
 */

import CobaltAnnotationList from './cobalt.annotationlist.js';
import cobalt from './cobalt.js';

/**
 * Non public text api.
 */
var cobaltText = {
    delete: function(text, range) {
        range.ranges.reverse().forEach(function(re) {
            text = text.slice(0, re.start) + text.slice(re.end);
        });
        return text;
    },
    copy: function(text, range) {
        var result = '';
        range.ranges.forEach(function(re) {
            result += text.slice(re.start, re.end);
        });
        return result;
    },
    insert: function(text, insert, position) {
        return text.slice(0,position) + insert + text.slice(position);
    }
};

/**
 * Non public annotations api.
 */
var cobaltAnnotations = {
    delete: function(annotations, range) {
        var result = [];
        annotations.forEach(function(ann) {
            ann = ann.delete(range);
            if ( ann ) {
                result.push(ann);
            }
        });
        return new CobaltAnnotationList(result);
    },
    copy: function(annotations, range) {
        var result = [];
        annotations.forEach(function(ann) {
            if ( ann.range.overlaps(range) ) {
                var copyAnn = cobalt.annotation( ann.range.intersect(range), ann.tag );
                result.push( copyAnn );
            }
        });
        return new CobaltAnnotationList(result);
    },
    insert: function(annotations, position, size ) {
        var result = [];
        var insertedRange = cobalt.range(position, position+size);
        annotations.forEach(function(ann) {
            var foo = ann.range.insert( insertedRange );
            result.push( cobalt.annotation( foo, ann.tag ) );
        });
        return new CobaltAnnotationList(result);
    }
};

function parseFragment(fragment) {
    var text = '', annotations = '';
    if (fragment) {
        var info = cobalt.mime.decode( fragment );
        info.parts.forEach( function(part) {
            switch( part.headers['content-type'] ) {
                case 'text/plain' :
                    text = part.message;
                  break;
                case 'text/cobalt' :
                    annotations = part.message;
                break;
            }
        });
    }
    return { text: text, annotations: annotations };
}

/**
 * Cobalt Fragment type and public api.
 */
class CobaltFragment {
    constructor( text, annotations ) {
        if ( text instanceof CobaltFragment ) {
            return text;
        }
        if ( typeof annotations == 'undefined') {
            var result = parseFragment(text);
            if ( result.text || result.annotations) {
                text = result.text;
                annotations = result.annotations;
            }
        }
        this.text = '' + text;
        this.annotations = new CobaltAnnotationList( annotations );
        Object.freeze(this);
    }

    get length() {
        return this.text.length;
    }

    /**
     * Returns a new fragment where the given range is deleted (cut).
     */
    delete( range ) {
        range = cobalt.range(range);
        return new CobaltFragment(
            cobaltText.delete( this.text, range ),
            cobaltAnnotations.delete( this.annotations, range )
        );
    }

    /**
     * Returns a new fragment with only the parts that overlap the given range.
     * The new fragment adds all text slices matching the range together as one
     * single text. All annotations are moved/cut to match this new text.
     */
    copy( range ) {
        range = cobalt.range(range);
        var annCopy = cobaltAnnotations.copy(
                this.annotations,
                range
            );
        // now move everything to the left;
        var inverseRange = range.invert(this.text.length);
        annCopy = annCopy.delete(range.invert(this.text.length));
        return new CobaltFragment(
            cobaltText.copy( this.text, range ),
            annCopy
        );
    }

    /**
     * Returns a combined new fragment with the inserted fragment text and annotations
     * inserted at the given position.
     */
    insert( range, fragment ) {
        range = cobalt.range(range);
        fragment = new CobaltFragment(fragment);
        var result = this.delete(range);
        if (!fragment || !fragment.text.length) {
            return result;
        }
        return new CobaltFragment(
            cobaltText.insert( result.text, fragment.text, range.start ),
            cobaltAnnotations
            .insert( result.annotations, range.start, fragment.text.length )
            .apply( cobaltAnnotations.insert( fragment.annotations, 0, range.start ) )
        );
    }

    /**
     * Returns a new range, with the given range/tag or annotation or annotationlist applied
     */
    apply( range, tag ) {
        range = cobalt.range(range);
        return new CobaltFragment(
            this.text,
            this.annotations.apply( range, tag )
        );
    }

    /**
     * Returns a new range, with the given range/tag or annotation or annotationlist removed
     */
    remove( range, tag ) {
        range = cobalt.range(range);
        return new CobaltFragment(
            this.text,
            this.annotations.remove( range, tag )
        );
    }

    /**
     * Search through the text using a regular expression or string. Returns a range object
     * encompassing all matches.
     */
    search( searchRe ) {
        function escapeRegExp(str) {
            return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
        }
        if ( typeof(searchRe) === 'string' ) {
            searchRe = new RegExp(escapeRegExp(searchRe), 'g');
        }
        var result = [];
        if (this.text.length) {
            var match, lastIndex;
            while (match = searchRe.exec(this.text)) {
                result.push([match.index, searchRe.lastIndex]);
                if (lastIndex==searchRe.lastIndex) {
                    searchRe.lastIndex++;
                }
                lastIndex = searchRe.lastIndex;
            }
        }
        return cobalt.range(result);
    }

    /**
     * Search through the annotations using a query language approximately like css selectors.
     * Returns a range object encompassing all matched ranges.
     */
    query( selector ) {
        return this.annotations.query(selector, this.text.length);
    }

    /*
     * Returns a mime encoded string with the text and annotations.
     */
    toString() {
        return cobalt.mime.encode( [
            'Content-type: text/plain\n\n' + this.text,
            'Content-type: text/cobalt\n\n' + this.annotations
        ]);
    }

};

export default function(text, annotations) {
    if ( text instanceof CobaltFragment ) {
        // because CobaltFragment is immutable, we can just return it.
        return text;
    } else {
        return new CobaltFragment(text, annotations);
    }
};
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 */

var _ = require('lodash');

var common = require('./common');
var publicPersonInfo = common.publicPersonInfo;

var patch = function(mock, api) {
  var data = mock.data;
  var getParam = mock.getParam;
  var getDelayFor = mock.getDelayFor;

  function getPendingInvitation(options) {
    return _.find(data.confirmations, function(confirmation) {
      var match = (
        confirmation.type === 'invite' &&
        confirmation.invitedBy === options.from &&
        confirmation.status === 'pending'
      );

      if (options.to) {
        match = match && confirmation.userid === options.to;
      }
      if (options.email) {
        match = match && confirmation.email === options.email;
      }

      return match;
    });
  }

  var setPermissions = common.setPermissions.bind(null, data);

  api.invitation.getReceived = function(callback) {
    api.log('[mock] GET /invitations/received');

    setTimeout(function() {
      var invitations = _.filter(data.confirmations, function(confirmation) {
        return (
          confirmation.type === 'invite' &&
          confirmation.userid === api.userId &&
          confirmation.status === 'pending'
        );
      });

      invitations = _.map(invitations, function(invitation) {
        return _.omit(invitation, 'type', 'status', 'userid', 'email');
      });

      invitations = _.map(invitations, function(invitation) {
        var fromUserId = invitation.invitedBy;
        var from = data.users[fromUserId];
        from = _.cloneDeep(from);
        from = publicPersonInfo(from);
        return _.assign({}, _.omit(invitation, 'invitedBy'), {
          from: from
        });
      });

      callback(null, invitations);
    }, getDelayFor('api.invitation.getReceived'));
  };

  api.invitation.accept = function(fromUserId, callback) {
    api.log('[mock] POST /invitations/from/' + fromUserId + '/accept');

    setTimeout(function() {
      var userId = api.userId;

      var invitation = getPendingInvitation({
        from: fromUserId,
        to: userId
      });

      if (!invitation) {
        var err = {status: 404, response: 'Not found'};
        return callback(err);
      }

      setPermissions(fromUserId, userId, invitation.permissions);
      // Note: we are mutating the object in the mock data here
      invitation.status = 'confirmed';

      callback();
    }, getDelayFor('api.invitation.accept'));
  };

  api.invitation.dismiss = function(fromUserId, callback) {
    api.log('[mock] POST /invitations/from/' + fromUserId + '/dismiss');

    setTimeout(function() {
      var userId = api.userId;

      var invitation = getPendingInvitation({
        from: fromUserId,
        to: userId
      });

      if (!invitation) {
        var err = {status: 404, response: 'Not found'};
        return callback(err);
      }

      // Note: we are mutating the object in the mock data here
      invitation.status = 'dismissed';

      callback();
    }, getDelayFor('api.invitation.dismiss'));
  };

  api.invitation.getSent = function(callback) {
    api.log('[mock] GET /invitations/sent');

    setTimeout(function() {
      var invitations = _.filter(data.confirmations, function(confirmation) {
        return (
          confirmation.type === 'invite' &&
          confirmation.invitedBy === api.userId &&
          confirmation.status === 'pending'
        );
      });

      invitations = _.map(invitations, function(invitation) {
        return _.omit(invitation, 'type', 'status', 'userid', 'invitedBy');
      });

      callback(null, invitations);
    }, getDelayFor('api.invitation.getSent'));
  };

  api.invitation.send = function(toEmail, permissions, callback) {
    api.log('[mock] POST /invitations');

    setTimeout(function() {
      var err;
      var userId = api.userId;
      var existingInvitation = _.find(data.confirmations, function(confirmation) {
        return (
          confirmation.type === 'invite' &&
          confirmation.email === toEmail &&
          confirmation.invitedBy === userId
        );
      });

      if (existingInvitation) {
        err = {status: 409, response: 'Invitation already sent to that email'};
        return callback(err);
      }

      var invitation = {
        'type': 'invite',
        'status': 'pending',
        'email': toEmail,
        'invitedBy': userId,
        'permissions': permissions
      };

      var existingUser = _.find(data.users, function(user, id) {
        return _.contains(user.emails, toEmail);
      });
      if (existingUser) {
        // Does user matched with email already belong to the group?
        var members = common.getMembersForGroup(data, userId);
        var existingMember = _.find(members, {userid: existingUser.userid});
        if (existingMember) {
          err = {status: 409, response: 'User with that email already part of the group'};
          return callback(err);
        }

        invitation.userid = existingUser.userid;
      }

      data.confirmations.push(invitation);

      invitation = _.pick(invitation, 'email', 'permissions');
      callback(null, invitation);
    }, getDelayFor('api.invitation.send'));
  };

  api.invitation.cancel = function(toEmail, callback) {
    api.log('[mock] POST /invitations/to/' + toEmail + '/cancel');

    setTimeout(function() {
      var userId = api.userId;

      var invitation = getPendingInvitation({
        from: userId,
        email: toEmail
      });

      if (!invitation) {
        var err = {status: 404, response: 'Not found'};
        return callback(err);
      }

      // Note: we are mutating the object in the mock data here
      invitation.status = 'canceled';

      callback();
    }, getDelayFor('api.invitation.cancel'));
  };

  return api;
};

module.exports = patch;
<commit_msg>Add mock api.invitation.getForToken<commit_after>/**
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 */

var _ = require('lodash');

var common = require('./common');
var publicPersonInfo = common.publicPersonInfo;

var invitationTokenSize = 12;

function generateInvitationToken() {
  return common.generateRandomId(invitationTokenSize);
}

var patch = function(mock, api) {
  var data = mock.data;
  var getParam = mock.getParam;
  var getDelayFor = mock.getDelayFor;

  function getPendingInvitation(options) {
    return _.find(data.confirmations, function(confirmation) {
      var match = (
        confirmation.type === 'invite' &&
        confirmation.invitedBy === options.from &&
        confirmation.status === 'pending'
      );

      if (options.to) {
        match = match && confirmation.userid === options.to;
      }
      if (options.email) {
        match = match && confirmation.email === options.email;
      }

      return match;
    });
  }

  function replaceInvitedByWithUser(invitation) {
    var fromUserId = invitation.invitedBy;
    var from = data.users[fromUserId];
    from = _.cloneDeep(from);
    from = publicPersonInfo(from);
    return _.assign({}, _.omit(invitation, 'invitedBy'), {
      from: from
    });
  }

  var setPermissions = common.setPermissions.bind(null, data);

  api.invitation.getReceived = function(callback) {
    api.log('[mock] GET /invitations/received');

    setTimeout(function() {
      var invitations = _.filter(data.confirmations, function(confirmation) {
        return (
          confirmation.type === 'invite' &&
          confirmation.userid === api.userId &&
          confirmation.status === 'pending'
        );
      });

      invitations = _.map(invitations, function(invitation) {
        return _.pick(invitation, 'invitedBy', 'permissions');
      });

      invitations = _.map(invitations, replaceInvitedByWithUser);

      callback(null, invitations);
    }, getDelayFor('api.invitation.getReceived'));
  };

  api.invitation.accept = function(fromUserId, callback) {
    api.log('[mock] POST /invitations/from/' + fromUserId + '/accept');

    setTimeout(function() {
      var userId = api.userId;

      var invitation = getPendingInvitation({
        from: fromUserId,
        to: userId
      });

      if (!invitation) {
        var err = {status: 404, response: 'Not found'};
        return callback(err);
      }

      setPermissions(fromUserId, userId, invitation.permissions);
      // Note: we are mutating the object in the mock data here
      invitation.status = 'confirmed';

      callback();
    }, getDelayFor('api.invitation.accept'));
  };

  api.invitation.dismiss = function(fromUserId, callback) {
    api.log('[mock] POST /invitations/from/' + fromUserId + '/dismiss');

    setTimeout(function() {
      var userId = api.userId;

      var invitation = getPendingInvitation({
        from: fromUserId,
        to: userId
      });

      if (!invitation) {
        var err = {status: 404, response: 'Not found'};
        return callback(err);
      }

      // Note: we are mutating the object in the mock data here
      invitation.status = 'dismissed';

      callback();
    }, getDelayFor('api.invitation.dismiss'));
  };

  api.invitation.getSent = function(callback) {
    api.log('[mock] GET /invitations/sent');

    setTimeout(function() {
      var invitations = _.filter(data.confirmations, function(confirmation) {
        return (
          confirmation.type === 'invite' &&
          confirmation.invitedBy === api.userId &&
          confirmation.status === 'pending'
        );
      });

      invitations = _.map(invitations, function(invitation) {
        return _.pick(invitation, 'email', 'permissions');
      });

      callback(null, invitations);
    }, getDelayFor('api.invitation.getSent'));
  };

  api.invitation.send = function(toEmail, permissions, callback) {
    api.log('[mock] POST /invitations');

    setTimeout(function() {
      var err;
      var userId = api.userId;
      var existingInvitation = _.find(data.confirmations, function(confirmation) {
        return (
          confirmation.type === 'invite' &&
          confirmation.email === toEmail &&
          confirmation.invitedBy === userId
        );
      });

      if (existingInvitation) {
        err = {status: 409, response: 'Invitation already sent to that email'};
        return callback(err);
      }

      var invitation = {
        type: 'invite',
        status: 'pending',
        email: toEmail,
        invitedBy: userId,
        permissions: permissions,
        token: generateInvitationToken()
      };

      var existingUser = _.find(data.users, function(user, id) {
        return _.contains(user.emails, toEmail);
      });
      if (existingUser) {
        // Does user matched with email already belong to the group?
        var members = common.getMembersForGroup(data, userId);
        var existingMember = _.find(members, {userid: existingUser.userid});
        if (existingMember) {
          err = {status: 409, response: 'User with that email already part of the group'};
          return callback(err);
        }

        invitation.userid = existingUser.userid;
      }

      data.confirmations.push(invitation);

      invitation = _.pick(invitation, 'email', 'permissions');
      callback(null, invitation);
    }, getDelayFor('api.invitation.send'));
  };

  api.invitation.cancel = function(toEmail, callback) {
    api.log('[mock] POST /invitations/to/' + toEmail + '/cancel');

    setTimeout(function() {
      var userId = api.userId;

      var invitation = getPendingInvitation({
        from: userId,
        email: toEmail
      });

      if (!invitation) {
        var err = {status: 404, response: 'Not found'};
        return callback(err);
      }

      // Note: we are mutating the object in the mock data here
      invitation.status = 'canceled';

      callback();
    }, getDelayFor('api.invitation.cancel'));
  };

  api.invitation.getForToken = function(token, callback) {
    api.log('[mock] GET /invitations/token/' + token);

    setTimeout(function() {
      var invitation = _.find(data.confirmations, function(confirmation) {
        return (
          confirmation.type === 'invite' &&
          confirmation.token === token &&
          confirmation.status === 'pending'
        );
      });

      if (!invitation) {
        var err = {status: 404, response: 'Not found'};
        return callback(err);
      }

      invitation = replaceInvitedByWithUser(invitation);
      // If invitation was sent to existing user account, add user object
      if (invitation.userid) {
        var user = data.users[invitation.userid];
        user = _.cloneDeep(user);
        user = publicPersonInfo(user);
        invitation = _.assign({}, _.omit(invitation, 'userid'), {
          user: user
        });
      }

      invitation = _.pick(invitation, 'user', 'email', 'from', 'permissions');
      callback(null, invitation);
    }, getDelayFor('api.invitation.getForToken'));
  };

  return api;
};

module.exports = patch;
<|endoftext|>