<commit_before>// Copyright (c) 2011-2012 UX Productivity Pty Ltd. All rights reserved.

(function() {
    function Range(startNode,startOffset,endNode,endOffset)
    {
        this.start = new Position(startNode,startOffset);
        this.end = new Position(endNode,endOffset);
    }

    function copy()
    {
        return new Range(this.start.node,this.start.offset,
                         this.end.node,this.end.offset);
    }

    function isEmpty()
    {
        return ((this.start.node == this.end.node) &&
                (this.start.offset == this.end.offset));
    }

    Range.prototype.toString = function()
    {
        return this.start.toString() + " - " + this.end.toString();
    }

    function trackWhileExecuting(fun)
    {
        Position.trackWhileExecuting([this.start,this.end],fun);
    }

    function selectWholeWords()
    {
        if ((this.start.node.nodeType == Node.TEXT_NODE) &&
            (this.end.node.nodeType == Node.TEXT_NODE)) {
            if (this.isForwards()) {
                // this.start comes before this.end
                this.start.moveToStartOfWord();
                this.end.moveToEndOfWord();
            }
            else {
                // this.end comes before this.end
                this.start.moveToEndOfWord();
                this.end.moveToStartOfWord();
            }
        }
    }

    function expand()
    {
        var doc = this.start.node.ownerDocument;
        while ((this.start.offset == 0) && (this.start.node != doc.body)) {
            var offset = DOM.nodeOffset(this.start.node);
            this.start.node = this.start.node.parentNode;
            this.start.offset = offset;
        }

        while ((this.end.offset == DOM.maxChildOffset(this.end.node)) &&
               (this.end.node != doc.body)) {
            var offset = DOM.nodeOffset(this.end.node);
            this.end.node = this.end.node.parentNode;
            this.end.offset = offset+1;
        }
    }

    function omitEmptyTextSelection()
    {
        if (!this.start.moveBackwardIfAtStart())
            this.start.moveForwardIfAtEnd()
        if (!this.end.moveBackwardIfAtStart())
            this.end.moveForwardIfAtEnd()
    }

    function isForwards()
    {
        var doc = this.start.node.ownerDocument;
        if ((this.start.node.parentNode == null) && (this.start.node != doc.documentElement))
            throw new Error("Range.isForwards "+this+": start node has been removed from document");
        if ((this.end.node.parentNode == null) && (this.end.node != doc.documentElement))
            throw new Error("Range.isForwards "+this+": end node has been removed from document");

        var start = this.start;
        var end = this.end;

        if ((start.node == end.node) && (start.node.nodeType == Node.TEXT_NODE))
            return (end.offset >= start.offset);

        var startParent = null;
        var startChild = null;
        var endParent = null;
        var endChild = null;

        if (end.node.nodeType == Node.ELEMENT_NODE) {
            endParent = end.node;
            endChild = end.node.childNodes[end.offset];
        }
        else {
            endParent = end.node.parentNode;
            endChild = end.node;
        }

        if (start.node.nodeType == Node.ELEMENT_NODE) {
            startParent = start.node;
            startChild = start.node.childNodes[start.offset];
        }
        else {
            startParent = start.node.parentNode;
            startChild = start.node;
            if (startChild == endChild)
                return false;
        }

        var startC = startChild;
        var startP = startParent;
        while (startP != null) {

            var endC = endChild;
            var endP = endParent;
            while (endP != null) {

                if (startP == endC)
                    return false;

                if (startP == endP) {
                    if (endC == null) // endC is last child, so startC must be endC or come before it
                        return true;
                    for (var n = startC; n != null; n = n.nextSibling) {
                        if (n == endC)
                            return true;
                    }
                    return false;
                }

                endC = endP;
                endP = endP.parentNode;
            }

            startC = startP;
            startP = startP.parentNode;
        }
        throw new Error("Could not find common ancestor");
    }

    function getInlineNodes(atLeastOne)
    {
        var all = this.getAllNodes(atLeastOne);
        var result = new Array();
        for (var i = 0; i < all.length; i++) {
            if (isInlineNode(all[i]))
                result.push(all[i]);
        }
        return result;
    }

    function getAllNodes(atLeastOne)
    {
        var result = new Array();
        var outermost = this.getOutermostNodes(atLeastOne);
        for (var i = 0; i < outermost.length; i++)
            addRecursive(outermost[i]);
        return result;

        function addRecursive(node)
        {
            result.push(node);
            for (var child = node.firstChild; child != null; child = child.nextSibling)
                addRecursive(child);
        }
    }

    function singleNode()
    {
        return this.start.closestActualNode();
    }

    function ensureRangeValidHierarchy()
    {
        var nodes = this.getAllNodes(true);

        var depths = new Array();
        for (var i = 0; i < nodes.length; i++) {
            var depth = getNodeDepth(nodes[i]);
            if (depths[depth] == null) {
                depths[depth] = new Array();
            }
            depths[depth].push(nodes[i]);
        }

        for (var depth = 0; depth < depths.length; depth++) {
            var firstDepth = true;
            if (depths[depth] != null) {
                for (var i = 0; i < depths[depth].length; i++) {
                    var node = depths[depth][i];
                    Hierarchy.ensureValidHierarchy(node,firstDepth);
                }
                firstDepth = false;
            }
        }
    };

    function forwards()
    {
        if (this.isForwards()) {
            return this;
        }
        else {
            var reverse = new Range(this.end.node,this.end.offset,this.start.node,this.start.offset);
            if (!reverse.isForwards())
                throw new Error("Both range "+this+" and its reverse are not forwards");
            return reverse;
        }
    }

    function detail()
    {
        if (!this.isForwards()) {
            var reverse = new Range(this.end.node,this.end.offset,this.start.node,this.start.offset);
            if (!reverse.isForwards())
                throw new Error("Both range "+this+" and its reverse are not forwards");
            return reverse.detail();
        }

        var detail = new Object();
        var start = this.start;
        var end = this.end;

        // Start location
        if (start.node.nodeType == Node.ELEMENT_NODE) {
            detail.startParent = start.node;
            detail.startChild = start.node.childNodes[start.offset];
        }
        else if ((start.node.nodeValue.length > 0) && (start.offset == start.node.nodeValue.length)) {
            detail.startParent = start.node.parentNode;
            detail.startChild = start.node.nextSibling;
        }
        else {
            detail.startParent = start.node.parentNode;
            detail.startChild = start.node;
        }

        // End location
        if (end.node.nodeType == Node.ELEMENT_NODE) {
            detail.endParent = end.node;
            detail.endChild = end.node.childNodes[end.offset];
        }
        else if (end.offset == 0) {
            detail.endParent = end.node.parentNode;
            detail.endChild = end.node;
        }
        else {
            detail.endParent = end.node.parentNode;
            detail.endChild = end.node.nextSibling;
        }

        // Common ancestor
        var startP = detail.startParent;
        var startC = detail.startChild;
        while (startP != null) {
            var endP = detail.endParent;
            var endC = detail.endChild
            while (endP != null) {
                if (startP == endP) {
                    detail.commonAncestor = startP;
                    detail.startAncestor = startC;
                    detail.endAncestor = endC;
                    // Found it
                    return detail;
                }
                endC = endP;
                endP = endP.parentNode;
            }
            startC = startP;
            startP = startP.parentNode;
        }
        throw new Error("Start and end of range have no common ancestor");
    }

    function getOutermostNodes(atLeastOne,info)
    {
        var beforeNodes = new Array();
        var middleNodes = new Array();
        var afterNodes = new Array();

        if (info != null) {
            info.beginning = beforeNodes;
            info.middle = middleNodes;
            info.end = afterNodes;
        }

        if (this.isEmpty())
            return atLeastOne ? [this.singleNode()] : [];

        // Note: start and end are *points* - they are always *in between* nodes or characters, never
        // *at* a node or character.
        // Everything after the end point is excluded from the selection
        // Everything after the start point, but before the end point, is included in the selection

        // We use (parent,child) pairs so that we have a way to represent a point that comes after all
        // the child nodes in a container - in which case the child is null. The parent, however, is
        // always non-null;

        var detail = this.detail();
        if (detail.commonAncestor == null)
            return atLeastOne ? [this.singleNode()] : [];
        var startParent = detail.startParent;
        var startChild = detail.startChild;
        var endParent = detail.endParent;
        var endChild = detail.endChild;
        var commonParent = detail.commonAncestor;
        var startAncestor = detail.startAncestor;
        var endAncestor = detail.endAncestor;

        // Add start nodes
        var topParent = startParent;
        var topChild = startChild;
        while (topParent != commonParent) {
            if (topChild != null)
                beforeNodes.push(topChild);

            while (((topChild == null) || (topChild.nextSibling == null)) &&
                   (topParent != commonParent)) {
                topChild = topParent;
                topParent = topParent.parentNode;
            }
            if (topParent != commonParent)
                topChild = topChild.nextSibling;
        }

        // Add middle nodes
        if (startAncestor != endAncestor) {
            var c = startAncestor;
            if ((c != null) && (c != startChild))
                c = c.nextSibling;
            for (; c != endAncestor; c = c.nextSibling)
                middleNodes.push(c);
        }

        // Add end nodes
        var bottomParent = endParent;
        var bottomChild = endChild;
        while (true) {

            while ((getPreviousSibling(bottomParent,bottomChild) == null) &&
                   (bottomParent != commonParent)) {
                bottomChild = bottomParent;
                bottomParent = bottomParent.parentNode;
            }
            if (bottomParent != commonParent)
                bottomChild = getPreviousSibling(bottomParent,bottomChild);

            if (bottomParent == commonParent)
                break;

            afterNodes.push(bottomChild);
        }
        afterNodes = afterNodes.reverse();

        var result = new Array();

        Array.prototype.push.apply(result,beforeNodes);
        Array.prototype.push.apply(result,middleNodes);
        Array.prototype.push.apply(result,afterNodes);

        if (result.length == 0)
            return atLeastOne ? [this.singleNode()] : [];
        else
            return result;

        function getPreviousSibling(parent,child)
        {
            if (child != null)
                return child.previousSibling;
            else if (parent.lastChild != null)
                return parent.lastChild;
            else
                return null;
        }

        function isAncestorLocation(ancestorParent,ancestorChild,
                                    descendantParent,descendantChild)
        {
            while ((descendantParent != null) &&
                   ((descendantParent != ancestorParent) || (descendantChild != ancestorChild))) {
                descendantChild = descendantParent;
                descendantParent = descendantParent.parentNode;
            }

            return ((descendantParent == ancestorParent) &&
                    (descendantChild == ancestorChild));
        }
    }

    function getClientRects()
    {
        var nodes = this.getOutermostNodes(true);

        // WebKit in iOS 5.0 has a bug where if the selection spans multiple paragraphs, the complete
        // rect for paragraphs other than the first is returned, instead of just the portions of it
        // that are actually in the range. To get around this problem, we go through each text node
        // individually and collect all the rects.
        var result = new Array();
        var doc = this.start.node.ownerDocument;
        var domRange = doc.createRange();
        for (var nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
            var node = nodes[nodeIndex];
            if (node.nodeType == Node.TEXT_NODE) {
                var startOffset = (node == this.start.node) ? this.start.offset : 0;
                var endOffset = (node == this.end.node) ? this.end.offset : node.nodeValue.length;
                domRange.setStart(node,startOffset);
                domRange.setEnd(node,endOffset);
                var rects = domRange.getClientRects();
                for (var rectIndex = 0; rectIndex < rects.length; rectIndex++)
                    result.push(rects[rectIndex]);
            }
            else if (node.nodeType == Node.ELEMENT_NODE) {
                result.push(node.getBoundingClientRect());
            }
        }
        return result;
    }

    function cloneContents()
    {
        var cloneMap = new NodeMap();
        var detail = this.detail();
        var cloneRoot = DOM.cloneNode(detail.commonAncestor,false);
        cloneMap.put(detail.commonAncestor,cloneRoot);
        var outermost = this.getOutermostNodes();
        for (var i = 0; i < outermost.length; i++) {

            var node = outermost[i];
            if (node.nodeType == Node.TEXT_NODE) {
                // FIXME: do this without modifying nodeValue
                var saved = node.nodeValue;
                if ((node == this.start.node) && (node == this.end.node)) {
                    node.nodeValue = node.nodeValue.slice(this.start.offset,this.end.offset);
                }
                else if (node == this.start.node) {
                    node.nodeValue = node.nodeValue.slice(this.start.offset);
                }
                else if (node == this.end.node) {
                    node.nodeValue = node.nodeValue.slice(0,this.end.offset);
                }
                add(outermost[i],true);
                node.nodeValue = saved;
            }
            else {
                add(outermost[i],true);
            }
        }

        var result = new Array();
        for (var child = cloneRoot.firstChild; child != null; child = child.nextSibling)
            result.push(child);

        return result;

        function add(node,deep)
        {
            if (cloneMap.containsKey(node))
                return cloneMap.get(node);

            var clone = DOM.cloneNode(node,deep);
            cloneMap.put(node,clone);
            if (node.parentNode == detail.commonAncestor) {
                DOM.appendChild(cloneRoot,clone);
            }
            else {
                var parentClone = add(node.parentNode,false);
                DOM.appendChild(parentClone,clone);
            }
            return clone;
        }
    }

    window.Range = Range;
    Range.prototype.copy = trace(copy);
    Range.prototype.isEmpty = trace(isEmpty);
    Range.prototype.trackWhileExecuting = trace(trackWhileExecuting);
    Range.prototype.selectWholeWords = trace(selectWholeWords);
    Range.prototype.expand = trace(expand);
    Range.prototype.omitEmptyTextSelection = trace(omitEmptyTextSelection);
    Range.prototype.isForwards = trace(isForwards);
    Range.prototype.getInlineNodes = trace(getInlineNodes);
    Range.prototype.getAllNodes = trace(getAllNodes);
    Range.prototype.singleNode = trace(singleNode);
    Range.prototype.ensureRangeValidHierarchy = trace(ensureRangeValidHierarchy);
    Range.prototype.forwards = trace(forwards);
    Range.prototype.detail = trace(detail);
    Range.prototype.getOutermostNodes = trace(getOutermostNodes);
    Range.prototype.getClientRects = trace(getClientRects);
    Range.prototype.cloneContents = trace(cloneContents);

})();
<commit_msg>In ensureRangeValidHierarchy(), make sure the range is being tracked, since the tree may be modified.<commit_after>// Copyright (c) 2011-2012 UX Productivity Pty Ltd. All rights reserved.

