<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013, Olivier BIOT
 * http://www.melonjs.org
 *
 */
(function () {
    /**
     * a generic 2D Vector Object
     * @class
     * @extends Object
     * @memberOf me
     * @constructor
     * @param {Number} [x=0] x value of the vector
     * @param {Number} [y=0] y value of the vector
     */
    me.Vector2d = Object.extend(
    /** @scope me.Vector2d.prototype */
    {
        /** @ignore */
        init : function (x, y) {
            return this.set(x || 0, y || 0);
        },

        /**
         * set the Vector x and y properties to the given values

         * @name set
         * @memberOf me.Vector2d
         * @function
         * @param {Number} x
         * @param {Number} y
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        set : function (x, y) {
            if (x !== +x || y !== +y) {
                throw new me.Vector2d.Error(
                    "invalid x,y parameters (not a number)"
                );
            }

            /**
             * x value of the vector
             * @public
             * @type Number
             * @name x
             * @memberOf me.Vector2d
             */
            this.x = x;

            /**
             * y value of the vector
             * @public
             * @type Number
             * @name y
             * @memberOf me.Vector2d
             */
            this.y = y;

            return this;
        },

        /**
         * set the Vector x and y properties to 0
         * @name setZero
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        setZero : function () {
            return this.set(0, 0);
        },

        /**
         * set the Vector x and y properties using the passed vector
         * @name setV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        setV : function (v) {
            this.x = v.x;
            this.y = v.y;
            return this;
        },

        /**
         * Add the passed vector to this vector
         * @name add
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        add : function (v) {
            this.x += v.x;
            this.y += v.y;
            return this;
        },

        /**
         * Substract the passed vector to this vector
         * @name sub
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        sub : function (v) {
            this.x -= v.x;
            this.y -= v.y;
            return this;
        },

        /**
         * Multiply this vector values by the given scalar
         * @name scale
         * @memberOf me.Vector2d
         * @function
         * @param {Number} x
         * @param {Number} [y=x]
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        scale : function (x, y) {
            this.x *= x;
            this.y *= typeof (y) !== "undefined" ? y : x;
            return this;
        },

        /**
         * Multiply this vector values by the passed vector
         * @name scaleV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        scaleV : function (v) {
            this.x *= v.x;
            this.y *= v.y;
            return this;
        },

        /**
         * Divide this vector values by the passed value
         * @name div
         * @memberOf me.Vector2d
         * @function
         * @param {Number} value
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        div : function (n) {
            this.x /= n;
            this.y /= n;
            return this;
        },

        /**
         * Update this vector values to absolute values
         * @name abs
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        abs : function () {
            if (this.x < 0) {
                this.x = -this.x;
            }
            if (this.y < 0) {
                this.y = -this.y;
            }
            return this;
        },

        /**
         * Clamp the vector value within the specified value range
         * @name clamp
         * @memberOf me.Vector2d
         * @function
         * @param {Number} low
         * @param {Number} high
         * @return {me.Vector2d} new me.Vector2d
         */
        clamp : function (low, high) {
            return new me.Vector2d(this.x.clamp(low, high), this.y.clamp(low, high));
        },

        /**
         * Clamp this vector value within the specified value range
         * @name clampSelf
         * @memberOf me.Vector2d
         * @function
         * @param {Number} low
         * @param {Number} high
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        clampSelf : function (low, high) {
            this.x = this.x.clamp(low, high);
            this.y = this.y.clamp(low, high);
            return this;
        },

        /**
         * Update this vector with the minimum value between this and the passed vector
         * @name minV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        minV : function (v) {
            this.x = this.x < v.x ? this.x : v.x;
            this.y = this.y < v.y ? this.y : v.y;
            return this;
        },

        /**
         * Update this vector with the maximum value between this and the passed vector
         * @name maxV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        maxV : function (v) {
            this.x = this.x > v.x ? this.x : v.x;
            this.y = this.y > v.y ? this.y : v.y;
            return this;
        },

        /**
         * Floor the vector values
         * @name floor
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        floor : function () {
            return new me.Vector2d(~~this.x, ~~this.y);
        },

        /**
         * Floor this vector values
         * @name floorSelf
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        floorSelf : function () {
            this.x = ~~this.x;
            this.y = ~~this.y;
            return this;
        },

        /**
         * Ceil the vector values
         * @name ceil
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        ceil : function () {
            return new me.Vector2d(Math.ceil(this.x), Math.ceil(this.y));
        },

        /**
         * Ceil this vector values
         * @name ceilSelf
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        ceilSelf : function () {
            this.x = Math.ceil(this.x);
            this.y = Math.ceil(this.y);
            return this;
        },

        /**
         * Negate the vector values
         * @name negate
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        negate : function () {
            return new me.Vector2d(-this.x, -this.y);
        },

        /**
         * Negate this vector values
         * @name negateSelf
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        negateSelf : function () {
            this.x = -this.x;
            this.y = -this.y;
            return this;
        },

        /**
         * Copy the x,y values of the passed vector to this one
         * @name copy
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        copy : function (v) {
            this.x = v.x;
            this.y = v.y;
            return this;
        },

        /**
         * return true if the two vectors are the same
         * @name equals
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Boolean}
         */
        equals : function (v) {
            return ((this.x === v.x) && (this.y === v.y));
        },

        /**
         * return the length (magnitude) of this vector
         * @name length
         * @memberOf me.Vector2d
         * @function
         * @return {Number}
         */
        length : function () {
            return Math.sqrt(this.x * this.x + this.y * this.y);
        },

        /**
         * normalize this vector (scale the vector so that its magnitude is 1)
         * @name normalize
         * @memberOf me.Vector2d
         * @function
         * @return {Number}
         */
        normalize : function () {
            var len = this.length();
            // some limit test
            if (len < Number.MIN_VALUE) {
                return 0.0;
            }
            var invL = 1.0 / len;
            this.x *= invL;
            this.y *= invL;
            return len;
        },

        /**
         * return the dot product of this vector and the passed one
         * @name dotProduct
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Number}
         */
        dotProduct : function (/**me.Vector2d*/ v) {
            return this.x * v.x + this.y * v.y;
        },

        /**
         * return the distance between this vector and the passed one
         * @name distance
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Number}
         */
        distance : function (v) {
            return Math.sqrt((this.x - v.x) * (this.x - v.x) + (this.y - v.y) * (this.y - v.y));
        },

        /**
         * return the angle between this vector and the passed one
         * @name angle
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Number} angle in radians
         */
        angle : function (v) {
            return Math.atan2((v.y - this.y), (v.x - this.x));
        },

        /**
         * return a clone copy of this vector
         * @name clone
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        clone : function () {
            return new me.Vector2d(this.x, this.y);
        },

        /**
         * convert the object to a string representation
         * @name toString
         * @memberOf me.Vector2d
         * @function
         * @return {String}
         */
        toString : function () {
            return "x:" + this.x + ",y:" + this.y;
        }
    });

    /**
     * Base class for Vector2d exception handling.
     * @name Error
     * @memberOf me.Vector2d
     * @constructor
     * @param {String} msg Error message.
     */
    me.Vector2d.Error = me.Error.extend({
        init : function (msg) {
            this._super(me.Error, "init", [ msg ]);
            this.name = "me.Vector2dError";
        }
    });
})();
<commit_msg>Correct class name<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2013, Olivier BIOT
 * http://www.melonjs.org
 *
 */
(function () {
    /**
     * a generic 2D Vector Object
     * @class
     * @extends Object
     * @memberOf me
     * @constructor
     * @param {Number} [x=0] x value of the vector
     * @param {Number} [y=0] y value of the vector
     */
    me.Vector2d = Object.extend(
    /** @scope me.Vector2d.prototype */
    {
        /** @ignore */
        init : function (x, y) {
            return this.set(x || 0, y || 0);
        },

        /**
         * set the Vector x and y properties to the given values

         * @name set
         * @memberOf me.Vector2d
         * @function
         * @param {Number} x
         * @param {Number} y
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        set : function (x, y) {
            if (x !== +x || y !== +y) {
                throw new me.Vector2d.Error(
                    "invalid x,y parameters (not a number)"
                );
            }

            /**
             * x value of the vector
             * @public
             * @type Number
             * @name x
             * @memberOf me.Vector2d
             */
            this.x = x;

            /**
             * y value of the vector
             * @public
             * @type Number
             * @name y
             * @memberOf me.Vector2d
             */
            this.y = y;

            return this;
        },

        /**
         * set the Vector x and y properties to 0
         * @name setZero
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        setZero : function () {
            return this.set(0, 0);
        },

        /**
         * set the Vector x and y properties using the passed vector
         * @name setV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        setV : function (v) {
            this.x = v.x;
            this.y = v.y;
            return this;
        },

        /**
         * Add the passed vector to this vector
         * @name add
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        add : function (v) {
            this.x += v.x;
            this.y += v.y;
            return this;
        },

        /**
         * Substract the passed vector to this vector
         * @name sub
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        sub : function (v) {
            this.x -= v.x;
            this.y -= v.y;
            return this;
        },

        /**
         * Multiply this vector values by the given scalar
         * @name scale
         * @memberOf me.Vector2d
         * @function
         * @param {Number} x
         * @param {Number} [y=x]
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        scale : function (x, y) {
            this.x *= x;
            this.y *= typeof (y) !== "undefined" ? y : x;
            return this;
        },

        /**
         * Multiply this vector values by the passed vector
         * @name scaleV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        scaleV : function (v) {
            this.x *= v.x;
            this.y *= v.y;
            return this;
        },

        /**
         * Divide this vector values by the passed value
         * @name div
         * @memberOf me.Vector2d
         * @function
         * @param {Number} value
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        div : function (n) {
            this.x /= n;
            this.y /= n;
            return this;
        },

        /**
         * Update this vector values to absolute values
         * @name abs
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        abs : function () {
            if (this.x < 0) {
                this.x = -this.x;
            }
            if (this.y < 0) {
                this.y = -this.y;
            }
            return this;
        },

        /**
         * Clamp the vector value within the specified value range
         * @name clamp
         * @memberOf me.Vector2d
         * @function
         * @param {Number} low
         * @param {Number} high
         * @return {me.Vector2d} new me.Vector2d
         */
        clamp : function (low, high) {
            return new me.Vector2d(this.x.clamp(low, high), this.y.clamp(low, high));
        },

        /**
         * Clamp this vector value within the specified value range
         * @name clampSelf
         * @memberOf me.Vector2d
         * @function
         * @param {Number} low
         * @param {Number} high
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        clampSelf : function (low, high) {
            this.x = this.x.clamp(low, high);
            this.y = this.y.clamp(low, high);
            return this;
        },

        /**
         * Update this vector with the minimum value between this and the passed vector
         * @name minV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        minV : function (v) {
            this.x = this.x < v.x ? this.x : v.x;
            this.y = this.y < v.y ? this.y : v.y;
            return this;
        },

        /**
         * Update this vector with the maximum value between this and the passed vector
         * @name maxV
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        maxV : function (v) {
            this.x = this.x > v.x ? this.x : v.x;
            this.y = this.y > v.y ? this.y : v.y;
            return this;
        },

        /**
         * Floor the vector values
         * @name floor
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        floor : function () {
            return new me.Vector2d(~~this.x, ~~this.y);
        },

        /**
         * Floor this vector values
         * @name floorSelf
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        floorSelf : function () {
            this.x = ~~this.x;
            this.y = ~~this.y;
            return this;
        },

        /**
         * Ceil the vector values
         * @name ceil
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        ceil : function () {
            return new me.Vector2d(Math.ceil(this.x), Math.ceil(this.y));
        },

        /**
         * Ceil this vector values
         * @name ceilSelf
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        ceilSelf : function () {
            this.x = Math.ceil(this.x);
            this.y = Math.ceil(this.y);
            return this;
        },

        /**
         * Negate the vector values
         * @name negate
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        negate : function () {
            return new me.Vector2d(-this.x, -this.y);
        },

        /**
         * Negate this vector values
         * @name negateSelf
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        negateSelf : function () {
            this.x = -this.x;
            this.y = -this.y;
            return this;
        },

        /**
         * Copy the x,y values of the passed vector to this one
         * @name copy
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {me.Vector2d} Reference to this object for method chaining
         */
        copy : function (v) {
            this.x = v.x;
            this.y = v.y;
            return this;
        },

        /**
         * return true if the two vectors are the same
         * @name equals
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Boolean}
         */
        equals : function (v) {
            return ((this.x === v.x) && (this.y === v.y));
        },

        /**
         * return the length (magnitude) of this vector
         * @name length
         * @memberOf me.Vector2d
         * @function
         * @return {Number}
         */
        length : function () {
            return Math.sqrt(this.x * this.x + this.y * this.y);
        },

        /**
         * normalize this vector (scale the vector so that its magnitude is 1)
         * @name normalize
         * @memberOf me.Vector2d
         * @function
         * @return {Number}
         */
        normalize : function () {
            var len = this.length();
            // some limit test
            if (len < Number.MIN_VALUE) {
                return 0.0;
            }
            var invL = 1.0 / len;
            this.x *= invL;
            this.y *= invL;
            return len;
        },

        /**
         * return the dot product of this vector and the passed one
         * @name dotProduct
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Number}
         */
        dotProduct : function (/**me.Vector2d*/ v) {
            return this.x * v.x + this.y * v.y;
        },

        /**
         * return the distance between this vector and the passed one
         * @name distance
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Number}
         */
        distance : function (v) {
            return Math.sqrt((this.x - v.x) * (this.x - v.x) + (this.y - v.y) * (this.y - v.y));
        },

        /**
         * return the angle between this vector and the passed one
         * @name angle
         * @memberOf me.Vector2d
         * @function
         * @param {me.Vector2d} v
         * @return {Number} angle in radians
         */
        angle : function (v) {
            return Math.atan2((v.y - this.y), (v.x - this.x));
        },

        /**
         * return a clone copy of this vector
         * @name clone
         * @memberOf me.Vector2d
         * @function
         * @return {me.Vector2d} new me.Vector2d
         */
        clone : function () {
            return new me.Vector2d(this.x, this.y);
        },

        /**
         * convert the object to a string representation
         * @name toString
         * @memberOf me.Vector2d
         * @function
         * @return {String}
         */
        toString : function () {
            return "x:" + this.x + ",y:" + this.y;
        }
    });

    /**
     * Base class for Vector2d exception handling.
     * @name Error
     * @memberOf me.Vector2d
     * @constructor
     * @param {String} msg Error message.
     */
    me.Vector2d.Error = me.Error.extend({
        init : function (msg) {
            this._super(me.Error, "init", [ msg ]);
            this.name = "me.Vector2d.Error";
        }
    });
})();
<|endoftext|>


<commit_before>/* ========================================
 * CORS Planner - jQuery Module
 *
 * jQuery plugin for Module Interactions
 *
 * Author: Wang Zhuochun
 * Last Edit: 17/Nov/2012 11:04 PM
 * ========================================
 * <License>
 * ======================================== */