(function() {
    function Range(startNode,startOffset,endNode,endOffset)
    {
        this.start = new Position(startNode,startOffset);
        this.end = new Position(endNode,endOffset);
    }

    function copy()
    {
        return new Range(this.start.node,this.start.offset,
                         this.end.node,this.end.offset);
    }

    function isEmpty()
    {
        return ((this.start.node == this.end.node) &&
                (this.start.offset == this.end.offset));
    }

    Range.prototype.toString = function()
    {
        return this.start.toString() + " - " + this.end.toString();
    }

    function trackWhileExecuting(fun)
    {
        Position.trackWhileExecuting([this.start,this.end],fun);
    }

    function selectWholeWords()
    {
        if ((this.start.node.nodeType == Node.TEXT_NODE) &&
            (this.end.node.nodeType == Node.TEXT_NODE)) {
            if (this.isForwards()) {
                // this.start comes before this.end
                this.start.moveToStartOfWord();
                this.end.moveToEndOfWord();
            }
            else {
                // this.end comes before this.end
                this.start.moveToEndOfWord();
                this.end.moveToStartOfWord();
            }
        }
    }

    function expand()
    {
        var doc = this.start.node.ownerDocument;
        while ((this.start.offset == 0) && (this.start.node != doc.body)) {
            var offset = DOM.nodeOffset(this.start.node);
            this.start.node = this.start.node.parentNode;
            this.start.offset = offset;
        }

        while ((this.end.offset == DOM.maxChildOffset(this.end.node)) &&
               (this.end.node != doc.body)) {
            var offset = DOM.nodeOffset(this.end.node);
            this.end.node = this.end.node.parentNode;
            this.end.offset = offset+1;
        }
    }

    function omitEmptyTextSelection()
    {
        if (!this.start.moveBackwardIfAtStart())
            this.start.moveForwardIfAtEnd()
        if (!this.end.moveBackwardIfAtStart())
            this.end.moveForwardIfAtEnd()
    }

    function isForwards()
    {
        var doc = this.start.node.ownerDocument;
        if ((this.start.node.parentNode == null) && (this.start.node != doc.documentElement))
            throw new Error("Range.isForwards "+this+": start node has been removed from document");
        if ((this.end.node.parentNode == null) && (this.end.node != doc.documentElement))
            throw new Error("Range.isForwards "+this+": end node has been removed from document");

        var start = this.start;
        var end = this.end;

        if ((start.node == end.node) && (start.node.nodeType == Node.TEXT_NODE))
            return (end.offset >= start.offset);

        var startParent = null;
        var startChild = null;
        var endParent = null;
        var endChild = null;

        if (end.node.nodeType == Node.ELEMENT_NODE) {
            endParent = end.node;
            endChild = end.node.childNodes[end.offset];
        }
        else {
            endParent = end.node.parentNode;
            endChild = end.node;
        }

        if (start.node.nodeType == Node.ELEMENT_NODE) {
            startParent = start.node;
            startChild = start.node.childNodes[start.offset];
        }
        else {
            startParent = start.node.parentNode;
            startChild = start.node;
            if (startChild == endChild)
                return false;
        }

        var startC = startChild;
        var startP = startParent;
        while (startP != null) {

            var endC = endChild;
            var endP = endParent;
            while (endP != null) {

                if (startP == endC)
                    return false;

                if (startP == endP) {
                    if (endC == null) // endC is last child, so startC must be endC or come before it
                        return true;
                    for (var n = startC; n != null; n = n.nextSibling) {
                        if (n == endC)
                            return true;
                    }
                    return false;
                }

                endC = endP;
                endP = endP.parentNode;
            }

            startC = startP;
            startP = startP.parentNode;
        }
        throw new Error("Could not find common ancestor");
    }

    function getInlineNodes(atLeastOne)
    {
        var all = this.getAllNodes(atLeastOne);
        var result = new Array();
        for (var i = 0; i < all.length; i++) {
            if (isInlineNode(all[i]))
                result.push(all[i]);
        }
        return result;
    }

    function getAllNodes(atLeastOne)
    {
        var result = new Array();
        var outermost = this.getOutermostNodes(atLeastOne);
        for (var i = 0; i < outermost.length; i++)
            addRecursive(outermost[i]);
        return result;

        function addRecursive(node)
        {
            result.push(node);
            for (var child = node.firstChild; child != null; child = child.nextSibling)
                addRecursive(child);
        }
    }

    function singleNode()
    {
        return this.start.closestActualNode();
    }

    function ensureRangeValidHierarchy()
    {
        var range = this;
        this.trackWhileExecuting(function() {
            var nodes = range.getAllNodes(true);

            var depths = new Array();
            for (var i = 0; i < nodes.length; i++) {
                var depth = getNodeDepth(nodes[i]);
                if (depths[depth] == null) {
                    depths[depth] = new Array();
                }
                depths[depth].push(nodes[i]);
            }

            for (var depth = 0; depth < depths.length; depth++) {
                var firstDepth = true;
                if (depths[depth] != null) {
                    for (var i = 0; i < depths[depth].length; i++) {
                        var node = depths[depth][i];
                        Hierarchy.ensureValidHierarchy(node,firstDepth);
                    }
                    firstDepth = false;
                }
            }
        });
    };

    function forwards()
    {
        if (this.isForwards()) {
            return this;
        }
        else {
            var reverse = new Range(this.end.node,this.end.offset,this.start.node,this.start.offset);
            if (!reverse.isForwards())
                throw new Error("Both range "+this+" and its reverse are not forwards");
            return reverse;
        }
    }

    function detail()
    {
        if (!this.isForwards()) {
            var reverse = new Range(this.end.node,this.end.offset,this.start.node,this.start.offset);
            if (!reverse.isForwards())
                throw new Error("Both range "+this+" and its reverse are not forwards");
            return reverse.detail();
        }

        var detail = new Object();
        var start = this.start;
        var end = this.end;

        // Start location
        if (start.node.nodeType == Node.ELEMENT_NODE) {
            detail.startParent = start.node;
            detail.startChild = start.node.childNodes[start.offset];
        }
        else if ((start.node.nodeValue.length > 0) && (start.offset == start.node.nodeValue.length)) {
            detail.startParent = start.node.parentNode;
            detail.startChild = start.node.nextSibling;
        }
        else {
            detail.startParent = start.node.parentNode;
            detail.startChild = start.node;
        }

        // End location
        if (end.node.nodeType == Node.ELEMENT_NODE) {
            detail.endParent = end.node;
            detail.endChild = end.node.childNodes[end.offset];
        }
        else if (end.offset == 0) {
            detail.endParent = end.node.parentNode;
            detail.endChild = end.node;
        }
        else {
            detail.endParent = end.node.parentNode;
            detail.endChild = end.node.nextSibling;
        }

        // Common ancestor
        var startP = detail.startParent;
        var startC = detail.startChild;
        while (startP != null) {
            var endP = detail.endParent;
            var endC = detail.endChild
            while (endP != null) {
                if (startP == endP) {
                    detail.commonAncestor = startP;
                    detail.startAncestor = startC;
                    detail.endAncestor = endC;
                    // Found it
                    return detail;
                }
                endC = endP;
                endP = endP.parentNode;
            }
            startC = startP;
            startP = startP.parentNode;
        }
        throw new Error("Start and end of range have no common ancestor");
    }

    function getOutermostNodes(atLeastOne,info)
    {
        var beforeNodes = new Array();
        var middleNodes = new Array();
        var afterNodes = new Array();

        if (info != null) {
            info.beginning = beforeNodes;
            info.middle = middleNodes;
            info.end = afterNodes;
        }

        if (this.isEmpty())
            return atLeastOne ? [this.singleNode()] : [];

        // Note: start and end are *points* - they are always *in between* nodes or characters, never
        // *at* a node or character.
        // Everything after the end point is excluded from the selection
        // Everything after the start point, but before the end point, is included in the selection

        // We use (parent,child) pairs so that we have a way to represent a point that comes after all
        // the child nodes in a container - in which case the child is null. The parent, however, is
        // always non-null;

        var detail = this.detail();
        if (detail.commonAncestor == null)
            return atLeastOne ? [this.singleNode()] : [];
        var startParent = detail.startParent;
        var startChild = detail.startChild;
        var endParent = detail.endParent;
        var endChild = detail.endChild;
        var commonParent = detail.commonAncestor;
        var startAncestor = detail.startAncestor;
        var endAncestor = detail.endAncestor;

        // Add start nodes
        var topParent = startParent;
        var topChild = startChild;
        while (topParent != commonParent) {
            if (topChild != null)
                beforeNodes.push(topChild);

            while (((topChild == null) || (topChild.nextSibling == null)) &&
                   (topParent != commonParent)) {
                topChild = topParent;
                topParent = topParent.parentNode;
            }
            if (topParent != commonParent)
                topChild = topChild.nextSibling;
        }

        // Add middle nodes
        if (startAncestor != endAncestor) {
            var c = startAncestor;
            if ((c != null) && (c != startChild))
                c = c.nextSibling;
            for (; c != endAncestor; c = c.nextSibling)
                middleNodes.push(c);
        }

        // Add end nodes
        var bottomParent = endParent;
        var bottomChild = endChild;
        while (true) {

            while ((getPreviousSibling(bottomParent,bottomChild) == null) &&
                   (bottomParent != commonParent)) {
                bottomChild = bottomParent;
                bottomParent = bottomParent.parentNode;
            }
            if (bottomParent != commonParent)
                bottomChild = getPreviousSibling(bottomParent,bottomChild);

            if (bottomParent == commonParent)
                break;

            afterNodes.push(bottomChild);
        }
        afterNodes = afterNodes.reverse();

        var result = new Array();

        Array.prototype.push.apply(result,beforeNodes);
        Array.prototype.push.apply(result,middleNodes);
        Array.prototype.push.apply(result,afterNodes);

        if (result.length == 0)
            return atLeastOne ? [this.singleNode()] : [];
        else
            return result;

        function getPreviousSibling(parent,child)
        {
            if (child != null)
                return child.previousSibling;
            else if (parent.lastChild != null)
                return parent.lastChild;
            else
                return null;
        }

        function isAncestorLocation(ancestorParent,ancestorChild,
                                    descendantParent,descendantChild)
        {
            while ((descendantParent != null) &&
                   ((descendantParent != ancestorParent) || (descendantChild != ancestorChild))) {
                descendantChild = descendantParent;
                descendantParent = descendantParent.parentNode;
            }

            return ((descendantParent == ancestorParent) &&
                    (descendantChild == ancestorChild));
        }
    }

    function getClientRects()
    {
        var nodes = this.getOutermostNodes(true);

        // WebKit in iOS 5.0 has a bug where if the selection spans multiple paragraphs, the complete
        // rect for paragraphs other than the first is returned, instead of just the portions of it
        // that are actually in the range. To get around this problem, we go through each text node
        // individually and collect all the rects.
        var result = new Array();
        var doc = this.start.node.ownerDocument;
        var domRange = doc.createRange();
        for (var nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
            var node = nodes[nodeIndex];
            if (node.nodeType == Node.TEXT_NODE) {
                var startOffset = (node == this.start.node) ? this.start.offset : 0;
                var endOffset = (node == this.end.node) ? this.end.offset : node.nodeValue.length;
                domRange.setStart(node,startOffset);
                domRange.setEnd(node,endOffset);
                var rects = domRange.getClientRects();
                for (var rectIndex = 0; rectIndex < rects.length; rectIndex++)
                    result.push(rects[rectIndex]);
            }
            else if (node.nodeType == Node.ELEMENT_NODE) {
                result.push(node.getBoundingClientRect());
            }
        }
        return result;
    }

    function cloneContents()
    {
        var cloneMap = new NodeMap();
        var detail = this.detail();
        var cloneRoot = DOM.cloneNode(detail.commonAncestor,false);
        cloneMap.put(detail.commonAncestor,cloneRoot);
        var outermost = this.getOutermostNodes();
        for (var i = 0; i < outermost.length; i++) {

            var node = outermost[i];
            if (node.nodeType == Node.TEXT_NODE) {
                // FIXME: do this without modifying nodeValue
                var saved = node.nodeValue;
                if ((node == this.start.node) && (node == this.end.node)) {
                    node.nodeValue = node.nodeValue.slice(this.start.offset,this.end.offset);
                }
                else if (node == this.start.node) {
                    node.nodeValue = node.nodeValue.slice(this.start.offset);
                }
                else if (node == this.end.node) {
                    node.nodeValue = node.nodeValue.slice(0,this.end.offset);
                }
                add(outermost[i],true);
                node.nodeValue = saved;
            }
            else {
                add(outermost[i],true);
            }
        }

        var result = new Array();
        for (var child = cloneRoot.firstChild; child != null; child = child.nextSibling)
            result.push(child);

        return result;

        function add(node,deep)
        {
            if (cloneMap.containsKey(node))
                return cloneMap.get(node);

            var clone = DOM.cloneNode(node,deep);
            cloneMap.put(node,clone);
            if (node.parentNode == detail.commonAncestor) {
                DOM.appendChild(cloneRoot,clone);
            }
            else {
                var parentClone = add(node.parentNode,false);
                DOM.appendChild(parentClone,clone);
            }
            return clone;
        }
    }

    window.Range = Range;
    Range.prototype.copy = trace(copy);
    Range.prototype.isEmpty = trace(isEmpty);
    Range.prototype.trackWhileExecuting = trace(trackWhileExecuting);
    Range.prototype.selectWholeWords = trace(selectWholeWords);
    Range.prototype.expand = trace(expand);
    Range.prototype.omitEmptyTextSelection = trace(omitEmptyTextSelection);
    Range.prototype.isForwards = trace(isForwards);
    Range.prototype.getInlineNodes = trace(getInlineNodes);
    Range.prototype.getAllNodes = trace(getAllNodes);
    Range.prototype.singleNode = trace(singleNode);
    Range.prototype.ensureRangeValidHierarchy = trace(ensureRangeValidHierarchy);
    Range.prototype.forwards = trace(forwards);
    Range.prototype.detail = trace(detail);
    Range.prototype.getOutermostNodes = trace(getOutermostNodes);
    Range.prototype.getClientRects = trace(getClientRects);
    Range.prototype.cloneContents = trace(cloneContents);

})();
<|endoftext|>


<commit_before>;(function (root, factory) {
    if ( typeof define === 'function' && define.amd ) {
        define(['jquery'], function ($) {
            return factory($);
        });
    }
    else {
        factory(root.jQuery);
    }
}(this, function ($) {

    //******************/
    //****  FORMS   ****/
    //******************/
    (function () {

        var $forms = $('form.modal'),
            valid_class = 'valid',
            invalid_class = 'invalid',
            help_class = 'help',
            changeHandler = function () {
                var valid = true,
                    $this = $(this);
                $this.next().hide();
                if ( this.type in validation_map ) {
                    valid = validation_map[this.type].test(this.value);
                }
                if ( valid ) {
                    $this.addClass(valid_class);
                    $this.removeClass(invalid_class);
                }
                else {
                    $this.addClass(invalid_class);
                    $this.removeClass(valid_class);
                }
                $this.closest('form').trigger(valid ? 'validate' : 'invalid');
            },
            editHandler = function (e) {
                var $this = $(this);
                $this.removeClass(valid_class + ' ' + invalid_class)
                    .next()
                        .show();
            },
            validateHandler = function () {
                var valid = false,
                    $form = $(this).find('input').each(function () {
                                if ( this.type in validation_map ) {
                                    if ( ! validation_map[this.type].test($(this).val()) ) {
                                        valid = false;
                                        // break
                                        return false;
                                    }
                                }
                                valid = true;
                            }).end();
                $form.trigger(valid ? 'valid' : 'invalid');
            },
            validHandler = function () {
                $(this).find('.mock-button').addClass('hide')
                    .end().find('input[type=submit]').removeClass('hide');
            },
            invalidHandler = function () {
                $(this).find('input[type=submit]').addClass('hide')
                    .end().find('.mock-button').removeClass('hide');
            },
            validation_map = {};

        $forms.on({
                validate: validateHandler,
                valid   : validHandler,
                invalid : invalidHandler
            })
            .each(function (i, form) {
                $(form).find('input')
                    .on('change', changeHandler)
                    .on('focus', editHandler)
                    .each(function (i, input) {
                        var $input = $(input),
                            pattern = $input.attr('pattern'),
                            type = input.type;
                        if ( pattern && ! (type in validation_map) ) {
                            validation_map[type] = RegExp(pattern);
                        }
                    });
            });

        $forms.submit(function(event) {
            var $form = $(this),
                action = $form.find(':submit'),
                mock_button = $form.find('.mock-button'),
                notices = $form.find('.notices');

            event.preventDefault();

            action.val('Wait').attr('disabled', 'disabled');

            $.ajax({
                type: $form.attr("method"),
                url: $form.attr("action"),
                data: $form.serialize(),
                dataType: "json"
            })

            .done(function(response) {
                notices.html(response.data);
                action.removeAttr('disabled');

                // close the modal
                $('[id^="overlay"]').hide();
                // update to show user
                // LOLz
                location.reload(true);
            })

            .fail(function(jqXHR, textStatus) {
                notices.html(jqXHR.responseText);
                action.val('Cancel').removeAttr('disabled').addClass('hide');
                mock_button.removeClass('hide');
            });
        });

    }());

    //******************/
    //**** OVERLAYS ****/
    //******************/
    (function () {

        $('#overlay, .mock-button, [id^="overlay"] .close').click(function (event) {
            $('[id^="overlay"]').hide();
        });

        $('.login-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-login form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-login').show();
            $('#overlay-contact').hide();
            $('#overlay-register').hide();
            $('#overlay-password-reset').hide();
            $('#overlay-password-change').hide();
        });

        $('.register-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-register form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-register').show();
            $('#overlay-contact').hide();
            $('#overlay-login').hide();
            $('#overlay-password-reset').hide();
            $('#overlay-password-change').hide();
        });

        $('.password-reset-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-password-reset form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-password-reset').show();
            $('#overlay-contact').hide();
            $('#overlay-register').hide();
            $('#overlay-login').hide();
            $('#overlay-password-change').hide();
        });

        $('.password-change-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-password-change form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-password-change').show();
            $('#overlay-contact').hide();
            $('#overlay-register').hide();
            $('#overlay-login').hide();
            $('#overlay-password-show').hide();
        });

        $('.contact-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-contact form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-contact').show();
            $('#overlay-password-change').hide();
            $('#overlay-register').hide();
            $('#overlay-login').hide();
            $('#overlay-password-show').hide();
            $('#panel-nav').hide();
        });

    }());

    // NAV PANEL UX
    (function () {

        $('#panel-action').mousemove(function(e){
            var x = e.pageX - this.offsetLeft,
            y = e.pageY - this.offsetTop;

            if (x >= 295) {

                $('#panel-nav').show();

            } else {

                $('#nav-anchor').mouseenter(function (event) {
                    event.preventDefault();
                    $('#panel-nav').show();
                });

                $('#panel-nav').mouseleave(function () {
                    $('#panel-nav').hide();
                });

                $('#panel-nav-close').click(function () {
                    $('#panel-nav').hide();
                });

            }

         });

    }());

    // PROJECT GALLERY UX
    (function () {

        $('.project').mouseenter(function (event) {
            $('.projects-public, .projects-developers').children().addClass('reduce');
            $(this).removeClass('reduce').find('.detail').show();
        });

        $('.project').mouseleave(function (event) {
            $('.projects-public, .projects-developers').children().removeClass('reduce');
            $(this).find('.detail').hide();
        });

    }());

}));
<commit_msg>Added initial auto validation if inputs are prefilled by browser or content is left from backward/forward navigation<commit_after>;(function (root, factory) {
    if ( typeof define === 'function' && define.amd ) {
        define(['jquery'], function ($) {
            return factory($);
        });
    }
    else {
        factory(root.jQuery);
    }
}(this, function ($) {

    //******************/
    //****  FORMS   ****/
    //******************/
    (function () {

        var $forms = $('form.modal'),
            valid_class = 'valid',
            invalid_class = 'invalid',
            help_class = 'help',
            changeHandler = function () {
                var valid = true,
                    $this = $(this);
                $this.next().hide();
                if ( this.type in validation_map ) {
                    valid = validation_map[this.type].test(this.value);
                }
                if ( valid ) {
                    $this.addClass(valid_class);
                    $this.removeClass(invalid_class);
                }
                else {
                    $this.addClass(invalid_class);
                    $this.removeClass(valid_class);
                }
                $this.closest('form').trigger(valid ? 'validate' : 'invalid');
            },
            editHandler = function (e) {
                var $this = $(this);
                $this.removeClass(valid_class + ' ' + invalid_class)
                    .next()
                        .show();
            },
            validateHandler = function () {
                var valid = false,
                    $form = $(this).find('input').each(function () {
                                if ( this.type in validation_map ) {
                                    if ( ! validation_map[this.type].test($(this).val()) ) {
                                        valid = false;
                                        // break
                                        return false;
                                    }
                                }
                                valid = true;
                            }).end();
                $form.trigger(valid ? 'valid' : 'invalid');
            },
            validHandler = function () {
                $(this).find('.mock-button').addClass('hide')
                    .end().find('input[type=submit]').removeClass('hide');
            },
            invalidHandler = function () {
                $(this).find('input[type=submit]').addClass('hide')
                    .end().find('.mock-button').removeClass('hide');
            },
            validation_map = {};

        $forms.on({
                validate: validateHandler,
                valid   : validHandler,
                invalid : invalidHandler
            })
            .each(function (i, form) {
                $(form).find('input')
                    .on('change', changeHandler)
                    .on('focus', editHandler)
                    .each(function (i, input) {
                        var $input = $(input),
                            pattern = $input.attr('pattern'),
                            type = input.type;
                        if ( pattern && ! (type in validation_map) ) {
                            validation_map[type] = RegExp(pattern);
                        }
                    });
            });

        $forms.submit(function(event) {
            var $form = $(this),
                action = $form.find(':submit'),
                mock_button = $form.find('.mock-button'),
                notices = $form.find('.notices');

            event.preventDefault();

            action.val('Wait').attr('disabled', 'disabled');

            $.ajax({
                type: $form.attr("method"),
                url: $form.attr("action"),
                data: $form.serialize(),
                dataType: "json"
            })

            .done(function(response) {
                notices.html(response.data);
                action.removeAttr('disabled');

                // close the modal
                $('[id^="overlay"]').hide();
                // update to show user
                // LOLz
                location.reload(true);
            })

            .fail(function(jqXHR, textStatus) {
                notices.html(jqXHR.responseText);
                action.val('Cancel').removeAttr('disabled').addClass('hide');
                mock_button.removeClass('hide');
            });
        });

    }());

    //******************/
    //**** OVERLAYS ****/
    //******************/
    (function () {

        $('#overlay, .mock-button, [id^="overlay"] .close').click(function (event) {
            $('[id^="overlay"]').hide();
        });

        $('.login-link').click(function (event) {
            event.preventDefault();
            var $form = $('form');
            $form.removeAttr('id');
            $('#overlay-login form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-login').show();
            $('#overlay-contact').hide();
            $('#overlay-register').hide();
            $('#overlay-password-reset').hide();
            $('#overlay-password-change').hide();

            // if the browser prefilled the data or kept it in backward/forward navigation
            // trigger change on every non-empty input
            $form.find('.field input').each(function () {
                var $input = $(this);
                if ( $input.val() ) {
                    $input.trigger('change');
                }
            });
        });

        $('.register-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-register form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-register').show();
            $('#overlay-contact').hide();
            $('#overlay-login').hide();
            $('#overlay-password-reset').hide();
            $('#overlay-password-change').hide();
        });

        $('.password-reset-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-password-reset form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-password-reset').show();
            $('#overlay-contact').hide();
            $('#overlay-register').hide();
            $('#overlay-login').hide();
            $('#overlay-password-change').hide();
        });

        $('.password-change-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-password-change form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-password-change').show();
            $('#overlay-contact').hide();
            $('#overlay-register').hide();
            $('#overlay-login').hide();
            $('#overlay-password-show').hide();
        });

        $('.contact-link').click(function (event) {
            event.preventDefault();
            $('form').removeAttr('id');
            $('#overlay-contact form').attr('id', 'active-form');
            $('#overlay').show();
            $('#overlay-contact').show();
            $('#overlay-password-change').hide();
            $('#overlay-register').hide();
            $('#overlay-login').hide();
            $('#overlay-password-show').hide();
            $('#panel-nav').hide();
        });

    }());

    // NAV PANEL UX
    (function () {

        $('#panel-action').mousemove(function(e){
            var x = e.pageX - this.offsetLeft,
            y = e.pageY - this.offsetTop;

            if (x >= 295) {

                $('#panel-nav').show();

            } else {

                $('#nav-anchor').mouseenter(function (event) {
                    event.preventDefault();
                    $('#panel-nav').show();
                });

                $('#panel-nav').mouseleave(function () {
                    $('#panel-nav').hide();
                });

                $('#panel-nav-close').click(function () {
                    $('#panel-nav').hide();
                });

            }

         });

    }());

    // PROJECT GALLERY UX
    (function () {

        $('.project').mouseenter(function (event) {
            $('.projects-public, .projects-developers').children().addClass('reduce');
            $(this).removeClass('reduce').find('.detail').show();
        });

        $('.project').mouseleave(function (event) {
            $('.projects-public, .projects-developers').children().removeClass('reduce');
            $(this).find('.detail').hide();
        });

    }());

}));
<|endoftext|>