define(function(require, exports) {

    "use strict";
    /*jshint jquery:true, laxcomma:true, browser:true, maxerr:50*/

    (function($) {

        var _project = "planner_"
          , _pluginName = "modules"
          , $grid = $("#tt-grid")
        // Plugin constructor
          , Plugin = function(element, options) {
            this.$elem = $(element);
            this.init(options);
          };

        Plugin.prototype = {
            constructor: Plugin

            , init: function(opt) {
                this.$method = this.$elem.find(".method");
                this.$info = this.$elem.find(".info");

                // module data
                this.data = opt.data;
                // module id
                this.id = this.data.get("code");
                // clashing modules
                this.clash = [];
                // update ui
                this.updateElem();
                // attach events
                this.attachEvents();
                // bind subscription
                this.subscribeEvents();
                // resize
                this.resize();
            }

            , get: function(key) {
                return this.data.get(key);
            }

            , resize: function() {
                var pivotItems = $("#metro-pivot").find(".pivotItem");
                // width
                this.$info.width(pivotItems.width() - 43);
            }

            , updateElem: function() {
                if (!this.data.is("visible")) {
                    this.showOnTimetable(false);
                }
            }

            , showOnTimetable: function(show, $elem, $this) {
                $elem = $elem || this.$method.find(".timetable i");
                $this = $this || this.$elem;

                if (show) {
                    // open eye, unmark inactive
                    $this.removeClass("inactive");
                    $elem.removeClass("icon-eye-close").addClass("icon-eye-open");
                } else {
                    // close eye, mark inactive
                    $this.addClass("inactive");
                    $elem.removeClass("icon-eye-open").addClass("icon-eye-close");
                }
            }

            , attachEvents: function(item, fun) {
                var self = this;

                // elem events
                this.$elem.on("mouseenter", function() {
                    $grid.find(".slot[id^=" + self.id + "-]").addClass("hover");
                });

                this.$elem.on("mouseleave", function() {
                    $grid.find(".slot[id^=" + self.id + "-]").removeClass("hover");
                });

                // debug info
                this.$elem.on("dblclick", function() {
                    window._debug = window._debug || {};
                    window._debug.module = self.data;

                    if (window.console)
                        window.console.log(self);
                });

                // method events
                this.$method.on("click", ".detail", function() {
                    $.publish("module:detail", self.data);
                    // switch to detail panel
                    $("#metro-pivot").data("controller").goToItemByName("Detail");
                });

                this.$method.on("click", ".timetable", function() {
                    var $this = $(this).find("i");

                    if ($this.hasClass("icon-eye-open")) {
                        // remove slots on timetable
                        $this.removeClass("icon-eye-open").addClass("icon-eye-close");
                        // mark the slot inactive
                        self.$elem.addClass("inactive");
                        // update module status
                        self.data.set("visible", false);
                        // remove all the slots from timetable
                        $grid.find(".slot[id^=" + self.id + "-]").remove();
                    } else {
                        // add slots to timetable
                        $this.removeClass("icon-eye-close").addClass("icon-eye-open");
                        // remove the inactive
                        self.$elem.removeClass("inactive");
                        // update module status
                        self.data.set("visible", true);
                        // allocate the slots on timetable
                        $.publish("grid:module:reallocate", self.data);
                    }
                });

                this.$method.on("click", ".remove", function() {
                    // hide tooltip
                    $(this).tooltip("destroy");
                    // tell all clashing modules
                    self.removeClash();
                    // remove all the slots from timetable
                    $grid.find(".slot[id^=" + self.id + "-]").remove();
                    // check empty rows and remove it
                    $.publish("grid:rows:clearEmpty");
                    // remove the DOM
                    self.$elem.fadeOut(500, function() {
                        // remove DOM itself
                        self.$elem.remove();
                        // publish event
                        $.publish("module:remove", self.id);
                    });
                });
            }

            , subscribeEvents: function() {
                var self = this;

                $.subscribe("module:clean:all", function() {
                    self.$method.find(".remove").trigger("click");
                });

                this.$elem.on("clash.add", function(e, mod) {
                    self.clash.push(mod);
                    self.handleClash();
                });

                this.$elem.on("clash.remove", function(e, mod) {
                    var idx = self.clash.indexOf(mod);

                    if (idx >= 0) {
                        self.clash.splice(idx, 1);
                    }

                    self.handleClash();
                });
            }

            , handleClash: function() {
                if (this.clash.length > 0 && !this.$elem.hasClass("clashing")) {
                    this.$elem.addClass("clashing");
                } else if (this.clash.length === 0) {
                    this.$elem.removeClass("clashing");
                }
            }

            , removeClash: function() {
                var i, len = this.clash.length, $fst, fstId;

                if (len < 0) { return ; }
                // keep the first -> comes with great responsibility
                fstId = this.clash[0];
                // remove self from the first
                $fst = $("#" + fstId).trigger("clash.remove", [this.id]);

                for (i = 1; i < len; i++) {
                    // remove self, and add the first
                    $("#" + this.clash[i])
                        .trigger("clash.remove", [this.id])
                        .trigger("clash.add", [fstId]);
                    // tell the first to remember all other clashes
                    // XXX: can get speed improvement for fst
                    $fst.trigger("clash.add", [this.clash[i]]);
                }
            }
        };

        /* PLUGIN DEFINITION
         * ======================================== */
        $.fn.module = function(options) {
            return this.each(function () {
                if (!$.data(this, _project + _pluginName)) {
                    $.data(this, _project + _pluginName, new Plugin(this, options));
                }
            });
        };

    }(jQuery));

});
<commit_msg>improve clash add and remove<commit_after>/* ========================================
 * CORS Planner - jQuery Module
 *
 * jQuery plugin for Module Interactions
 *
 * Author: Wang Zhuochun
 * Last Edit: 17/Nov/2012 11:04 PM
 * ========================================
 * <License>
 * ======================================== */

define(function(require, exports) {

    "use strict";
    /*jshint jquery:true, laxcomma:true, browser:true, maxerr:50*/

    (function($) {

        var _project = "planner_"
          , _pluginName = "modules"
          , $grid = $("#tt-grid")
        // Plugin constructor
          , Plugin = function(element, options) {
            this.$elem = $(element);
            this.init(options);
          };

        Plugin.prototype = {
            constructor: Plugin

            , init: function(opt) {
                this.$method = this.$elem.find(".method");
                this.$info = this.$elem.find(".info");

                // module data
                this.data = opt.data;
                // module id
                this.id = this.data.get("code");
                // clashing modules
                this.clash = [];
                // update ui
                this.updateElem();
                // attach events
                this.attachEvents();
                // bind subscription
                this.subscribeEvents();
                // resize
                this.resize();
            }

            , get: function(key) {
                return this.data.get(key);
            }

            , resize: function() {
                var pivotItems = $("#metro-pivot").find(".pivotItem");
                // width
                this.$info.width(pivotItems.width() - 43);
            }

            , updateElem: function() {
                if (!this.data.is("visible")) {
                    this.showOnTimetable(false);
                }
            }

            , showOnTimetable: function(show, $elem, $this) {
                $elem = $elem || this.$method.find(".timetable i");
                $this = $this || this.$elem;

                if (show) {
                    // open eye, unmark inactive
                    $this.removeClass("inactive");
                    $elem.removeClass("icon-eye-close").addClass("icon-eye-open");
                } else {
                    // close eye, mark inactive
                    $this.addClass("inactive");
                    $elem.removeClass("icon-eye-open").addClass("icon-eye-close");
                }
            }

            , attachEvents: function(item, fun) {
                var self = this;

                // elem events
                this.$elem.on("mouseenter", function() {
                    $grid.find(".slot[id^=" + self.id + "-]").addClass("hover");
                });

                this.$elem.on("mouseleave", function() {
                    $grid.find(".slot[id^=" + self.id + "-]").removeClass("hover");
                });

                // debug info
                this.$elem.on("dblclick", function() {
                    window._debug = window._debug || {};
                    window._debug.module = self.data;

                    if (window.console)
                        window.console.log(self);
                });

                // method events
                this.$method.on("click", ".detail", function() {
                    $.publish("module:detail", self.data);
                    // switch to detail panel
                    $("#metro-pivot").data("controller").goToItemByName("Detail");
                });

                this.$method.on("click", ".timetable", function() {
                    var $this = $(this).find("i");

                    if ($this.hasClass("icon-eye-open")) {
                        // remove slots on timetable
                        $this.removeClass("icon-eye-open").addClass("icon-eye-close");
                        // mark the slot inactive
                        self.$elem.addClass("inactive");
                        // update module status
                        self.data.set("visible", false);
                        // remove all the slots from timetable
                        $grid.find(".slot[id^=" + self.id + "-]").remove();
                    } else {
                        // add slots to timetable
                        $this.removeClass("icon-eye-close").addClass("icon-eye-open");
                        // remove the inactive
                        self.$elem.removeClass("inactive");
                        // update module status
                        self.data.set("visible", true);
                        // allocate the slots on timetable
                        $.publish("grid:module:reallocate", self.data);
                    }
                });

                this.$method.on("click", ".remove", function() {
                    // hide tooltip
                    $(this).tooltip("destroy");
                    // tell all clashing modules
                    self.removeClash();
                    // remove all the slots from timetable
                    $grid.find(".slot[id^=" + self.id + "-]").remove();
                    // check empty rows and remove it
                    $.publish("grid:rows:clearEmpty");
                    // remove the DOM
                    self.$elem.fadeOut(500, function() {
                        // remove DOM itself
                        self.$elem.remove();
                        // publish event
                        $.publish("module:remove", self.id);
                    });
                });
            }

            , subscribeEvents: function() {
                var self = this;

                $.subscribe("module:clean:all", function() {
                    self.$method.find(".remove").trigger("click");
                });

                this.$elem.on("clash.add", function(e, mod) {
                    if ($.isArray(mod)) {
                        if (mod.length === 0) {
                            return ;
                        }

                        self.clash = self.clash.concat(mod);
                    } else {
                        self.clash.push(mod);
                    }

                    self.handleClash();
                });

                this.$elem.on("clash.remove", function(e, mod) {
                    var idx = self.clash.indexOf(mod);

                    if (idx >= 0) {
                        self.clash.splice(idx, 1);
                    }

                    self.handleClash();
                });
            }

            , handleClash: function() {
                if (this.clash.length > 0 && !this.$elem.hasClass("clashing")) {
                    this.$elem.addClass("clashing");
                } else if (this.clash.length === 0) {
                    this.$elem.removeClass("clashing");
                }
            }

            , removeClash: function() {
                var i, len = this.clash.length, fstId;

                if (len < 0) { return ; }

                // keep the first -> comes with great responsibility
                fstId = this.clash.splice(0, 1)[0];

                // remove self from the first, add other clashes to it
                $("#" + fstId)
                    .trigger("clash.remove", [this.id])
                    .trigger("clash.add", [this.clash]);

                for (i = 1; i < len; i++) {
                    // remove self, and add the first
                    $("#" + this.clash[i])
                        .trigger("clash.remove", [this.id])
                        .trigger("clash.add", [fstId]);
                }
            }
        };

        /* PLUGIN DEFINITION
         * ======================================== */
        $.fn.module = function(options) {
            return this.each(function () {
                if (!$.data(this, _project + _pluginName)) {
                    $.data(this, _project + _pluginName, new Plugin(this, options));
                }
            });
        };

    }(jQuery));

});
<|endoftext|>