<commit_before> $(document).ready(function()
 {
    ShowActionOnOver();
    guessRepo();
    checkVersion();
 });

warning = '

Note

 You are not using the most up to date version of the library. '

 function checkVersion() {
    $.ajax({
     type: 'GET',
     url: "/api/v1/version/" + doc_slug + "/highest/" + doc_version + "/",
     success: function(data, textStatus, request) {
      if (!data.is_highest) {
         $("div.body").prepend(warning + "" + data.version + " is the newest version.

")
      }
     },
     dataType: 'json'
    });
 }

 function ShowActionOnOver()
 {
   $(".module-item-menu").hover(
       function()
       {
          $(".hidden-child",this).show();
       },
       function()
       {
           $(".hidden-child",this).hide();
        }
    );
 }

function guessRepo() {
  $("#id_repo").blur(
      function() {
         val = this.value
         repo = $("#id_repo_type")[0]
         if (val.indexOf("git") >= 0) {
            repo.value = "git"
         }
         else if (val.indexOf("bitbucket") >= 0) {
            repo.value = "hg"
         }
         else if (val.indexOf("hg") >= 0) {
            repo.value = "hg"
         }
         else if (val.indexOf("bzr") >= 0) {
            repo.value = "bzr"
         }
         else if (val.indexOf("launchpad") >= 0) {
            repo.value = "bzr"
         }
         else if (val.indexOf("trunk") >= 0) {
            repo.value = "svn"
         }
         else if (val.indexOf("svn") >= 0) {
            repo.value = "svn"
         }
      }
  )
}
<commit_msg>Hard code the URL so it works on subdomains/CNAMEs.<commit_after> $(document).ready(function()
 {
    ShowActionOnOver();
    guessRepo();
    checkVersion();
 });

warning = '

Note

 You are not using the most up to date version of the library. '

 function checkVersion() {
    $.ajax({
     type: 'GET',
     url: "http://readthedocs.org/api/v1/version/" + doc_slug + "/highest/" + doc_version + "/",
     success: function(data, textStatus, request) {
      if (!data.is_highest) {
         $("div.body").prepend(warning + "" + data.version + " is the newest version.

")
      }
     },
     dataType: 'json'
    });
 }

 function ShowActionOnOver()
 {
   $(".module-item-menu").hover(
       function()
       {
          $(".hidden-child",this).show();
       },
       function()
       {
           $(".hidden-child",this).hide();
        }
    );
 }

function guessRepo() {
  $("#id_repo").blur(
      function() {
         val = this.value
         repo = $("#id_repo_type")[0]
         if (val.indexOf("git") >= 0) {
            repo.value = "git"
         }
         else if (val.indexOf("bitbucket") >= 0) {
            repo.value = "hg"
         }
         else if (val.indexOf("hg") >= 0) {
            repo.value = "hg"
         }
         else if (val.indexOf("bzr") >= 0) {
            repo.value = "bzr"
         }
         else if (val.indexOf("launchpad") >= 0) {
            repo.value = "bzr"
         }
         else if (val.indexOf("trunk") >= 0) {
            repo.value = "svn"
         }
         else if (val.indexOf("svn") >= 0) {
            repo.value = "svn"
         }
      }
  )
}
<|endoftext|>


<commit_before><commit_msg>bug fix -- isObject<commit_after><|endoftext|>


<commit_before>/*
 Copyright (C) 2015-2017 GEM Foundation

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/agpl.html>.
 */

(function($, Backbone, _) {
    var calculation_table;

    var progressHandlingFunction = function(progress) {
        var percent = progress.loaded / progress.total * 100;
        $('.bar').css('width', percent + '%');
        if (percent == 100) {
            dialog.hide();
        }
    };

    var htmlEscape = function (record) {
        // record[3] is the log message
        record[3] = record[3].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
        return record
    };

    var dialog = (function ()
                  {
                      var pleaseWaitDiv = $('

Processing...

');
                      return {
                          show: function(msg, progress) {
                              $('h1', pleaseWaitDiv).text(msg);
                              if (progress) {
                                  progressHandlingFunction({loaded: 0, total: 1});
                              } else {
                                  progressHandlingFunction({loaded: 1, total: 1});
                              }
                              pleaseWaitDiv.modal('show');
                          },
                          hide: function () {
                              pleaseWaitDiv.modal('hide');
                          }
                      };
                  })();

    var diaerror = (function ()
                  {
                      var errorDiv = $('
\

\

\

\

Calculation not accepted: traceback
\

\

\

\

\
                      <input type="checkbox" id="diaerror_scroll_enabled" checked>\
                      Auto Scroll   \
                      <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\

\

\

\

');
                      errorDiv.bind('hide', function() { calculation_table.hide_log(); });
                      return {
                          getdiv: function() {
                              return errorDiv;
                          },

                          show: function(is_large, title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              if (msg != null) {
                                  $('.modal-body-pre', errorDiv).html(msg);
                              }
                              if (is_large) {
                                  errorDiv.addClass("errorDialogLarge");
                              }
                              else {
                                  errorDiv.removeClass("errorDialogLarge");
                              }
                              errorDiv.modal('show');
                          },

                          append: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              $( msg ).appendTo( $('.modal-body-pre', errorDiv) );
                          },

                          scroll_to_bottom: function(ctx) {
                              ctx.scrollTop(ctx[0].scrollHeight);
                          },

                          hide: function () {
                              errorDiv.modal('hide');
                          }
                      };
                  })();

    var CalculationTable = Backbone.View.extend(
        {
            /* the html element where the table is rendered */
            el: $('#my-calculations'),

            logXhr: null,
            logId: -1,
            logIsNew: false,
            logLinesAll: 0,
            logLines: 0,
            logTimeout: null,

            initialize: function(options) {

                /* whatever happens to any calculation, re-render the table */
                _.bindAll(this, 'render');
                this.calculations = options.calculations;
                this.calculations.bind('reset', this.render);
                this.calculations.bind('add', this.render);
                this.calculations.bind('remove', this.render);

                /* if false, it prevents the table to be refreshed */
                this.can_be_rendered = true;

                this.render();
            },

            events: {
                "click .btn-show-remove": "remove_calculation",
                "click .btn-danger": "show_modal_confirm_remove_calculation",
                "click .btn-hide-remove": "hide_modal_confirm_remove_calculation",
                "click .btn-traceback": "show_traceback",
                "click .btn-log": "show_log",
                "click .btn-file": "on_run_risk_clicked",
                "change .btn-file input": "on_run_risk_queued"
            },

            /* When an input dialog is opened, it is very important to not re-render the table */
            on_run_risk_clicked: function(e) {
                /* if a file input dialog has been opened do not refresh the calc table */
                this.can_be_rendered = false;
            },

            on_run_risk_queued: function(e) {
                this.can_be_rendered = true;
            },

            show_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var show_or_back = (function(e) {
                    this.conf_show = $('#confirmDialog' + calc_id).show();
                    this.back_conf_show = $('.back_confirmDialog' + calc_id).show();
                    closeTimer();
                })();
            },

            hide_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var hide_or_back = (function(e) {
                    this.conf_hide = $('#confirmDialog' + calc_id).hide();
                    this.back_conf_hide = $('.back_confirmDialog' + calc_id).hide();
                    setTimer();
                })();
            },

            remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var calc_desc = $(e.target).attr('data-calc-desc');
                var view = this;
                diaerror.show(false, "Removing calculation " + calc_id, "...");

                var hide_or_back = (function(e) {
                    this.conf_hide = $('#confirmDialog' + calc_id).hide();
                    this.back_conf_hide = $('.back_confirmDialog' + calc_id).hide();
                    setTimer();
                })();

                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/remove",
                                    type: "POST",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 403) {
                                            diaerror.show(false, "Error:", "Calculation:
(" + calc_id + ") " + calc_desc + " " + JSON.parse(jqXHR.responseText).error);
                                        }
                                    },
                                    success: function(data, textStatus, jqXHR) {
                                        err = data.error;
                                        if(!err) {
                                            err = "removed.";
                                        }
                                        diaerror.show(false, "Calculation removed", "Calculation:
(" + calc_id + ") " + calc_desc + " " + err );
                                        view.calculations.remove([view.calculations.get(calc_id)]);
                                    }});
            },

            show_traceback: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/traceback",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 404) {
                                            diaerror.show(false, "Calculation " + calc_id + " not found.");
                                        }
                                        else {
                                            diaerror.show(false, "Error retrieving traceback for calculation " + calc_id, textStatus);
                                        }
                                        // alert("Error: " + textStatus);
                                    },
                                    success: function (data, textStatus, jqXHR) {
                                        if (data.length == 0) {
                                            diaerror.show(true, "Traceback not found for calculation " + calc_id, []);
                                        }
                                        else {
                                            var out = "";
                                            var ct = 0;
                                            for (s in data) {
                                                if (data[s] == "")
                                                    continue;
                                                out += '
' + data[s] + '
';
                                                ct++;
                                            }
                                            diaerror.show(true, "Traceback of calculation " + calc_id, out);
                                        }
                                        // alert("Success: " + textStatus);
                                    }});
            },

            _show_log_priv: function(is_new, calc_id, is_running, from) {
                var was_running = is_running;

                // TO CHECK hide_log method enable console.log and take a look
                // console.log("_show_log_priv: begin");
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                if (is_new) {
                    if (this.logTimeout != null) {
                        window.clearTimeout(this.logTimeout);
                        this.logTimeout = null;
                    }
                }
                var obj = this;

                this.logXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/log/" + from + ":",
                                      error: function (jqXHR, textStatus, errorThrown) {
                                          if (jqXHR.status == 404) {
                                              diaerror.show(true, "Log of calculation " + calc_id + " not found.");
                                          }
                                          else {
                                              diaerror.show(true, "Error retrieving log for calculation " + calc_id, textStatus);
                                          }
                                          obj.logIsNew = false;
                                      },
                                      success: function (data, textStatus, jqXHR) {
                                          var delay = 250;

                                          if (is_new) {
                                              obj.logLines = 0;
                                              obj.logLinesAll = 0;
                                          }
                                          else {
                                              // if data is empty check if job is still running
                                              if (is_running) {
                                                  if (data.length == 0) {
                                                      var ajax, status;

                                                      delay = 1000;

                                                      ajax = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/status",
                                                                     async: false}).done(function (data) { status = data.is_running; });
                                                      if (status !== true) {
                                                          is_running = false;
                                                      }
                                                  }
                                              }
                                          }
                                          var out = "";

                                          for (s in data) {
                                              if (data[s] == "") {
                                                  obj.logLinesAll++;
                                                  continue;
                                              }
                                              out += '
' + htmlEscape(data[s]) + '
';
                                              obj.logLines++;
                                              obj.logLinesAll++;
                                          }

                                          var title;
                                          if (is_running) {
                                              var dt;
                                              dt = new Date();
                                              title = "Log of calculation " + calc_id + " - " + dt.toString();
                                          }
                                          else if (was_running != is_running) {
                                              title = "Log of calculation " + calc_id + " - finished";
                                          }
                                          else {
                                              title = "Log of calculation " + calc_id;
                                          }

                                          if (obj.logIsNew) {
                                              diaerror.show(true, title, out);
                                          }
                                          else {
                                              diaerror.append(title, out);
                                          }
                                          if ($("#diaerror_scroll_enabled").prop( "checked" ) || was_running == false) {
                                              diaerror.scroll_to_bottom($('.modal-body', diaerror.getdiv()));
                                          }

                                          if (is_running) {
                                              function log_update(obj)
                                              {
                                                  obj._show_log_priv(false, obj.logId, true, obj.logLinesAll);
                                              }

                                              obj.logTimeout = window.setTimeout(log_update, delay, obj);
                                          }
                                          else {
                                              $('#diaerror_scroll_enabled_box').hide();
                                          }

                                          obj.logIsNew = false;
                                      }});
            },

            show_log: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var is_running = ($(e.target).attr('is-running') == "true");

                this.logId = calc_id;
                this.logIsNew = true;

                if (is_running)
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).show();
                else
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).hide();

                this._show_log_priv(true, calc_id, is_running, "0");
            },

            hide_log: function(e) {
                if (this.logTimeout != null) {
                    window.clearTimeout(this.logTimeout);
                    this.logTimeout = null;
                }
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                $('#diaerror_scroll_enabled_box').hide();
            },

            render: function() {
                if (!this.can_be_rendered) {
                    return;
                };
                this.$el.html(_.template($('#calculation-table-template').html(),
                                         { calculations: this.calculations.models }));
            }
        });

    var Calculation = Backbone.Model.extend({
        defaults: {
            map: null,
            layers: []
        }
    });

    var Calculations = Backbone.Collection.extend(
        {
            model: Calculation,
            url: gem_oq_server_url + "/v1/calc/list?relevant=true"
        });
    var calculations = new Calculations();

    var refresh_calcs;

    function setTimer() {
        refresh_calcs = setInterval(function() { calculations.fetch({reset: true}) }, 3000);
    }

    function closeTimer() {
        refresh_calcs = clearInterval(refresh_calcs);
    }

    /* classic event management */
    $(document).ready(
        function() {
            calculation_table = new CalculationTable({ calculations: calculations });
            calculations.fetch({reset: true});
            setTimer();

            /* XXX. Reset the input file value to ensure the change event
               will be always triggered */
            $(document).on("click", 'input[name=archive]',
                           function(e) { this.value = null; });
            $(document).on("change", 'input[name=archive]',
                           function(e) {
                               dialog.show('Uploading calculation', true);
                               var input = $(e.target);
                               var form = input.parents('form')[0];

                               $(form).ajaxSubmit(
                                   {
                                    xhr: function() {  // custom xhr to add progress bar management
                                        var myXhr = $.ajaxSettings.xhr();
                                        if(myXhr.upload){ // if upload property exists
                                            myXhr.upload.addEventListener('progress', progressHandlingFunction, false);
                                        }
                                        return myXhr;
                                    },
                                    success: function(data) {
                                        calculations.add(new Calculation(data), {at: 0});
                                    },
                                    error: function(xhr) {
                                        dialog.hide();
                                        var s, out, data = $.parseJSON(xhr.responseText);
                                        var out = "";
                                        var ct = 0;
                                        for (s in data) {
                                            if (data[s] == "")
                                                continue;
                                            out += '
' + data[s] + '
';
                                            ct++;
                                        }
                                        diaerror.show(false, "Calculation not accepted: traceback", out);
                                    }});
                           });

            $(document).on('hidden.bs.modal', 'div[id^=traceback-]',
                           function(e) {
                               setTimer();
                           });

        });
})($, Backbone, _, gem_oq_server_url);
<commit_msg>deleted two points after error title modal [skip CI]<commit_after>/*
 Copyright (C) 2015-2017 GEM Foundation

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/agpl.html>.
 */