<commit_before>/**
 * Tests that a label element is close (DOM-wise) to it's target form element.
 */
quail.components.labelProximity = function(testName, options) {
  quail.html.find(options.options.selector).each(function() {
    var $label = quail.html.find('label[for=' + $(this).attr('id') + ']').first();
    if (!$label.length) {
      quail.testFails(testName, $(this));
    }
    if (!$(this).parent().is($label.parent())) {
      quail.testFails(testName, $(this));
    }
  });
};<commit_msg>The labelProximity component now works.<commit_after>/**
 * Tests that a label element is close (DOM-wise) to it's target form element.
 */
quail.components.labelProximity = function(quail, test, Case, options) {
  var $scope = test.get('$scope');
  $scope.find(options.options.selector).each(function() {
    var $label = $scope.find('label[for=' + $(this).attr('id') + ']').first();
    if (!$label.length) {
      test.add(Case({
        element: this,
        expected: $(this).closest('.quail-test').data('expected'),
        status: 'failed'
      }));
    }
    else if (!$(this).parent().is($label.parent())) {
      test.add(Case({
        element: this,
        expected: $(this).closest('.quail-test').data('expected'),
        status: 'failed'
      }));
    }
    else {
      test.add(Case({
        element: this,
        expected: $(this).closest('.quail-test').data('expected'),
        status: 'passed'
      }));
    }
  });
};
<|endoftext|>