(function($, Backbone, _) {
    var calculation_table;

    var progressHandlingFunction = function(progress) {
        var percent = progress.loaded / progress.total * 100;
        $('.bar').css('width', percent + '%');
        if (percent == 100) {
            dialog.hide();
        }
    };

    var htmlEscape = function (record) {
        // record[3] is the log message
        record[3] = record[3].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
        return record
    };

    var dialog = (function ()
                  {
                      var pleaseWaitDiv = $('

Processing...

');
                      return {
                          show: function(msg, progress) {
                              $('h1', pleaseWaitDiv).text(msg);
                              if (progress) {
                                  progressHandlingFunction({loaded: 0, total: 1});
                              } else {
                                  progressHandlingFunction({loaded: 1, total: 1});
                              }
                              pleaseWaitDiv.modal('show');
                          },
                          hide: function () {
                              pleaseWaitDiv.modal('hide');
                          }
                      };
                  })();

    var diaerror = (function ()
                  {
                      var errorDiv = $('
\

\

\

\

Calculation not accepted: traceback
\

\

\

\

\
                      <input type="checkbox" id="diaerror_scroll_enabled" checked>\
                      Auto Scroll   \
                      <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\

\

\

\

');
                      errorDiv.bind('hide', function() { calculation_table.hide_log(); });
                      return {
                          getdiv: function() {
                              return errorDiv;
                          },

                          show: function(is_large, title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              if (msg != null) {
                                  $('.modal-body-pre', errorDiv).html(msg);
                              }
                              if (is_large) {
                                  errorDiv.addClass("errorDialogLarge");
                              }
                              else {
                                  errorDiv.removeClass("errorDialogLarge");
                              }
                              errorDiv.modal('show');
                          },

                          append: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              $( msg ).appendTo( $('.modal-body-pre', errorDiv) );
                          },

                          scroll_to_bottom: function(ctx) {
                              ctx.scrollTop(ctx[0].scrollHeight);
                          },

                          hide: function () {
                              errorDiv.modal('hide');
                          }
                      };
                  })();

    var CalculationTable = Backbone.View.extend(
        {
            /* the html element where the table is rendered */
            el: $('#my-calculations'),

            logXhr: null,
            logId: -1,
            logIsNew: false,
            logLinesAll: 0,
            logLines: 0,
            logTimeout: null,

            initialize: function(options) {

                /* whatever happens to any calculation, re-render the table */
                _.bindAll(this, 'render');
                this.calculations = options.calculations;
                this.calculations.bind('reset', this.render);
                this.calculations.bind('add', this.render);
                this.calculations.bind('remove', this.render);

                /* if false, it prevents the table to be refreshed */
                this.can_be_rendered = true;

                this.render();
            },

            events: {
                "click .btn-show-remove": "remove_calculation",
                "click .btn-danger": "show_modal_confirm_remove_calculation",
                "click .btn-hide-remove": "hide_modal_confirm_remove_calculation",
                "click .btn-traceback": "show_traceback",
                "click .btn-log": "show_log",
                "click .btn-file": "on_run_risk_clicked",
                "change .btn-file input": "on_run_risk_queued"
            },

            /* When an input dialog is opened, it is very important to not re-render the table */
            on_run_risk_clicked: function(e) {
                /* if a file input dialog has been opened do not refresh the calc table */
                this.can_be_rendered = false;
            },

            on_run_risk_queued: function(e) {
                this.can_be_rendered = true;
            },

            show_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var show_or_back = (function(e) {
                    this.conf_show = $('#confirmDialog' + calc_id).show();
                    this.back_conf_show = $('.back_confirmDialog' + calc_id).show();
                    closeTimer();
                })();
            },

            hide_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var hide_or_back = (function(e) {
                    this.conf_hide = $('#confirmDialog' + calc_id).hide();
                    this.back_conf_hide = $('.back_confirmDialog' + calc_id).hide();
                    setTimer();
                })();
            },

            remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var calc_desc = $(e.target).attr('data-calc-desc');
                var view = this;
                diaerror.show(false, "Removing calculation " + calc_id, "...");

                var hide_or_back = (function(e) {
                    this.conf_hide = $('#confirmDialog' + calc_id).hide();
                    this.back_conf_hide = $('.back_confirmDialog' + calc_id).hide();
                    setTimer();
                })();

                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/remove",
                                    type: "POST",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 403) {
                                            diaerror.show(false, "Error", "Calculation:
(" + calc_id + ") " + calc_desc + " " + JSON.parse(jqXHR.responseText).error);
                                        }
                                    },
                                    success: function(data, textStatus, jqXHR) {
                                        err = data.error;
                                        if(!err) {
                                            err = "removed.";
                                        }
                                        diaerror.show(false, "Calculation removed", "Calculation:
(" + calc_id + ") " + calc_desc + " " + err );
                                        view.calculations.remove([view.calculations.get(calc_id)]);
                                    }});
            },

            show_traceback: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/traceback",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 404) {
                                            diaerror.show(false, "Calculation " + calc_id + " not found.");
                                        }
                                        else {
                                            diaerror.show(false, "Error retrieving traceback for calculation " + calc_id, textStatus);
                                        }
                                        // alert("Error: " + textStatus);
                                    },
                                    success: function (data, textStatus, jqXHR) {
                                        if (data.length == 0) {
                                            diaerror.show(true, "Traceback not found for calculation " + calc_id, []);
                                        }
                                        else {
                                            var out = "";
                                            var ct = 0;
                                            for (s in data) {
                                                if (data[s] == "")
                                                    continue;
                                                out += '
' + data[s] + '
';
                                                ct++;
                                            }
                                            diaerror.show(true, "Traceback of calculation " + calc_id, out);
                                        }
                                        // alert("Success: " + textStatus);
                                    }});
            },

            _show_log_priv: function(is_new, calc_id, is_running, from) {
                var was_running = is_running;

                // TO CHECK hide_log method enable console.log and take a look
                // console.log("_show_log_priv: begin");
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                if (is_new) {
                    if (this.logTimeout != null) {
                        window.clearTimeout(this.logTimeout);
                        this.logTimeout = null;
                    }
                }
                var obj = this;

                this.logXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/log/" + from + ":",
                                      error: function (jqXHR, textStatus, errorThrown) {
                                          if (jqXHR.status == 404) {
                                              diaerror.show(true, "Log of calculation " + calc_id + " not found.");
                                          }
                                          else {
                                              diaerror.show(true, "Error retrieving log for calculation " + calc_id, textStatus);
                                          }
                                          obj.logIsNew = false;
                                      },
                                      success: function (data, textStatus, jqXHR) {
                                          var delay = 250;

                                          if (is_new) {
                                              obj.logLines = 0;
                                              obj.logLinesAll = 0;
                                          }
                                          else {
                                              // if data is empty check if job is still running
                                              if (is_running) {
                                                  if (data.length == 0) {
                                                      var ajax, status;

                                                      delay = 1000;

                                                      ajax = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/status",
                                                                     async: false}).done(function (data) { status = data.is_running; });
                                                      if (status !== true) {
                                                          is_running = false;
                                                      }
                                                  }
                                              }
                                          }
                                          var out = "";

                                          for (s in data) {
                                              if (data[s] == "") {
                                                  obj.logLinesAll++;
                                                  continue;
                                              }
                                              out += '
' + htmlEscape(data[s]) + '
';
                                              obj.logLines++;
                                              obj.logLinesAll++;
                                          }

                                          var title;
                                          if (is_running) {
                                              var dt;
                                              dt = new Date();
                                              title = "Log of calculation " + calc_id + " - " + dt.toString();
                                          }
                                          else if (was_running != is_running) {
                                              title = "Log of calculation " + calc_id + " - finished";
                                          }
                                          else {
                                              title = "Log of calculation " + calc_id;
                                          }

                                          if (obj.logIsNew) {
                                              diaerror.show(true, title, out);
                                          }
                                          else {
                                              diaerror.append(title, out);
                                          }
                                          if ($("#diaerror_scroll_enabled").prop( "checked" ) || was_running == false) {
                                              diaerror.scroll_to_bottom($('.modal-body', diaerror.getdiv()));
                                          }

                                          if (is_running) {
                                              function log_update(obj)
                                              {
                                                  obj._show_log_priv(false, obj.logId, true, obj.logLinesAll);
                                              }

                                              obj.logTimeout = window.setTimeout(log_update, delay, obj);
                                          }
                                          else {
                                              $('#diaerror_scroll_enabled_box').hide();
                                          }

                                          obj.logIsNew = false;
                                      }});
            },

            show_log: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var is_running = ($(e.target).attr('is-running') == "true");

                this.logId = calc_id;
                this.logIsNew = true;

                if (is_running)
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).show();
                else
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).hide();

                this._show_log_priv(true, calc_id, is_running, "0");
            },

            hide_log: function(e) {
                if (this.logTimeout != null) {
                    window.clearTimeout(this.logTimeout);
                    this.logTimeout = null;
                }
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                $('#diaerror_scroll_enabled_box').hide();
            },

            render: function() {
                if (!this.can_be_rendered) {
                    return;
                };
                this.$el.html(_.template($('#calculation-table-template').html(),
                                         { calculations: this.calculations.models }));
            }
        });

    var Calculation = Backbone.Model.extend({
        defaults: {
            map: null,
            layers: []
        }
    });

    var Calculations = Backbone.Collection.extend(
        {
            model: Calculation,
            url: gem_oq_server_url + "/v1/calc/list?relevant=true"
        });
    var calculations = new Calculations();

    var refresh_calcs;

    function setTimer() {
        refresh_calcs = setInterval(function() { calculations.fetch({reset: true}) }, 3000);
    }

    function closeTimer() {
        refresh_calcs = clearInterval(refresh_calcs);
    }

    /* classic event management */
    $(document).ready(
        function() {
            calculation_table = new CalculationTable({ calculations: calculations });
            calculations.fetch({reset: true});
            setTimer();

            /* XXX. Reset the input file value to ensure the change event
               will be always triggered */
            $(document).on("click", 'input[name=archive]',
                           function(e) { this.value = null; });
            $(document).on("change", 'input[name=archive]',
                           function(e) {
                               dialog.show('Uploading calculation', true);
                               var input = $(e.target);
                               var form = input.parents('form')[0];

                               $(form).ajaxSubmit(
                                   {
                                    xhr: function() {  // custom xhr to add progress bar management
                                        var myXhr = $.ajaxSettings.xhr();
                                        if(myXhr.upload){ // if upload property exists
                                            myXhr.upload.addEventListener('progress', progressHandlingFunction, false);
                                        }
                                        return myXhr;
                                    },
                                    success: function(data) {
                                        calculations.add(new Calculation(data), {at: 0});
                                    },
                                    error: function(xhr) {
                                        dialog.hide();
                                        var s, out, data = $.parseJSON(xhr.responseText);
                                        var out = "";
                                        var ct = 0;
                                        for (s in data) {
                                            if (data[s] == "")
                                                continue;
                                            out += '
' + data[s] + '
';
                                            ct++;
                                        }
                                        diaerror.show(false, "Calculation not accepted: traceback", out);
                                    }});
                           });

            $(document).on('hidden.bs.modal', 'div[id^=traceback-]',
                           function(e) {
                               setTimer();
                           });

        });
})($, Backbone, _, gem_oq_server_url);
<|endoftext|>