<commit_before>/**
 * Highlighter module for Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core, TextRange and CssClassApplier modules.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Highlighter", function(api, module) {
    api.requireModules( ["CssClassApplier"] );

    var log = log4javascript.getLogger("rangy.Highlighter");
    var dom = api.dom;
    var contains = dom.arrayContains;
    var getBody = dom.getBody;

    // Puts highlights in order, last in document first.
    function compareHighlights(h1, h2) {
        return h1.characterRange.start - h2.characterRange.start;
    }

    var forEach = [].forEach ?
        function(arr, func) {
            arr.forEach(func);
        } :
        function(arr, func) {
            for (var i = 0, len = arr.length; i < len; ++i) {
                func( arr[i] );
            }
        };

    var nextHighlightId = 1;

    /*----------------------------------------------------------------------------------------------------------------*/

    var highlighterTypes = {};

    function HighlighterType(type, converterCreator) {
        this.type = type;
        this.converterCreator = converterCreator
    }

    HighlighterType.prototype.create = function() {
        var converter = this.converterCreator();
        converter.type = this.type;
        return converter;
    };

    function registerHighlighterType(type, converterCreator) {
        highlighterTypes[type] = new HighlighterType(type, converterCreator);
    }

    function getConverter(type) {
        var highlighterType = highlighterTypes[type];
        if (highlighterType instanceof HighlighterType) {
            return highlighterType.create();
        } else {
            throw new Error("Highlighter type '" + type + "' is not valid")
        }
    }

    api.registerHighlighterType = registerHighlighterType;

    /*----------------------------------------------------------------------------------------------------------------*/

    function CharacterRange(start, end) {
        this.start = start;
        this.end = end;
    }

    CharacterRange.prototype = {
        intersects: function(charRange) {
            return this.start < charRange.end && this.end > charRange.start;
        },

        union: function(charRange) {
            return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
        },

        intersection: function(charRange) {
            return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
        },

        toString: function() {
            return "[CharacterRange(" + this.start + ", " + this.end + ")]";
        }
    };

    CharacterRange.fromCharacterRange = function(charRange) {
        return new CharacterRange(charRange.start, charRange.end);
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    var textContentConverter = {
        rangeToCharacterRange: function(range, containerNode) {
            var bookmark = range.getBookmark(containerNode);
            return new CharacterRange(bookmark.start, bookmark.end);
        },

        characterRangeToRange: function(doc, characterRange, containerNode) {
            var range = api.createRange(doc);
            range.moveToBookmark({
                start: characterRange.start,
                end: characterRange.end,
                containerNode: containerNode
            });

            return range;
        },

        serializeSelection: function(selection, containerNode) {
            var ranges = selection.getAllRanges(), rangeCount = ranges.length;
            var rangeInfos = [];

            var backward = rangeCount == 1 && selection.isBackward();

            for (var i = 0, len = ranges.length; i < len; ++i) {
                rangeInfos[i] = {
                    characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
                    backward: backward
                };
            }

            return rangeInfos;
        },

        restoreSelection: function(selection, savedSelection, containerNode) {
            selection.removeAllRanges();
            var doc = selection.win.document;
            for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
                rangeInfo = savedSelection[i];
                characterRange = rangeInfo.characterRange;
                range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
                selection.addRange(range, rangeInfo.backward);
            }
        }
    };

    registerHighlighterType("textContent", function() {
        return textContentConverter;
    });

    /*----------------------------------------------------------------------------------------------------------------*/

    // Lazily load the TextRange-based converter so that the dependency is only checked when required.
    registerHighlighterType("TextRange", (function() {
        var converter;

        return function() {
            if (!converter) {
                // Test that textRangeModule exists and is supported
                var textRangeModule = api.modules.TextRange;
                if (!textRangeModule) {
                    throw new Error("TextRange module is missing.");
                } else if (!textRangeModule.supported) {
                    throw new Error("TextRange module is present but not supported.");
                }

                converter = {
                    rangeToCharacterRange: function(range, containerNode) {
                        return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
                    },

                    characterRangeToRange: function(doc, characterRange, containerNode) {
                        var range = api.createRange(doc);
                        range.selectCharacters(containerNode, characterRange.start, characterRange.end);
                        return range;
                    },

                    serializeSelection: function(selection, containerNode) {
                        return selection.saveCharacterRanges(containerNode);
                    },

                    restoreSelection: function(selection, savedSelection, containerNode) {
                        selection.restoreCharacterRanges(containerNode, savedSelection);
                    }
                }
            }

            return converter;
        }
    })());

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
        if (id) {
            this.id = id;
            nextHighlightId = Math.max(nextHighlightId, id + 1);
        } else {
            this.id = nextHighlightId++;
        }
        this.characterRange = characterRange;
        this.doc = doc;
        this.classApplier = classApplier;
        this.converter = converter;
        this.containerElementId = containerElementId || null;
        this.applied = false;
    }

    Highlight.prototype = {
        getContainerElement: function() {
            return this.containerElementId ? this.doc.getElementById(this.containerElementId) : getBody(this.doc);
        },

        getRange: function() {
            return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
        },

        fromRange: function(range) {
            this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
        },

        containsElement: function(el) {
            return this.getRange().containsNodeContents(el.firstChild);
        },

        unapply: function() {
            this.classApplier.undoToRange(this.getRange());
            this.applied = false;
        },

        apply: function() {
            this.classApplier.applyToRange(this.getRange());
            this.applied = true;
        },

        getHighlightElements: function() {
            return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
        },

        toString: function() {
            return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
                this.characterRange.start + " - " + this.characterRange.end + ")]";
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlighter(doc, type) {
        type = type || "textContent";
        this.doc = doc || document;
        this.classAppliers = {};
        this.highlights = [];
        this.converter = getConverter(type);
    }

    Highlighter.prototype = {
        addClassApplier: function(classApplier) {
            this.classAppliers[classApplier.cssClass] = classApplier;
        },

        getHighlightForElement: function(el) {
            var highlights = this.highlights;
            for (var i = 0, len = highlights.length; i < len; ++i) {
                if (highlights[i].containsElement(el)) {
                    return highlights[i];
                }
            }
            return null;
        },

        removeHighlights: function(highlights) {
            for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
                highlight = this.highlights[i];
                if (contains(highlights, highlight)) {
                    highlight.unapply();
                    this.highlights.splice(i--, 1);
                }
            }
        },

        getIntersectingHighlights: function(ranges) {
            // Test each range against each of the highlighted ranges to see whether they overlap
            var intersectingHighlights = [], highlights = this.highlights, converter = this.converter;
            forEach(ranges, function(range) {
                //var selCharRange = converter.rangeToCharacterRange(range);
                forEach(highlights, function(highlight) {
                    if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
                        intersectingHighlights.push(highlight);
                    }
                });
            });

            return intersectingHighlights;
        },

        highlightCharacterRanges: function(className, charRanges, containerElementId) {
            var i, len, j;
            var highlights = this.highlights;
            var converter = this.converter;
            var doc = this.doc;
            var highlightsToRemove = [];
            var classApplier = this.classAppliers[className];
            containerElementId = containerElementId || null;

            var containerElement, containerElementRange, containerElementCharRange;
            if (containerElementId) {
                containerElement = this.doc.getElementById(containerElementId);
                if (containerElement) {
                    containerElementRange = api.createRange(this.doc);
                    containerElementRange.selectNodeContents(containerElement);
                    containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
                    containerElementRange.detach();
                }
            }

            var charRange, highlightCharRange, highlightRange, merged;
            for (i = 0, len = charRanges.length; i < len; ++i) {
                charRange = charRanges[i];
                merged = false;

                // Restrict character range to container element, if it exists
                if (containerElementCharRange) {
                    charRange = charRange.intersection(containerElementCharRange);
                }

                // Check for intersection with existing highlights. For each intersection, create a new highlight
                // which is the union of the highlight range and the selected range
                for (j = 0; j < highlights.length; ++j) {
                    if (containerElementId == highlights[j].containerElementId) {
                        highlightCharRange = highlights[j].characterRange;

                        if (highlightCharRange.intersects(charRange)) {
                            // Replace the existing highlight in the list of current highlights and add it to the list for
                            // removal
                            highlightsToRemove.push(highlights[j]);
                            highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
                        }
                    }
                }

                if (!merged) {
                    highlights.push( new Highlight(doc, charRange, classApplier, converter, null, containerElementId) );
                }
            }

            // Remove the old highlights
            forEach(highlightsToRemove, function(highlightToRemove) {
                highlightToRemove.unapply();
            });

            // Apply new highlights
            var newHighlights = [];
            forEach(highlights, function(highlight) {
                if (!highlight.applied) {
                    highlight.apply();
                    newHighlights.push(highlight);
                }
            });

            return newHighlights;
        },

        highlightRanges: function(className, ranges, containerElement) {
            var selCharRanges = [];
            var converter = this.converter;
            var containerElementId = containerElement ? containerElement.id : null;
            var containerElementRange;
            if (containerElement) {
                containerElementRange = api.createRange(containerElement);
                containerElementRange.selectNodeContents(containerElement);
            }

            forEach(ranges, function(range) {
                var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
                selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
            });

            return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
        },

        highlightSelection: function(className, selection, containerElementId) {
            var converter = this.converter;
            selection = selection || api.getSelection();
            var classApplier = this.classAppliers[className];
            var highlights = this.highlights;
            var doc = selection.win.document;
            var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);

            if (!classApplier) {
                throw new Error("No class applier found for class '" + className + "'");
            }

            // Store the existing selection as character ranges
            var serializedSelection = converter.serializeSelection(selection, containerElement);

            // Create an array of selected character ranges
            var selCharRanges = [];
            forEach(serializedSelection, function(rangeInfo) {
                selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
            });

            var newHighlights = this.highlightCharacterRanges(className, selCharRanges, containerElementId);

            // Restore selection
            converter.restoreSelection(selection, serializedSelection, containerElement);

            return newHighlights;
        },

        unhighlightSelection: function(selection) {
            selection = selection || api.getSelection();
            var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
            this.removeHighlights(intersectingHighlights);
            selection.removeAllRanges();
        },

        selectionOverlapsHighlight: function(selection) {
            selection = selection || api.getSelection();
            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
        },

        serialize: function() {
            var highlights = this.highlights;
            highlights.sort(compareHighlights);
            var serializedHighlights = ["type:" + this.converter.type];

            forEach(highlights, function(highlight) {
                var characterRange = highlight.characterRange;
                serializedHighlights.push( [
                    characterRange.start,
                    characterRange.end,
                    highlight.id,
                    highlight.classApplier.cssClass,
                    highlight.containerElementId
                ].join("$") );
            });

            return serializedHighlights.join("|");
        },

        deserialize: function(serialized) {
            var serializedHighlights = serialized.split("|");
            var highlights = [];

            var firstHighlight = serializedHighlights[0];
            var regexResult;
            var serializationType, serializationConverter, convertType = false;
            if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
                serializationType = regexResult[1];
                if (serializationType != this.converter.type) {
                    serializationConverter = getConverter(serializationType);
                    convertType = true;
                }
                serializedHighlights.shift();
            } else {
                throw new Error("Serialized highlights are invalid.");
            }

            var classApplier, highlight, characterRange, containerElementId, containerElement;

            for (var i = serializedHighlights.length, parts; i-- > 0; ) {
                parts = serializedHighlights[i].split("$");
                characterRange = new CharacterRange(+parts[0], +parts[1]);
                containerElementId = parts[4] || null;
                containerElement = containerElementId ? this.doc.getElementById(containerElementId) : getBody(this.doc);

                // Convert to the current Highlighter's type, if different from the serialization type
                if (convertType) {
                    characterRange = this.converter.rangeToCharacterRange(
                        serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
                        containerElement
                    );
                }

                classApplier = this.classAppliers[parts[3]];
                highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parts[2], containerElementId);
                highlight.apply();
                highlights.push(highlight);
            }
            this.highlights = highlights;
        }
    };

    api.Highlighter = Highlighter;

    api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
        return new Highlighter(doc, rangeCharacterOffsetConverterType);
    };
});
<commit_msg>Added removeAllHighlights() method<commit_after>/**
 * Highlighter module for Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core, TextRange and CssClassApplier modules.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Highlighter", function(api, module) {
    api.requireModules( ["CssClassApplier"] );

    var log = log4javascript.getLogger("rangy.Highlighter");
    var dom = api.dom;
    var contains = dom.arrayContains;
    var getBody = dom.getBody;

    // Puts highlights in order, last in document first.
    function compareHighlights(h1, h2) {
        return h1.characterRange.start - h2.characterRange.start;
    }

    var forEach = [].forEach ?
        function(arr, func) {
            arr.forEach(func);
        } :
        function(arr, func) {
            for (var i = 0, len = arr.length; i < len; ++i) {
                func( arr[i] );
            }
        };

    var nextHighlightId = 1;

    /*----------------------------------------------------------------------------------------------------------------*/

    var highlighterTypes = {};

    function HighlighterType(type, converterCreator) {
        this.type = type;
        this.converterCreator = converterCreator
    }

    HighlighterType.prototype.create = function() {
        var converter = this.converterCreator();
        converter.type = this.type;
        return converter;
    };

    function registerHighlighterType(type, converterCreator) {
        highlighterTypes[type] = new HighlighterType(type, converterCreator);
    }

    function getConverter(type) {
        var highlighterType = highlighterTypes[type];
        if (highlighterType instanceof HighlighterType) {
            return highlighterType.create();
        } else {
            throw new Error("Highlighter type '" + type + "' is not valid")
        }
    }

    api.registerHighlighterType = registerHighlighterType;

    /*----------------------------------------------------------------------------------------------------------------*/

    function CharacterRange(start, end) {
        this.start = start;
        this.end = end;
    }

    CharacterRange.prototype = {
        intersects: function(charRange) {
            return this.start < charRange.end && this.end > charRange.start;
        },

        union: function(charRange) {
            return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
        },

        intersection: function(charRange) {
            return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
        },

        toString: function() {
            return "[CharacterRange(" + this.start + ", " + this.end + ")]";
        }
    };

    CharacterRange.fromCharacterRange = function(charRange) {
        return new CharacterRange(charRange.start, charRange.end);
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    var textContentConverter = {
        rangeToCharacterRange: function(range, containerNode) {
            var bookmark = range.getBookmark(containerNode);
            return new CharacterRange(bookmark.start, bookmark.end);
        },

        characterRangeToRange: function(doc, characterRange, containerNode) {
            var range = api.createRange(doc);
            range.moveToBookmark({
                start: characterRange.start,
                end: characterRange.end,
                containerNode: containerNode
            });

            return range;
        },

        serializeSelection: function(selection, containerNode) {
            var ranges = selection.getAllRanges(), rangeCount = ranges.length;
            var rangeInfos = [];

            var backward = rangeCount == 1 && selection.isBackward();

            for (var i = 0, len = ranges.length; i < len; ++i) {
                rangeInfos[i] = {
                    characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
                    backward: backward
                };
            }

            return rangeInfos;
        },

        restoreSelection: function(selection, savedSelection, containerNode) {
            selection.removeAllRanges();
            var doc = selection.win.document;
            for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
                rangeInfo = savedSelection[i];
                characterRange = rangeInfo.characterRange;
                range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
                selection.addRange(range, rangeInfo.backward);
            }
        }
    };

    registerHighlighterType("textContent", function() {
        return textContentConverter;
    });

    /*----------------------------------------------------------------------------------------------------------------*/

    // Lazily load the TextRange-based converter so that the dependency is only checked when required.
    registerHighlighterType("TextRange", (function() {
        var converter;

        return function() {
            if (!converter) {
                // Test that textRangeModule exists and is supported
                var textRangeModule = api.modules.TextRange;
                if (!textRangeModule) {
                    throw new Error("TextRange module is missing.");
                } else if (!textRangeModule.supported) {
                    throw new Error("TextRange module is present but not supported.");
                }

                converter = {
                    rangeToCharacterRange: function(range, containerNode) {
                        return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
                    },

                    characterRangeToRange: function(doc, characterRange, containerNode) {
                        var range = api.createRange(doc);
                        range.selectCharacters(containerNode, characterRange.start, characterRange.end);
                        return range;
                    },

                    serializeSelection: function(selection, containerNode) {
                        return selection.saveCharacterRanges(containerNode);
                    },

                    restoreSelection: function(selection, savedSelection, containerNode) {
                        selection.restoreCharacterRanges(containerNode, savedSelection);
                    }
                }
            }

            return converter;
        }
    })());

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
        if (id) {
            this.id = id;
            nextHighlightId = Math.max(nextHighlightId, id + 1);
        } else {
            this.id = nextHighlightId++;
        }
        this.characterRange = characterRange;
        this.doc = doc;
        this.classApplier = classApplier;
        this.converter = converter;
        this.containerElementId = containerElementId || null;
        this.applied = false;
    }

    Highlight.prototype = {
        getContainerElement: function() {
            return this.containerElementId ? this.doc.getElementById(this.containerElementId) : getBody(this.doc);
        },

        getRange: function() {
            return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
        },

        fromRange: function(range) {
            this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
        },

        containsElement: function(el) {
            return this.getRange().containsNodeContents(el.firstChild);
        },

        unapply: function() {
            this.classApplier.undoToRange(this.getRange());
            this.applied = false;
        },

        apply: function() {
            this.classApplier.applyToRange(this.getRange());
            this.applied = true;
        },

        getHighlightElements: function() {
            return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
        },

        toString: function() {
            return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
                this.characterRange.start + " - " + this.characterRange.end + ")]";
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlighter(doc, type) {
        type = type || "textContent";
        this.doc = doc || document;
        this.classAppliers = {};
        this.highlights = [];
        this.converter = getConverter(type);
    }

    Highlighter.prototype = {
        addClassApplier: function(classApplier) {
            this.classAppliers[classApplier.cssClass] = classApplier;
        },

        getHighlightForElement: function(el) {
            var highlights = this.highlights;
            for (var i = 0, len = highlights.length; i < len; ++i) {
                if (highlights[i].containsElement(el)) {
                    return highlights[i];
                }
            }
            return null;
        },

        removeHighlights: function(highlights) {
            for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
                highlight = this.highlights[i];
                if (contains(highlights, highlight)) {
                    highlight.unapply();
                    this.highlights.splice(i--, 1);
                }
            }
        },

        removeAllHighlights: function() {
            this.removeHighlights(this.highlights);
        },

        getIntersectingHighlights: function(ranges) {
            // Test each range against each of the highlighted ranges to see whether they overlap
            var intersectingHighlights = [], highlights = this.highlights, converter = this.converter;
            forEach(ranges, function(range) {
                //var selCharRange = converter.rangeToCharacterRange(range);
                forEach(highlights, function(highlight) {
                    if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
                        intersectingHighlights.push(highlight);
                    }
                });
            });

            return intersectingHighlights;
        },

        highlightCharacterRanges: function(className, charRanges, containerElementId) {
            var i, len, j;
            var highlights = this.highlights;
            var converter = this.converter;
            var doc = this.doc;
            var highlightsToRemove = [];
            var classApplier = this.classAppliers[className];
            containerElementId = containerElementId || null;

            var containerElement, containerElementRange, containerElementCharRange;
            if (containerElementId) {
                containerElement = this.doc.getElementById(containerElementId);
                if (containerElement) {
                    containerElementRange = api.createRange(this.doc);
                    containerElementRange.selectNodeContents(containerElement);
                    containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
                    containerElementRange.detach();
                }
            }

            var charRange, highlightCharRange, highlightRange, merged;
            for (i = 0, len = charRanges.length; i < len; ++i) {
                charRange = charRanges[i];
                merged = false;

                // Restrict character range to container element, if it exists
                if (containerElementCharRange) {
                    charRange = charRange.intersection(containerElementCharRange);
                }

                // Check for intersection with existing highlights. For each intersection, create a new highlight
                // which is the union of the highlight range and the selected range
                for (j = 0; j < highlights.length; ++j) {
                    if (containerElementId == highlights[j].containerElementId) {
                        highlightCharRange = highlights[j].characterRange;

                        if (highlightCharRange.intersects(charRange)) {
                            // Replace the existing highlight in the list of current highlights and add it to the list for
                            // removal
                            highlightsToRemove.push(highlights[j]);
                            highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
                        }
                    }
                }

                if (!merged) {
                    highlights.push( new Highlight(doc, charRange, classApplier, converter, null, containerElementId) );
                }
            }

            // Remove the old highlights
            forEach(highlightsToRemove, function(highlightToRemove) {
                highlightToRemove.unapply();
            });

            // Apply new highlights
            var newHighlights = [];
            forEach(highlights, function(highlight) {
                if (!highlight.applied) {
                    highlight.apply();
                    newHighlights.push(highlight);
                }
            });

            return newHighlights;
        },

        highlightRanges: function(className, ranges, containerElement) {
            var selCharRanges = [];
            var converter = this.converter;
            var containerElementId = containerElement ? containerElement.id : null;
            var containerElementRange;
            if (containerElement) {
                containerElementRange = api.createRange(containerElement);
                containerElementRange.selectNodeContents(containerElement);
            }

            forEach(ranges, function(range) {
                var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
                selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
            });

            return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
        },

        highlightSelection: function(className, selection, containerElementId) {
            var converter = this.converter;
            selection = selection || api.getSelection();
            var classApplier = this.classAppliers[className];
            var highlights = this.highlights;
            var doc = selection.win.document;
            var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);

            if (!classApplier) {
                throw new Error("No class applier found for class '" + className + "'");
            }

            // Store the existing selection as character ranges
            var serializedSelection = converter.serializeSelection(selection, containerElement);

            // Create an array of selected character ranges
            var selCharRanges = [];
            forEach(serializedSelection, function(rangeInfo) {
                selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
            });

            var newHighlights = this.highlightCharacterRanges(className, selCharRanges, containerElementId);

            // Restore selection
            converter.restoreSelection(selection, serializedSelection, containerElement);

            return newHighlights;
        },

        unhighlightSelection: function(selection) {
            selection = selection || api.getSelection();
            var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
            this.removeHighlights(intersectingHighlights);
            selection.removeAllRanges();
        },

        selectionOverlapsHighlight: function(selection) {
            selection = selection || api.getSelection();
            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
        },

        serialize: function() {
            var highlights = this.highlights;
            highlights.sort(compareHighlights);
            var serializedHighlights = ["type:" + this.converter.type];

            forEach(highlights, function(highlight) {
                var characterRange = highlight.characterRange;
                serializedHighlights.push( [
                    characterRange.start,
                    characterRange.end,
                    highlight.id,
                    highlight.classApplier.cssClass,
                    highlight.containerElementId
                ].join("$") );
            });

            return serializedHighlights.join("|");
        },

        deserialize: function(serialized) {
            var serializedHighlights = serialized.split("|");
            var highlights = [];

            var firstHighlight = serializedHighlights[0];
            var regexResult;
            var serializationType, serializationConverter, convertType = false;
            if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
                serializationType = regexResult[1];
                if (serializationType != this.converter.type) {
                    serializationConverter = getConverter(serializationType);
                    convertType = true;
                }
                serializedHighlights.shift();
            } else {
                throw new Error("Serialized highlights are invalid.");
            }

            var classApplier, highlight, characterRange, containerElementId, containerElement;

            for (var i = serializedHighlights.length, parts; i-- > 0; ) {
                parts = serializedHighlights[i].split("$");
                characterRange = new CharacterRange(+parts[0], +parts[1]);
                containerElementId = parts[4] || null;
                containerElement = containerElementId ? this.doc.getElementById(containerElementId) : getBody(this.doc);

                // Convert to the current Highlighter's type, if different from the serialization type
                if (convertType) {
                    characterRange = this.converter.rangeToCharacterRange(
                        serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
                        containerElement
                    );
                }

                classApplier = this.classAppliers[parts[3]];
                highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parts[2], containerElementId);
                highlight.apply();
                highlights.push(highlight);
            }
            this.highlights = highlights;
        }
    };

    api.Highlighter = Highlighter;

    api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
        return new Highlighter(doc, rangeCharacterOffsetConverterType);
    };
});
<|endoftext|>


<commit_before>/**
 * @license Rangy Text Inputs, a cross-browser textarea and text input library plug-in for jQuery.
 *
 * Part of Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on jQuery 1.0 or later.
 *
 * Copyright 2010, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
(function($) {
    var UNDEF = "undefined";
    var getSelection, setSelection, deleteSelectedText, deleteText, insertText;
    var replaceSelectedText, surroundSelectedText, extractSelectedText, collapseSelection;

    // Trio of isHost* functions taken from Peter Michaux's article:
    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
    function isHostMethod(object, property) {
        var t = typeof object[property];
        return t === "function" || (!!(t == "object" && object[property])) || t == "unknown";
    }

    function isHostProperty(object, property) {
        return typeof(object[property]) != UNDEF;
    }

    function isHostObject(object, property) {
        return !!(typeof(object[property]) == "object" && object[property]);
    }

    function fail(reason) {
        if (window.console && window.console.log) {
            window.console.log("TextInputs module for Rangy not supported in your browser. Reason: " + reason);
        }
    }

    function adjustOffsets(el, start, end) {
        if (start < 0) {
            start += el.value.length;
        }
        if (typeof end == UNDEF) {
            end = start;
        }
        if (end < 0) {
            end += el.value.length;
        }
        return { start: start, end: end };
    }

    function makeSelection(el, start, end) {
        return {
            start: start,
            end: end,
            length: end - start,
            text: el.value.slice(start, end)
        };
    }

    function getBody() {
        return isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
    }

    $(document).ready(function() {
        var testTextArea = document.createElement("textarea");

        getBody().appendChild(testTextArea);

        if (isHostProperty(testTextArea, "selectionStart") && isHostProperty(testTextArea, "selectionEnd")) {
            getSelection = function(el) {
                var start = el.selectionStart, end = el.selectionEnd;
                return makeSelection(el, start, end);
            };

            setSelection = function(el, startOffset, endOffset) {
                var offsets = adjustOffsets(el, startOffset, endOffset);
                el.selectionStart = offsets.start;
                el.selectionEnd = offsets.end;
            };

            collapseSelection = function(el, toStart) {
                if (toStart) {
                    el.selectionEnd = el.selectionStart;
                } else {
                    el.selectionStart = el.selectionEnd;
                }
            };
        } else if (isHostMethod(testTextArea, "createTextRange") && isHostObject(document, "selection") &&
                   isHostMethod(document.selection, "createRange")) {

            getSelection = function(el) {
                var start = 0, end = 0, normalizedValue, textInputRange, len, endRange;
                var range = document.selection.createRange();

                if (range && range.parentElement() == el) {
                    len = el.value.length;

                    normalizedValue = el.value.replace(/\r\n/g, "\n");
                    textInputRange = el.createTextRange();
                    textInputRange.moveToBookmark(range.getBookmark());
                    endRange = el.createTextRange();
                    endRange.collapse(false);
                    if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
                        start = end = len;
                    } else {
                        start = -textInputRange.moveStart("character", -len);
                        start += normalizedValue.slice(0, start).split("\n").length - 1;
                        if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
                            end = len;
                        } else {
                            end = -textInputRange.moveEnd("character", -len);
                            end += normalizedValue.slice(0, end).split("\n").length - 1;
                        }
                    }
                }

                return makeSelection(el, start, end);
            };

            // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
            // the textarea value is two characters. This function corrects for that by converting a text offset into a
            // range character offset by subtracting one character for every line break in the textarea prior to the
            // offset
            var offsetToRangeCharacterMove = function(el, offset) {
                return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
            };

            setSelection = function(el, startOffset, endOffset) {
                var offsets = adjustOffsets(el, startOffset, endOffset);
                var range = el.createTextRange();
                var startCharMove = offsetToRangeCharacterMove(el, offsets.start);
                range.collapse(true);
                if (offsets.start == offsets.end) {
                    range.move("character", startCharMove);
                } else {
                    range.moveEnd("character", offsetToRangeCharacterMove(el, offsets.end));
                    range.moveStart("character", startCharMove);
                }
                range.select();
            };

            collapseSelection = function(el, toStart) {
                var range = document.selection.createRange();
                range.collapse(toStart);
                range.select();
            };
        } else {
            getBody().removeChild(testTextArea);
            fail("No means of finding text input caret position");
            return;
        }

        // Clean up
        getBody().removeChild(testTextArea);

        deleteText = function(el, start, end, moveSelection) {
            var val;
            if (start != end) {
                val = el.value;
                el.value = val.slice(0, start) + val.slice(end);
            }
            if (moveSelection) {
                setSelection(el, start, start);
            }
        };

        deleteSelectedText = function(el) {
            var sel = getSelection(el);
            deleteText(el, sel.start, sel.end, true);
        };

        extractSelectedText = function(el) {
            var sel = getSelection(el), val;
            if (sel.start != sel.end) {
                val = el.value;
                el.value = val.slice(0, sel.start) + val.slice(sel.end);
            }
            setSelection(el, sel.start, sel.start);
            return sel.text;
        };

        insertText = function(el, text, index, moveSelection) {
            var val = el.value, caretIndex;
            el.value = val.slice(0, index) + text + val.slice(index);
            if (moveSelection) {
                caretIndex = index + text.length;
                setSelection(el, caretIndex, caretIndex);
            }
        };

        replaceSelectedText = function(el, text) {
            var sel = getSelection(el), val = el.value;
            el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
            var caretIndex = sel.start + text.length;
            setSelection(el, caretIndex, caretIndex);
        };

        surroundSelectedText = function(el, before, after) {
            var sel = getSelection(el), val = el.value;
            log.info(sel.start + ", " + sel.end + ", '" + sel.text + "'");
            el.value = val.slice(0, sel.start) + before + sel.text + after + val.slice(sel.end);
            var startIndex = sel.start + before.length;
            var endIndex = startIndex + sel.length;
            setSelection(el, startIndex, endIndex);
        };

        function jQuerify(func, returnThis) {
            return function() {
                var el = this.jquery ? this[0] : this;
                var nodeName = el.nodeName.toLowerCase();

                if (el.nodeType == 1 && (nodeName == "textarea" || (nodeName == "input" && el.type == "text"))) {
                    var args = [el].concat(Array.prototype.slice.call(arguments));
                    var result = func.apply(this, args);
                    if (!returnThis) {
                        return result;
                    }
                }
                if (returnThis) {
                    return this;
                }
            };
        }

        $.fn.rangy = {
            getSelection: jQuerify(getSelection, false),
            setSelection: jQuerify(setSelection, true),
            collapseSelection: jQuerify(collapseSelection, true),
            deleteSelectedText: jQuerify(deleteSelectedText, true),
            deleteText: jQuerify(deleteText, true),
            extractSelectedText: jQuerify(extractSelectedText, false),
            insertText: jQuerify(insertText, true),
            replaceSelectedText: jQuerify(replaceSelectedText, true),
            surroundSelectedText: jQuerify(surroundSelectedText, true)
        };
    });
})(jQuery);<commit_msg>Reverted jquery plug-in to remove rangy namespace that broke things<commit_after>/**
 * @license Rangy Text Inputs, a cross-browser textarea and text input library plug-in for jQuery.
 *
 * Part of Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on jQuery 1.0 or later.
 *
 * Copyright 2010, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
(function($) {
    var UNDEF = "undefined";
    var getSelection, setSelection, deleteSelectedText, deleteText, insertText;
    var replaceSelectedText, surroundSelectedText, extractSelectedText, collapseSelection;

    // Trio of isHost* functions taken from Peter Michaux's article:
    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
    function isHostMethod(object, property) {
        var t = typeof object[property];
        return t === "function" || (!!(t == "object" && object[property])) || t == "unknown";
    }

    function isHostProperty(object, property) {
        return typeof(object[property]) != UNDEF;
    }

    function isHostObject(object, property) {
        return !!(typeof(object[property]) == "object" && object[property]);
    }

    function fail(reason) {
        if (window.console && window.console.log) {
            window.console.log("TextInputs module for Rangy not supported in your browser. Reason: " + reason);
        }
    }

    function adjustOffsets(el, start, end) {
        if (start < 0) {
            start += el.value.length;
        }
        if (typeof end == UNDEF) {
            end = start;
        }
        if (end < 0) {
            end += el.value.length;
        }
        return { start: start, end: end };
    }

    function makeSelection(el, start, end) {
        return {
            start: start,
            end: end,
            length: end - start,
            text: el.value.slice(start, end)
        };
    }

    function getBody() {
        return isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
    }

    $(document).ready(function() {
        var testTextArea = document.createElement("textarea");

        getBody().appendChild(testTextArea);

        if (isHostProperty(testTextArea, "selectionStart") && isHostProperty(testTextArea, "selectionEnd")) {
            getSelection = function(el) {
                var start = el.selectionStart, end = el.selectionEnd;
                return makeSelection(el, start, end);
            };

            setSelection = function(el, startOffset, endOffset) {
                var offsets = adjustOffsets(el, startOffset, endOffset);
                el.selectionStart = offsets.start;
                el.selectionEnd = offsets.end;
            };

            collapseSelection = function(el, toStart) {
                if (toStart) {
                    el.selectionEnd = el.selectionStart;
                } else {
                    el.selectionStart = el.selectionEnd;
                }
            };
        } else if (isHostMethod(testTextArea, "createTextRange") && isHostObject(document, "selection") &&
                   isHostMethod(document.selection, "createRange")) {

            getSelection = function(el) {
                var start = 0, end = 0, normalizedValue, textInputRange, len, endRange;
                var range = document.selection.createRange();

                if (range && range.parentElement() == el) {
                    len = el.value.length;

                    normalizedValue = el.value.replace(/\r\n/g, "\n");
                    textInputRange = el.createTextRange();
                    textInputRange.moveToBookmark(range.getBookmark());
                    endRange = el.createTextRange();
                    endRange.collapse(false);
                    if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
                        start = end = len;
                    } else {
                        start = -textInputRange.moveStart("character", -len);
                        start += normalizedValue.slice(0, start).split("\n").length - 1;
                        if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
                            end = len;
                        } else {
                            end = -textInputRange.moveEnd("character", -len);
                            end += normalizedValue.slice(0, end).split("\n").length - 1;
                        }
                    }
                }

                return makeSelection(el, start, end);
            };

            // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
            // the textarea value is two characters. This function corrects for that by converting a text offset into a
            // range character offset by subtracting one character for every line break in the textarea prior to the
            // offset
            var offsetToRangeCharacterMove = function(el, offset) {
                return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
            };

            setSelection = function(el, startOffset, endOffset) {
                var offsets = adjustOffsets(el, startOffset, endOffset);
                var range = el.createTextRange();
                var startCharMove = offsetToRangeCharacterMove(el, offsets.start);
                range.collapse(true);
                if (offsets.start == offsets.end) {
                    range.move("character", startCharMove);
                } else {
                    range.moveEnd("character", offsetToRangeCharacterMove(el, offsets.end));
                    range.moveStart("character", startCharMove);
                }
                range.select();
            };

            collapseSelection = function(el, toStart) {
                var range = document.selection.createRange();
                range.collapse(toStart);
                range.select();
            };
        } else {
            getBody().removeChild(testTextArea);
            fail("No means of finding text input caret position");
            return;
        }

        // Clean up
        getBody().removeChild(testTextArea);

        deleteText = function(el, start, end, moveSelection) {
            var val;
            if (start != end) {
                val = el.value;
                el.value = val.slice(0, start) + val.slice(end);
            }
            if (moveSelection) {
                setSelection(el, start, start);
            }
        };

        deleteSelectedText = function(el) {
            var sel = getSelection(el);
            deleteText(el, sel.start, sel.end, true);
        };

        extractSelectedText = function(el) {
            var sel = getSelection(el), val;
            if (sel.start != sel.end) {
                val = el.value;
                el.value = val.slice(0, sel.start) + val.slice(sel.end);
            }
            setSelection(el, sel.start, sel.start);
            return sel.text;
        };

        insertText = function(el, text, index, moveSelection) {
            var val = el.value, caretIndex;
            el.value = val.slice(0, index) + text + val.slice(index);
            if (moveSelection) {
                caretIndex = index + text.length;
                setSelection(el, caretIndex, caretIndex);
            }
        };

        replaceSelectedText = function(el, text) {
            var sel = getSelection(el), val = el.value;
            el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
            var caretIndex = sel.start + text.length;
            setSelection(el, caretIndex, caretIndex);
        };

        surroundSelectedText = function(el, before, after) {
            var sel = getSelection(el), val = el.value;
            log.info(sel.start + ", " + sel.end + ", '" + sel.text + "'");
            el.value = val.slice(0, sel.start) + before + sel.text + after + val.slice(sel.end);
            var startIndex = sel.start + before.length;
            var endIndex = startIndex + sel.length;
            setSelection(el, startIndex, endIndex);
        };

        function jQuerify(func, returnThis) {
            return function() {
                var el = this.jquery ? this[0] : this;
                var nodeName = el.nodeName.toLowerCase();

                if (el.nodeType == 1 && (nodeName == "textarea" || (nodeName == "input" && el.type == "text"))) {
                    var args = [el].concat(Array.prototype.slice.call(arguments));
                    var result = func.apply(this, args);
                    if (!returnThis) {
                        return result;
                    }
                }
                if (returnThis) {
                    return this;
                }
            };
        }

        $.fn.extend({
            getSelection: jQuerify(getSelection, false),
            setSelection: jQuerify(setSelection, true),
            collapseSelection: jQuerify(collapseSelection, true),
            deleteSelectedText: jQuerify(deleteSelectedText, true),
            deleteText: jQuerify(deleteText, true),
            extractSelectedText: jQuerify(extractSelectedText, false),
            insertText: jQuerify(insertText, true),
            replaceSelectedText: jQuerify(replaceSelectedText, true),
            surroundSelectedText: jQuerify(surroundSelectedText, true)
        });
    });
})(jQuery);<|endoftext|>