<commit_before>// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
//                  Luke Smith http://lucassmith.name/ (2008)
//                  Loic Dachary <loic@dachary.org> (2008)
//                  Johan Euphrosine <proppy@aminche.com> (2008)
//                  Oyvind Sean Kinsey http://kinsey.no/blog (2010)
//                  Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)

/**
 * Main function giving a function stack trace with a forced or passed in Error
 *
 * @cfg {Error} e The error to create a stacktrace from (optional)
 * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
 * @return {Array} of Strings with functions, lines, files, and arguments where possible
 */
function printStackTrace(options) {
    options = options || {guess: true};
    var ex = options.e || null, guess = !!options.guess;
    var p = new printStackTrace.implementation(), result = p.run(ex);
    return (guess) ? p.guessAnonymousFunctions(result) : result;
}

printStackTrace.implementation = function() {
};

printStackTrace.implementation.prototype = {
    /**
     * @param {Error} ex The error to create a stacktrace from (optional)
     * @param {String} mode Forced mode (optional, mostly for unit tests)
     */
    run: function(ex, mode) {
        ex = ex || this.createException();
        // examine exception properties w/o debugger
        //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);}
        mode = mode || this.mode(ex);
        if (mode === 'other') {
            return this.other(arguments.callee);
        } else {
            return this[mode](ex);
        }
    },

    createException: function() {
        try {
            this.undef();
        } catch (e) {
            return e;
        }
    },

    /**
     * Mode could differ for different exception, e.g.
     * exceptions in Chrome may or may not have arguments or stack.
     *
     * @return {String} mode of operation for the exception
     */
    mode: function(e) {
        if (e['arguments'] && e.stack) {
            return 'chrome';
        } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) {
            // e.message.indexOf("Backtrace:") > -1 -> opera
            // !e.stacktrace -> opera
            if (!e.stacktrace) {
                return 'opera9'; // use e.message
            }
            // 'opera#sourceloc' in e -> opera9, opera10a
            if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
                return 'opera9'; // use e.message
            }
            // e.stacktrace && !e.stack -> opera10a
            if (!e.stack) {
                return 'opera10a'; // use e.stacktrace
            }
            // e.stacktrace && e.stack -> opera10b
            if (e.stacktrace.indexOf("called from line") < 0) {
                return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
            }
            // e.stacktrace && e.stack -> opera11
            return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
        } else if (e.stack) {
            return 'firefox';
        }
        return 'other';
    },

    /**
     * Given a context, function name, and callback function, overwrite it so that it calls
     * printStackTrace() first with a callback and then runs the rest of the body.
     *
     * @param {Object} context of execution (e.g. window)
     * @param {String} functionName to instrument
     * @param {Function} function to call with a stack trace on invocation
     */
    instrumentFunction: function(context, functionName, callback) {
        context = context || window;
        var original = context[functionName];
        context[functionName] = function instrumented() {
            callback.call(this, printStackTrace().slice(4));
            return context[functionName]._instrumented.apply(this, arguments);
        };
        context[functionName]._instrumented = original;
    },

    /**
     * Given a context and function name of a function that has been
     * instrumented, revert the function to it's original (non-instrumented)
     * state.
     *
     * @param {Object} context of execution (e.g. window)
     * @param {String} functionName to de-instrument
     */
    deinstrumentFunction: function(context, functionName) {
        if (context[functionName].constructor === Function &&
                context[functionName]._instrumented &&
                context[functionName]._instrumented.constructor === Function) {
            context[functionName] = context[functionName]._instrumented;
        }
    },

    /**
     * Given an Error object, return a formatted Array based on Chrome's stack string.
     *
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    chrome: function(e) {
        var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, '').
          replace(/^\s+(at eval )?at\s+/gm, '').
          replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2').
          replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n');
        stack.pop();
        return stack;
    },

    /**
     * Given an Error object, return a formatted Array based on Firefox's stack string.
     *
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    firefox: function(e) {
        return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n');
    },

    opera11: function(e) {
        // "Error thrown at line 42, column 12 in <anonymous function>() in file://localhost/G:/js/stacktrace.js:\n"
        // "Error thrown at line 42, column 12 in <anonymous function: createException>() in file://localhost/G:/js/stacktrace.js:\n"
        // "called from line 7, column 4 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n"
        // "called from line 15, column 3 in file://localhost/G:/js/test/functional/testcase1.html:\n"
        var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
        var lines = e.stacktrace.split('\n'), result = [];

        for (var i = 0, len = lines.length; i < len; i += 2) {
            var match = lineRE.exec(lines[i]);
            if (match) {
                var location = match[4] + ':' + match[1] + ':' + match[2];
                var fnName = match[3] || "global code";
                fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
                result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
            }
        }

        return result;
    },

    opera10b: function(e) {
        // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
        // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
        // "@file://localhost/G:/js/test/functional/testcase1.html:15"
        var lineRE = /^(.*)@(.+):(\d+)$/;
        var lines = e.stacktrace.split('\n'), result = [];

        for (var i = 0, len = lines.length; i < len; i++) {
            var match = lineRE.exec(lines[i]);
            if (match) {
                var fnName = match[1]? (match[1] + '()') : "global code";
                result.push(fnName + '@' + match[2] + ':' + match[3]);
            }
        }

        return result;
    },

    /**
     * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
     *
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    opera10a: function(e) {
        // "  Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
        // "  Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
        var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
        var lines = e.stacktrace.split('\n'), result = [];

        for (var i = 0, len = lines.length; i < len; i += 2) {
            var match = lineRE.exec(lines[i]);
            if (match) {
                var fnName = match[3] || ANON;
                result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
            }
        }

        return result;
    },

    // Opera 7.x-9.2x only!
    opera9: function(e) {
        // "  Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
        // "  Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
        var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
        var lines = e.message.split('\n'), result = [];

        for (var i = 2, len = lines.length; i < len; i += 2) {
            var match = lineRE.exec(lines[i]);
            if (match) {
                result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
            }
        }

        return result;
    },

    // Safari, IE, and others
    other: function(curr) {
        var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
        while (curr && curr['arguments'] && stack.length < maxStackSize) {
            fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
            args = Array.prototype.slice.call(curr['arguments'] || []);
            stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
            curr = curr.caller;
        }
        return stack;
    },

    /**
     * Given arguments array as a String, subsituting type names for non-string types.
     *
     * @param {Arguments} object
     * @return {Array} of Strings with stringified arguments
     */
    stringifyArguments: function(args) {
        var result = [];
        var slice = Array.prototype.slice;
        for (var i = 0; i < args.length; ++i) {
            var arg = args[i];
            if (arg === undefined) {
                result[i] = 'undefined';
            } else if (arg === null) {
                result[i] = 'null';
            } else if (arg.constructor) {
                if (arg.constructor === Array) {
                    if (arg.length < 3) {
                        result[i] = '[' + this.stringifyArguments(arg) + ']';
                    } else {
                        result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
                    }
                } else if (arg.constructor === Object) {
                    result[i] = '#object';
                } else if (arg.constructor === Function) {
                    result[i] = '#function';
                } else if (arg.constructor === String) {
                    result[i] = '"' + arg + '"';
                } else if (arg.constructor === Number) {
                    result[i] = arg;
                }
            }
        }
        return result.join(',');
    },

    sourceCache: {},

    /**
     * @return the text from a given URL
     */
    ajax: function(url) {
        var req = this.createXMLHTTPObject();
        if (req) {
            try {
                req.open('GET', url, false);
                //req.overrideMimeType('text/plain');
                //req.overrideMimeType('text/javascript');
                req.send(null);
                //return req.status == 200 ? req.responseText : '';
                return req.responseText;
            } catch (e) {
            }
        }
        return '';
    },

    /**
     * Try XHR methods in order and store XHR factory.
     *
     * @return <Function> XHR function or equivalent
     */
    createXMLHTTPObject: function() {
        var xmlhttp, XMLHttpFactories = [
            function() {
                return new XMLHttpRequest();
            }, function() {
                return new ActiveXObject('Msxml2.XMLHTTP');
            }, function() {
                return new ActiveXObject('Msxml3.XMLHTTP');
            }, function() {
                return new ActiveXObject('Microsoft.XMLHTTP');
            }
        ];
        for (var i = 0; i < XMLHttpFactories.length; i++) {
            try {
                xmlhttp = XMLHttpFactories[i]();
                // Use memoization to cache the factory
                this.createXMLHTTPObject = XMLHttpFactories[i];
                return xmlhttp;
            } catch (e) {
            }
        }
    },

    /**
     * Given a URL, check if it is in the same domain (so we can get the source
     * via Ajax).
     *
     * @param url <String> source url
     * @return False if we need a cross-domain request
     */
    isSameDomain: function(url) {
        return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs.
    },

    /**
     * Get source code from given URL if in the same domain.
     *
     * @param url <String> JS source URL
     * @return <Array> Array of source code lines
     */
    getSource: function(url) {
        // TODO reuse source from script tags?
        if (!(url in this.sourceCache)) {
            this.sourceCache[url] = this.ajax(url).split('\n');
        }
        return this.sourceCache[url];
    },

    guessAnonymousFunctions: function(stack) {
        for (var i = 0; i < stack.length; ++i) {
            var reStack = /\{anonymous\}\(.*\)@(.*)/,
                reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
                frame = stack[i], ref = reStack.exec(frame);

            if (ref) {
                var m = reRef.exec(ref[1]);
                if (m) { // If falsey, we did not get any file/line information
                    var file = m[1], lineno = m[2], charno = m[3] || 0;
                    if (file && this.isSameDomain(file) && lineno) {
                        var functionName = this.guessAnonymousFunction(file, lineno, charno);
                        stack[i] = frame.replace('{anonymous}', functionName);
                    }
                }
            }
        }
        return stack;
    },

    guessAnonymousFunction: function(url, lineNo, charNo) {
        var ret;
        try {
            ret = this.findFunctionName(this.getSource(url), lineNo);
        } catch (e) {
            ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
        }
        return ret;
    },

    findFunctionName: function(source, lineNo) {
        // FIXME findFunctionName fails for compressed source
        // (more than one function on the same line)
        // TODO use captured args
        // function {name}({args}) m[1]=name m[2]=args
        var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
        // {name} = function ({args}) TODO args capture
        // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
        var reFunctionExpression = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/;
        // {name} = eval()
        var reFunctionEvaluation = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
        // Walk backwards in the source lines until we find
        // the line which matches one of the patterns above
        var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
        for (var i = 0; i < maxLines; ++i) {
            // lineNo is 1-based, source[] is 0-based
            line = source[lineNo - i - 1];
            commentPos = line.indexOf('//');
            if (commentPos >= 0) {
                line = line.substr(0, commentPos);
            }
            // TODO check other types of comments? Commented code may lead to false positive
            if (line) {
                code = line + code;
                m = reFunctionExpression.exec(code);
                if (m && m[1]) {
                    return m[1];
                }
                m = reFunctionDeclaration.exec(code);
                if (m && m[1]) {
                    //return m[1] + "(" + (m[2] || "") + ")";
                    return m[1];
                }
                m = reFunctionEvaluation.exec(code);
                if (m && m[1]) {
                    return m[1];
                }
            }
        }
        return '(?)';
    }
};
<commit_msg>stacktrace.js version from official site of the project: http://stacktracejs.com/<commit_after>// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
//                  Luke Smith http://lucassmith.name/ (2008)
//                  Loic Dachary <loic@dachary.org> (2008)
//                  Johan Euphrosine <proppy@aminche.com> (2008)
//                  Øyvind Sean Kinsey http://kinsey.no/blog (2010)
//                  Victor Homyakov (2010)
//
// Information and discussions
// http://jspoker.pokersource.info/skin/test-printstacktrace.html
// http://eriwen.com/javascript/js-stack-trace/
// http://eriwen.com/javascript/stacktrace-update/
// http://pastie.org/253058
//
// guessFunctionNameFromLines comes from firebug
//
// Software License Agreement (BSD License)
//
// Copyright (c) 2007, Parakey Inc.
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above
//   copyright notice, this list of conditions and the
//   following disclaimer.
//
// * Redistributions in binary form must reproduce the above
//   copyright notice, this list of conditions and the
//   following disclaimer in the documentation and/or other
//   materials provided with the distribution.
//
// * Neither the name of Parakey Inc. nor the names of its
//   contributors may be used to endorse or promote products
//   derived from this software without specific prior
//   written permission of Parakey Inc.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/**
 * Main function giving a function stack trace with a forced or passed in Error
 *
 * @cfg {Error} e The error to create a stacktrace from (optional)
 * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
 * @return {Array} of Strings with functions, lines, files, and arguments where possible
 */
function printStackTrace(options) {
    var ex = (options && options.e) ? options.e : null;
    var guess = options ? !!options.guess : true;

    var p = new printStackTrace.implementation();
    var result = p.run(ex);
    return (guess) ? p.guessFunctions(result) : result;
}

printStackTrace.implementation = function() {};

printStackTrace.implementation.prototype = {
    run: function(ex) {
        ex = ex ||
            (function() {
                try {
                    var _err = __undef__ << 1;
                } catch (e) {
                    return e;
                }
            })();
        // Use either the stored mode, or resolve it
        var mode = this._mode || this.mode(ex);
        if (mode === 'other') {
            return this.other(arguments.callee);
        } else {
            return this[mode](ex);
        }
    },

    /**
     * @return {String} mode of operation for the environment in question.
     */
    mode: function(e) {
        if (e['arguments']) {
            return (this._mode = 'chrome');
        } else if (window.opera && e.stacktrace) {
            return (this._mode = 'opera10');
        } else if (e.stack) {
            return (this._mode = 'firefox');
        } else if (window.opera && !('stacktrace' in e)) { //Opera 9-
            return (this._mode = 'opera');
        }
        return (this._mode = 'other');
    },

    /**
     * Given a context, function name, and callback function, overwrite it so that it calls
     * printStackTrace() first with a callback and then runs the rest of the body.
     *
     * @param {Object} context of execution (e.g. window)
     * @param {String} functionName to instrument
     * @param {Function} function to call with a stack trace on invocation
     */
    instrumentFunction: function(context, functionName, callback) {
        context = context || window;
        context['_old' + functionName] = context[functionName];
        context[functionName] = function() {
            callback.call(this, printStackTrace());
            return context['_old' + functionName].apply(this, arguments);
        };
        context[functionName]._instrumented = true;
    },

    /**
     * Given a context and function name of a function that has been
     * instrumented, revert the function to it's original (non-instrumented)
     * state.
     *
     * @param {Object} context of execution (e.g. window)
     * @param {String} functionName to de-instrument
     */
    deinstrumentFunction: function(context, functionName) {
        if (context[functionName].constructor === Function &&
                context[functionName]._instrumented &&
                context['_old' + functionName].constructor === Function) {
            context[functionName] = context['_old' + functionName];
        }
    },

    /**
     * Given an Error object, return a formatted Array based on Chrome's stack string.
     *
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    chrome: function(e) {
        return e.stack.replace(/^[^\(]+?[\n$]/gm, '').replace(/^\s+at\s+/gm, '').replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').split('\n');
    },

    /**
     * Given an Error object, return a formatted Array based on Firefox's stack string.
     *
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    firefox: function(e) {
        return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n');
    },

    /**
     * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
     *
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    opera10: function(e) {
        var stack = e.stacktrace;
        var lines = stack.split('\n'), ANON = '{anonymous}',
            lineRE = /.*line (\d+), column (\d+) in ((<anonymous function\:?\s*(\S+))|([^\(]+)\([^\)]*\))(?: in )?(.*)\s*$/i, i, j, len;
        for (i = 2, j = 0, len = lines.length; i < len - 2; i++) {
            if (lineRE.test(lines[i])) {
                var location = RegExp.$6 + ':' + RegExp.$1 + ':' + RegExp.$2;
                var fnName = RegExp.$3;
                fnName = fnName.replace(/<anonymous function\:?\s?(\S+)?>/g, ANON);
                lines[j++] = fnName + '@' + location;
            }
        }

        lines.splice(j, lines.length - j);
        return lines;
    },

    // Opera 7.x-9.x only!
    opera: function(e) {
        var lines = e.message.split('\n'), ANON = '{anonymous}',
            lineRE = /Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i,
            i, j, len;

        for (i = 4, j = 0, len = lines.length; i < len; i += 2) {
            //TODO: RegExp.exec() would probably be cleaner here
            if (lineRE.test(lines[i])) {
                lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i + 1].replace(/^\s+/, '');
            }
        }

        lines.splice(j, lines.length - j);
        return lines;
    },

    // Safari, IE, and others
    other: function(curr) {
        var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i,
            stack = [], fn, args, maxStackSize = 10;

        while (curr && stack.length < maxStackSize) {
            fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
            args = Array.prototype.slice.call(curr['arguments']);
            stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
            curr = curr.caller;
        }
        return stack;
    },

    /**
     * Given arguments array as a String, subsituting type names for non-string types.
     *
     * @param {Arguments} object
     * @return {Array} of Strings with stringified arguments
     */
    stringifyArguments: function(args) {
        for (var i = 0; i < args.length; ++i) {
            var arg = args[i];
            if (arg === undefined) {
                args[i] = 'undefined';
            } else if (arg === null) {
                args[i] = 'null';
            } else if (arg.constructor) {
                if (arg.constructor === Array) {
                    if (arg.length < 3) {
                        args[i] = '[' + this.stringifyArguments(arg) + ']';
                    } else {
                        args[i] = '[' + this.stringifyArguments(Array.prototype.slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(Array.prototype.slice.call(arg, -1)) + ']';
                    }
                } else if (arg.constructor === Object) {
                    args[i] = '#object';
                } else if (arg.constructor === Function) {
                    args[i] = '#function';
                } else if (arg.constructor === String) {
                    args[i] = '"' + arg + '"';
                }
            }
        }
        return args.join(',');
    },

    sourceCache: {},

    /**
     * @return the text from a given URL.
     */
    ajax: function(url) {
        var req = this.createXMLHTTPObject();
        if (!req) {
            return;
        }
        req.open('GET', url, false);
        req.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
        req.send('');
        return req.responseText;
    },

    /**
     * Try XHR methods in order and store XHR factory.
     *
     * @return <Function> XHR function or equivalent
     */
    createXMLHTTPObject: function() {
        var xmlhttp, XMLHttpFactories = [
            function() {
                return new XMLHttpRequest();
            }, function() {
                return new ActiveXObject('Msxml2.XMLHTTP');
            }, function() {
                return new ActiveXObject('Msxml3.XMLHTTP');
            }, function() {
                return new ActiveXObject('Microsoft.XMLHTTP');
            }
        ];
        for (var i = 0; i < XMLHttpFactories.length; i++) {
            try {
                xmlhttp = XMLHttpFactories[i]();
                // Use memoization to cache the factory
                this.createXMLHTTPObject = XMLHttpFactories[i];
                return xmlhttp;
            } catch (e) {}
        }
    },

    /**
     * Given a URL, check if it is in the same domain (so we can get the source
     * via Ajax).
     *
     * @param url <String> source url
     * @return False if we need a cross-domain request
     */
    isSameDomain: function(url) {
        return url.indexOf(location.hostname) !== -1;
    },

    /**
     * Get source code from given URL if in the same domain.
     *
     * @param url <String> JS source URL
     * @return <Array> Array of source code lines
     */
    getSource: function(url) {
        if (!(url in this.sourceCache)) {
            this.sourceCache[url] = this.ajax(url).split('\n');
        }
        return this.sourceCache[url];
    },

    guessFunctions: function(stack) {
        for (var i = 0; i < stack.length; ++i) {
            var reStack = /\{anonymous\}\(.*\)@(\w+:\/\/([\-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/;
            var frame = stack[i], m = reStack.exec(frame);
            if (m) {
                var file = m[1], lineno = m[4]; //m[7] is character position in Chrome
                if (file && this.isSameDomain(file) && lineno) {
                    var functionName = this.guessFunctionName(file, lineno);
                    stack[i] = frame.replace('{anonymous}', functionName);
                }
            }
        }
        return stack;
    },

    guessFunctionName: function(url, lineNo) {
        try {
            return this.guessFunctionNameFromLines(lineNo, this.getSource(url));
        } catch (e) {
            return 'getSource failed with url: ' + url + ', exception: ' + e.toString();
        }
    },

    guessFunctionNameFromLines: function(lineNo, source) {
        var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
        var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
        // Walk backwards from the first line in the function until we find the line which
        // matches the pattern above, which is the function definition
        var line = "", maxLines = 10;
        for (var i = 0; i < maxLines; ++i) {
            line = source[lineNo - i] + line;
            if (line !== undefined) {
                var m = reGuessFunction.exec(line);
                if (m && m[1]) {
                    return m[1];
                } else {
                    m = reFunctionArgNames.exec(line);
                    if (m && m[1]) {
                        return m[1];
                    }
                }
            }
        }
        return '(?)';
    }
};<|endoftext|>


<commit_before>(function () {
    'use strict';

    /**
     * @param {Base} Base
     * @param {ReadFile} readFile
     * @param {$rootScope.Scope} $scope
     * @param {app.utils} utils
     * @param {ValidateService} validateService
     * @param {Waves} waves
     * @param {User} user
     * @param {app.utils.decorators} decorators
     * @return {MassSend}
     */
    const controller = function (Base, readFile, $scope, utils, validateService, waves, user, decorators) {

        const Papa = require('papaparse');

        class MassSend extends Base {

            /**
             * @return {IMassSendTx}
             */
            get tx() {
                return this.state.massSend;
            }

            /**
             * @type {number}
             */
            get validTxCount() {
                if (this.tx.transfers.length) {
                    const errors = utils.toHash(this.errors, 'recipient');
                    return this.tx.transfers.filter((data) => !errors[data.recipient]).length;
                } else {
                    return 0;
                }
            }

            constructor() {
                super();
                /**
                 * @type {ISendState}
                 */
                this.state = null;
                /**
                 * @type {number}
                 */
                this.maxTransfersCount = 100;
                /**
                 * @type {Function}
                 */
                this.onContinue = null;
                /**
                 * @type {string}
                 */
                this.recipientCsv = '';
                /**
                 * @type {Money}
                 */
                this.totalAmount = null;
                /**
                 * @type {Array<{recipient: string}>}
                 */
                this.errors = [];
                /**
                 * @type {Array<ITransferItem>}
                 */
                this.transfers = [];
                /**
                 * @type {MassSend.IView}
                 */
                this.view = utils.liteObject({
                    errors: utils.liteObject({ showAll: false })
                });
                /**
                 * @type {boolean}
                 */
                this.isValidAmounts = true;
                /**
                 * @type {form.FormController}
                 */
                this.massSend = null;
                /**
                 * @type {boolean}
                 */
                this.importError = false;
                /**
                 * @type {boolean}
                 */
                this.hasFee = false;
            }

            $postLink() {

                /**
                 * @type {Array<ITransferItem>}
                 */
                const transfers = this.tx.transfers || [];
                this.tx.attachment = this.tx.attachment || '';
                const assetId = this.state.assetId;
                transfers.forEach((item) => {
                    item.amount = this.state.moneyHash[assetId].cloneWithTokens(item.amount.toTokens());
                });
                this.tx.transfers = transfers;

                const onHasMoneyHash = () => {
                    const changeTransfers = utils.observe(this.state.massSend, 'transfers');
                    const changeAssetId = utils.observe(this.state, 'assetId');

                    this.receive(changeAssetId, this._onChangeAssetId, this);

                    this.observe(['transfers', 'errors'], this._updateTxList);
                    this.observe('totalAmount', this._validate);
                    this.observe('transfers', this._updateTextAreaContent);
                    this.observe('recipientCsv', this._onChangeCSVText);
                    this.receive(changeTransfers, this._calculateTotalAmount, this);
                    this.receive(changeTransfers, this._validate, this);
                    this.receive(changeTransfers, this._calculateFee, this);
                    this.receive(changeAssetId, this._calculateFee, this);
                    this.receive(utils.observe(this.state.massSend, 'fee'), this._currentHasFee, this);

                    this.transfers = this.tx.transfers.slice();
                    changeTransfers.dispatch();
                };

                if (this.state.moneyHash) {
                    onHasMoneyHash();
                } else {
                    utils.observe(this.state, 'moneyHash').once(() => {
                        onHasMoneyHash();
                        $scope.$digest();
                    });
                }
            }

            /**
             * @param {ImportFile#IOnChangeOptions} data
             */
            importFile(data) {
                if (data.status === 'ok') {
                    this.importError = false;
                    return readFile.read(data.file)
                        .then((content) => {
                            this.massSend.recipientCsv.$touched = true;
                            this._processTextAreaContent(content);
                            $scope.$digest();
                        });
                } else {
                    this.importError = true;
                    $scope.$digest();
                    setTimeout(() => {
                        this.importError = false;
                        $scope.$digest();
                    }, 3000);
                }
            }

            clear() {
                this.transfers = [];
                this.errors = [];
                this.importError = false;
            }

            nextStep() {
                const tx = waves.node.transactions.createTransaction(this.tx);
                const signable = ds.signature.getSignatureApi().makeSignable({
                    type: tx.type,
                    data: tx
                });

                return signable;
            }

            onTxSign(signable) {
                this.onContinue({ signable });
            }

            /**
             * @private
             */
            _onChangeAssetId() {
                const transfers = this.transfers;
                if (transfers && transfers.length) {
                    const assetId = this.state.assetId;

                    this.clear();

                    this.transfers = transfers.map((item) => {
                        return {
                            recipient: item.recipient,
                            amount: this.state.moneyHash[assetId].cloneWithTokens(item.amount.toTokens())
                        };
                    });
                }
            }

            /**
             * @private
             */
            _currentHasFee() {
                if (!this.state || !this.state.massSend || !this.state.massSend.fee) {
                    return null;
                }

                const fee = this.state.massSend.fee;
                const moneyHash = this.state.moneyHash;
                this.hasFee = moneyHash[fee.asset.id] && moneyHash[fee.asset.id].gte(fee);
            }

            /**
             * @private
             */
            _updateTxList() {
                const list = this.transfers.slice();
                const errors = utils.toHash(this.errors, 'recipient');
                const validList = list.filter((item) => !errors[item.recipient]);
                const endIndex = Math.min(validList.length, this.maxTransfersCount);
                const transfers = validList.slice(0, endIndex);

                if (MassSend._isNotEqual(this.tx.transfers, transfers)) {
                    this.tx.transfers = transfers;
                }
                this._currentHasFee();
            }

            /**
             * @private
             */
            _calculateTotalAmount() {
                const transfers = this.tx.transfers;

                if (transfers.length) {
                    this.totalAmount = transfers
                        .map(({ amount }) => amount.getTokens().gte(0) ? amount : amount.cloneWithTokens('0'))
                        .reduce((result, item) => result.add(item));

                    return null;
                }

                this.totalAmount = this.state.moneyHash[this.state.assetId].cloneWithTokens('0');
            }

            /**
             * @private
             */
            @decorators.async()
            _calculateFee() {
                waves.node.getFee(this.tx).then((fee) => {
                    this.tx.fee = fee;
                    $scope.$digest();
                });
            }

            /**
             * @param {string} content
             * @private
             */
            _processTextAreaContent(content) {
                const { data } = Papa.parse(content || '');
                const recipientHash = MassSend._getRecipientHashByCSVParseResult(data);
                const transfers = [];

                Object.keys(recipientHash).forEach((recipient) => {
                    const amountNum = recipientHash[recipient]
                        .map((amount) => {
                            try {
                                return MassSend._parseAmount(amount);
                            } catch (e) {
                                return new BigNumber(0);
                            }
                        })
                        .reduce((result, item) => result.plus(item));
                    const amount = this.state.moneyHash[this.state.assetId].cloneWithTokens(amountNum);
                    transfers.push({ recipient, amount });
                });

                if (MassSend._isNotEqual(this.transfers, transfers)) {
                    this.transfers = transfers;
                }
            }

            /**
             * @private
             */
            _validate() {
                this._validateAmounts();
                this._validateRecipients();
            }

            /**
             * @private
             */
            @decorators.async()
            _validateRecipients() {

                Promise.all(this.transfers.map((item) => MassSend._isValidRecipient(item.recipient)))
                    .then((list) => {
                        const errors = [];

                        list.forEach((response, index) => {
                            const recipient = this.transfers[index].recipient;

                            if (!response.state || this.transfers[index].amount.getTokens().lte(0)) {
                                errors.push({ recipient });
                            }
                        });

                        this.errors = errors;
                        $scope.$digest();
                    });
            }

            /**
             * @private
             */
            _validateAmounts() {
                if (!this.totalAmount) {
                    this.isValidAmounts = true;
                    return null;
                }

                const moneyHash = utils.groupMoney([this.totalAmount, this.tx.fee]);
                const balance = moneyHash[this.state.assetId];
                this.isValidAmounts = this.state.moneyHash[this.state.assetId].gte(balance) &&
                    this.totalAmount.getTokens().gt('0');
            }

            /**
             * @private
             */
            _onChangeCSVText() {
                const text = this.recipientCsv;
                this._processTextAreaContent(text);
                this._updateTextAreaContent();
                this._validateRecipients();
            }

            /**
             * @private
             */
            @decorators.async()
            _updateTextAreaContent() {
                const transfers = this.transfers;
                const text = Papa.unparse(transfers.map((item) => [item.recipient, item.amount.getTokens().toFixed()]));
                if (text !== this.recipientCsv) {
                    this.recipientCsv = text;
                    $scope.$digest();
                }
            }

            /**
             * @param {Array<Array<string>>} data
             * @return {Object.<string, Array<string>>}
             * @private
             */
            static _getRecipientHashByCSVParseResult(data) {
                const recipientHash = Object.create(null);
                data.forEach((item) => {
                    if (!item.length) {
                        return null;
                    }

                    const [recipient, amountString] = item.map((text) => text.replace(/\s/g, '').replace(/"/g, ''));
                    if (!(recipient && amountString)) {
                        return null;
                    }

                    if (!recipientHash[recipient]) {
                        recipientHash[recipient] = [];
                    }

                    recipientHash[recipient].push(amountString);
                });
                return recipientHash;
            }

            /**
             * @param {ITransferItem[]} a
             * @param {ITransferItem[]} b
             * @return {boolean}
             * @private
             */
            static _isNotEqual(a, b) {
                return !MassSend._isEqual(a, b);
            }

            /**
             * @param {ITransferItem[]} a
             * @param {ITransferItem[]} b
             * @return {boolean}
             * @private
             */
            static _isEqual(a, b) {
                return a.length === b.length && a.every((item, i) => {
                    return item.recipient === b[i].recipient && item.amount.eq(b[i].amount);
                });
            }

            /**
             * @param {string} recipient
             * @return Promise<boolean>
             * @private
             */
            @decorators.cachable(60)
            static _isValidRecipient(recipient) {
                return utils.resolve(validateService.wavesAddress(recipient));
            }

            /**
             * @param {string} amountString
             * @return {BigNumber}
             * @private
             */
            static _parseAmount(amountString) {
                const data = WavesApp.getLocaleData();
                const amount = amountString
                    .replace(new RegExp(`\\${data.separators.group}`, 'g'), '')
                    .replace(new RegExp(`\\${data.separators.decimal}`), '.')
                    .replace(',', '.');
                return new BigNumber(amount);
            }

        }

        return new MassSend();
    };

    controller.$inject = ['Base', 'readFile', '$scope', 'utils', 'validateService', 'waves', 'user', 'decorators'];

    angular.module('app.ui').component('wMassSend', {
        bindings: {
            state: '<',
            onContinue: '&'
        },
        templateUrl: 'modules/utils/modals/sendAsset/components/massSend/mass-send.html',
        transclude: true,
        controller
    });
})();

/**
 * @name MassSend
 *
 * @typedef {object} MassSend#IView
 * @property {object} errors
 * @property {boolean} errors.showAll
 */
<commit_msg>CLIENT-1669: parse only float numbers<commit_after>(function () {
    'use strict';

    /**
     * @param {Base} Base
     * @param {ReadFile} readFile
     * @param {$rootScope.Scope} $scope
     * @param {app.utils} utils
     * @param {ValidateService} validateService
     * @param {Waves} waves
     * @param {User} user
     * @param {app.utils.decorators} decorators
     * @return {MassSend}
     */
    const controller = function (Base, readFile, $scope, utils, validateService, waves, user, decorators) {

        const Papa = require('papaparse');

        class MassSend extends Base {

            /**
             * @return {IMassSendTx}
             */
            get tx() {
                return this.state.massSend;
            }

            /**
             * @type {number}
             */
            get validTxCount() {
                if (this.tx.transfers.length) {
                    const errors = utils.toHash(this.errors, 'recipient');
                    return this.tx.transfers.filter((data) => !errors[data.recipient]).length;
                } else {
                    return 0;
                }
            }

            constructor() {
                super();
                /**
                 * @type {ISendState}
                 */
                this.state = null;
                /**
                 * @type {number}
                 */
                this.maxTransfersCount = 100;
                /**
                 * @type {Function}
                 */
                this.onContinue = null;
                /**
                 * @type {string}
                 */
                this.recipientCsv = '';
                /**
                 * @type {Money}
                 */
                this.totalAmount = null;
                /**
                 * @type {Array<{recipient: string}>}
                 */
                this.errors = [];
                /**
                 * @type {Array<ITransferItem>}
                 */
                this.transfers = [];
                /**
                 * @type {MassSend.IView}
                 */
                this.view = utils.liteObject({
                    errors: utils.liteObject({ showAll: false })
                });
                /**
                 * @type {boolean}
                 */
                this.isValidAmounts = true;
                /**
                 * @type {form.FormController}
                 */
                this.massSend = null;
                /**
                 * @type {boolean}
                 */
                this.importError = false;
                /**
                 * @type {boolean}
                 */
                this.hasFee = false;
            }

            $postLink() {

                /**
                 * @type {Array<ITransferItem>}
                 */
                const transfers = this.tx.transfers || [];
                this.tx.attachment = this.tx.attachment || '';
                const assetId = this.state.assetId;
                transfers.forEach((item) => {
                    item.amount = this.state.moneyHash[assetId].cloneWithTokens(item.amount.toTokens());
                });
                this.tx.transfers = transfers;

                const onHasMoneyHash = () => {
                    const changeTransfers = utils.observe(this.state.massSend, 'transfers');
                    const changeAssetId = utils.observe(this.state, 'assetId');

                    this.receive(changeAssetId, this._onChangeAssetId, this);

                    this.observe(['transfers', 'errors'], this._updateTxList);
                    this.observe('totalAmount', this._validate);
                    this.observe('transfers', this._updateTextAreaContent);
                    this.observe('recipientCsv', this._onChangeCSVText);
                    this.receive(changeTransfers, this._calculateTotalAmount, this);
                    this.receive(changeTransfers, this._validate, this);
                    this.receive(changeTransfers, this._calculateFee, this);
                    this.receive(changeAssetId, this._calculateFee, this);
                    this.receive(utils.observe(this.state.massSend, 'fee'), this._currentHasFee, this);

                    this.transfers = this.tx.transfers.slice();
                    changeTransfers.dispatch();
                };

                if (this.state.moneyHash) {
                    onHasMoneyHash();
                } else {
                    utils.observe(this.state, 'moneyHash').once(() => {
                        onHasMoneyHash();
                        $scope.$digest();
                    });
                }
            }

            /**
             * @param {ImportFile#IOnChangeOptions} data
             */
            importFile(data) {
                if (data.status === 'ok') {
                    this.importError = false;
                    return readFile.read(data.file)
                        .then((content) => {
                            this.massSend.recipientCsv.$touched = true;
                            this._processTextAreaContent(content);
                            $scope.$digest();
                        });
                } else {
                    this.importError = true;
                    $scope.$digest();
                    setTimeout(() => {
                        this.importError = false;
                        $scope.$digest();
                    }, 3000);
                }
            }

            clear() {
                this.transfers = [];
                this.errors = [];
                this.importError = false;
            }

            nextStep() {
                const tx = waves.node.transactions.createTransaction(this.tx);
                const signable = ds.signature.getSignatureApi().makeSignable({
                    type: tx.type,
                    data: tx
                });

                return signable;
            }

            onTxSign(signable) {
                this.onContinue({ signable });
            }

            /**
             * @private
             */
            _onChangeAssetId() {
                const transfers = this.transfers;
                if (transfers && transfers.length) {
                    const assetId = this.state.assetId;

                    this.clear();

                    this.transfers = transfers.map((item) => {
                        return {
                            recipient: item.recipient,
                            amount: this.state.moneyHash[assetId].cloneWithTokens(item.amount.toTokens())
                        };
                    });
                }
            }

            /**
             * @private
             */
            _currentHasFee() {
                if (!this.state || !this.state.massSend || !this.state.massSend.fee) {
                    return null;
                }

                const fee = this.state.massSend.fee;
                const moneyHash = this.state.moneyHash;
                this.hasFee = moneyHash[fee.asset.id] && moneyHash[fee.asset.id].gte(fee);
            }

            /**
             * @private
             */
            _updateTxList() {
                const list = this.transfers.slice();
                const errors = utils.toHash(this.errors, 'recipient');
                const validList = list.filter((item) => !errors[item.recipient]);
                const endIndex = Math.min(validList.length, this.maxTransfersCount);
                const transfers = validList.slice(0, endIndex);

                if (MassSend._isNotEqual(this.tx.transfers, transfers)) {
                    this.tx.transfers = transfers;
                }
                this._currentHasFee();
            }

            /**
             * @private
             */
            _calculateTotalAmount() {
                const transfers = this.tx.transfers;

                if (transfers.length) {
                    this.totalAmount = transfers
                        .map(({ amount }) => amount.getTokens().gte(0) ? amount : amount.cloneWithTokens('0'))
                        .reduce((result, item) => result.add(item));

                    return null;
                }

                this.totalAmount = this.state.moneyHash[this.state.assetId].cloneWithTokens('0');
            }

            /**
             * @private
             */
            @decorators.async()
            _calculateFee() {
                waves.node.getFee(this.tx).then((fee) => {
                    this.tx.fee = fee;
                    $scope.$digest();
                });
            }

            /**
             * @param {string} content
             * @private
             */
            _processTextAreaContent(content) {
                const { data } = Papa.parse(content || '');
                const recipientHash = MassSend._getRecipientHashByCSVParseResult(data);
                const transfers = [];

                Object.keys(recipientHash).forEach((recipient) => {
                    const amountNum = recipientHash[recipient]
                        .map((amount) => {
                            try {
                                return MassSend._parseAmount(amount);
                            } catch (e) {
                                return new BigNumber(0);
                            }
                        })
                        .reduce((result, item) => result.plus(item));
                    const amount = this.state.moneyHash[this.state.assetId].cloneWithTokens(amountNum);
                    transfers.push({ recipient, amount });
                });

                if (MassSend._isNotEqual(this.transfers, transfers)) {
                    this.transfers = transfers;
                }
            }

            /**
             * @private
             */
            _validate() {
                this._validateAmounts();
                this._validateRecipients();
            }

            /**
             * @private
             */
            @decorators.async()
            _validateRecipients() {

                Promise.all(this.transfers.map((item) => MassSend._isValidRecipient(item.recipient)))
                    .then((list) => {
                        const errors = [];

                        list.forEach((response, index) => {
                            const recipient = this.transfers[index].recipient;

                            if (!response.state || this.transfers[index].amount.getTokens().lte(0)) {
                                errors.push({ recipient });
                            }
                        });

                        this.errors = errors;
                        $scope.$digest();
                    });
            }

            /**
             * @private
             */
            _validateAmounts() {
                if (!this.totalAmount) {
                    this.isValidAmounts = true;
                    return null;
                }

                const moneyHash = utils.groupMoney([this.totalAmount, this.tx.fee]);
                const balance = moneyHash[this.state.assetId];
                this.isValidAmounts = this.state.moneyHash[this.state.assetId].gte(balance) &&
                    this.totalAmount.getTokens().gt('0');
            }

            /**
             * @private
             */
            _onChangeCSVText() {
                const text = this.recipientCsv;
                this._processTextAreaContent(text);
                this._updateTextAreaContent();
                this._validateRecipients();
            }

            /**
             * @private
             */
            @decorators.async()
            _updateTextAreaContent() {
                const transfers = this.transfers;
                const text = Papa.unparse(transfers.map((item) => [item.recipient, item.amount.getTokens().toFixed()]));
                if (text !== this.recipientCsv) {
                    this.recipientCsv = text;
                    $scope.$digest();
                }
            }

            /**
             * @param {Array<Array<string>>} data
             * @return {Object.<string, Array<string>>}
             * @private
             */
            static _getRecipientHashByCSVParseResult(data) {
                const recipientHash = Object.create(null);
                data.forEach((item) => {
                    if (!item.length) {
                        return null;
                    }

                    const [recipient, amountString] = item.map((text) => text.replace(/\s/g, '').replace(/"/g, ''));
                    if (!(recipient && amountString)) {
                        return null;
                    }

                    if (!recipientHash[recipient]) {
                        recipientHash[recipient] = [];
                    }

                    recipientHash[recipient].push(amountString);
                });
                return recipientHash;
            }

            /**
             * @param {ITransferItem[]} a
             * @param {ITransferItem[]} b
             * @return {boolean}
             * @private
             */
            static _isNotEqual(a, b) {
                return !MassSend._isEqual(a, b);
            }

            /**
             * @param {ITransferItem[]} a
             * @param {ITransferItem[]} b
             * @return {boolean}
             * @private
             */
            static _isEqual(a, b) {
                return a.length === b.length && a.every((item, i) => {
                    return item.recipient === b[i].recipient && item.amount.eq(b[i].amount);
                });
            }

            /**
             * @param {string} recipient
             * @return Promise<boolean>
             * @private
             */
            @decorators.cachable(60)
            static _isValidRecipient(recipient) {
                return utils.resolve(validateService.wavesAddress(recipient));
            }

            /**
             * @param {string} amountString
             * @return {BigNumber}
             * @private
             */
            static _parseAmount(amountString) {
                const data = WavesApp.getLocaleData();
                const validate = /^([0-9]{0,1}\.){0,1}[0-9]+$/;
                const amount = amountString
                    .replace(new RegExp(`\\${data.separators.group}`, 'g'), '')
                    .replace(new RegExp(`\\${data.separators.decimal}`), '.')
                    .replace(',', '.');

                if (!validate.test(amount)) {
                    return new BigNumber(0);
                }

                return new BigNumber(amount);
            }

        }

        return new MassSend();
    };

    controller.$inject = ['Base', 'readFile', '$scope', 'utils', 'validateService', 'waves', 'user', 'decorators'];

    angular.module('app.ui').component('wMassSend', {
        bindings: {
            state: '<',
            onContinue: '&'
        },
        templateUrl: 'modules/utils/modals/sendAsset/components/massSend/mass-send.html',
        transclude: true,
        controller
    });
})();

/**
 * @name MassSend
 *
 * @typedef {object} MassSend#IView
 * @property {object} errors
 * @property {boolean} errors.showAll
 */
<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     319
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v320 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     320
// ==/UserScript==
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

(function() {
	if (!OC.Share) {
		OC.Share = {};
	}

	var TEMPLATE =
			'
' +
			'{{#each sharees}}' +
				'{{#if isCollection}}' +
				'
{{text}}
' +
				'{{/if}}' +
				'{{#unless isCollection}}' +
				'
' +
					'' +
					'{{#if avatarEnabled}}' +
					'

' +
					'{{/if}}' +
					'{{shareWithDisplayName}}' +
					'{{#if mailNotificationEnabled}} {{#unless isRemoteShare}}' +
					'' +
						'<input id="mail-{{cid}}-{{shareWith}}" type="checkbox" name="mailNotification" class="mailNotification checkbox" {{#if wasMailSent}}checked="checked"{{/if}} />' +
						'{{notifyByMailLabel}}' +
					'' +
					'{{/unless}} {{/if}}' +
					'{{#if isResharingAllowed}} {{#if sharePermissionPossible}} {{#unless isRemoteShare}}' +
					'' +
						'<input id="canShare-{{cid}}-{{shareWith}}" type="checkbox" name="share" class="permissions checkbox" {{#if hasSharePermission}}checked="checked"{{/if}} data-permissions="{{sharePermission}}" />' +
						'{{canShareLabel}}' +
					'' +
					'{{/unless}} {{/if}} {{/if}}' +
					'{{#if editPermissionPossible}}' +
					'' +
						'<input id="canEdit-{{cid}}-{{shareWith}}" type="checkbox" name="edit" class="permissions checkbox" {{#if hasEditPermission}}checked="checked"{{/if}} />' +
						'{{canEditLabel}}' +
						'{{#unless isRemoteShare}}' +
						'' +
						'{{/unless}}' +
					'' +
					'{{/if}}' +
					'{{#unless isRemoteShare}}' +
					'
' +
						'{{#if createPermissionPossible}}' +
						'' +
							'<input id="canCreate-{{cid}}-{{shareWith}}" type="checkbox" name="create" class="permissions checkbox" {{#if hasCreatePermission}}checked="checked"{{/if}} data-permissions="{{createPermission}}"/>' +
							'{{createPermissionLabel}}' +
						'' +
						'{{/if}}' +
						'{{#if updatePermissionPossible}}' +
						'' +
							'<input id="canUpdate-{{cid}}-{{shareWith}}" type="checkbox" name="update" class="permissions checkbox" {{#if hasUpdatePermission}}checked="checked"{{/if}} data-permissions="{{updatePermission}}"/>' +
							'{{updatePermissionLabel}}' +
						'' +
						'{{/if}}' +
						'{{#if deletePermissionPossible}} {{#unless isRemoteShare}}' +
						'' +
							'<input id="canDelete-{{cid}}-{{shareWith}}" type="checkbox" name="delete" class="permissions checkbox" {{#if hasDeletePermission}}checked="checked"{{/if}} data-permissions="{{deletePermission}}"/>' +
							'{{deletePermissionLabel}}' +
						'' +
						'{{/unless}} {{/if}}' +
					'
' +
					'{{/unless}}' +
				'
' +
			 	'{{/unless}}' +
			'{{/each}}' +
		'
'
		;

	/**
	 * @class OCA.Share.ShareDialogShareeListView
	 * @member {OC.Share.ShareItemModel} model
	 * @member {jQuery} $el
	 * @memberof OCA.Sharing
	 * @classdesc
	 *
	 * Represents the sharee list part in the GUI of the share dialogue
	 *
	 */
	var ShareDialogShareeListView = OC.Backbone.View.extend({
		/** @type {string} **/
		id: 'shareDialogLinkShare',

		/** @type {OC.Share.ShareConfigModel} **/
		configModel: undefined,

		/** @type {Function} **/
		_template: undefined,

		/** @type {boolean} **/
		showLink: true,

		/** @type {object} **/
		_collections: {},

		events: {
			'click .unshare': 'onUnshare',
			'click .permissions': 'onPermissionChange',
			'click .showCruds': 'onCrudsToggle',
			'click .mailNotification': 'onSendMailNotification'
		},

		initialize: function(options) {
			if(!_.isUndefined(options.configModel)) {
				this.configModel = options.configModel;
			} else {
				throw 'missing OC.Share.ShareConfigModel';
			}

			var view = this;
			this.model.on('change:shares', function() {
				view.render();
			});
		},

		processCollectionShare: function(shareIndex) {
			var type = this.model.getCollectionType(shareIndex);
			var id = this.model.getCollectionPath(shareIndex);
			if(type !== 'file' && type !== 'folder') {
				id = this.model.getCollectionSource(shareIndex);
			}
			var displayName = this.model.getShareWithDisplayName(shareIndex);
			if(!_.isUndefined(this._collections[id])) {
				if (this.model.getShareType(shareIndex) === OC.Share.SHARE_TYPE_LINK) {
					this._collections[id].text = this._collections[id].text + ", " + t('core', 'by link');
				} else {
					this._collections[id].text = this._collections[id].text + ", " + displayName;
				}
			} else {
				this._collections[id] = {};
				if (this.model.getShareType(shareIndex) === OC.Share.SHARE_TYPE_LINK) {
					this._collections[id].text = t('core', 'Shared in {item} by link', {
						'item': id
					});
				} else {
					this._collections[id].text = t('core', 'Shared in {item} with {user}', {
						'item': id,
						user: displayName
					});
				}
				this._collections[id].id = id;
				this._collections[id].isCollection = true;
			}
		},

		/**
		 *
		 * @param {OC.Share.Types.ShareInfo} shareInfo
		 * @returns {object}
		 */
		getShareeObject: function(shareIndex) {
			var shareWith = this.model.getShareWith(shareIndex);
			var shareWithDisplayName = this.model.getShareWithDisplayName(shareIndex);
			var shareType = this.model.getShareType(shareIndex);

			var hasPermissionOverride = {};
			if (shareType === OC.Share.SHARE_TYPE_GROUP) {
				shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')';
			} else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
				shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'remote') + ')';
				hasPermissionOverride = {
					createPermissionPossible: true,
					updatePermissionPossible: true
				};
			}

			return _.extend(hasPermissionOverride, {
				cid: this.cid,
				hasSharePermission: this.model.hasSharePermission(shareIndex),
				hasEditPermission: this.model.hasEditPermission(shareIndex),
				hasCreatePermission: this.model.hasCreatePermission(shareIndex),
				hasUpdatePermission: this.model.hasUpdatePermission(shareIndex),
				hasDeletePermission: this.model.hasDeletePermission(shareIndex),
				wasMailSent: this.model.notificationMailWasSent(shareIndex),
				shareWith: shareWith,
				shareWithDisplayName: shareWithDisplayName,
				shareType: shareType,
				modSeed: shareType !== OC.Share.SHARE_TYPE_USER,
				isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE
			});
		},

		getShareeList: function() {
			var universal = {
				avatarEnabled: this.configModel.areAvatarsEnabled(),
				mailNotificationEnabled: this.configModel.isMailNotificationEnabled(),
				notifyByMailLabel: t('core', 'notify by email'),
				unshareLabel: t('core', 'Unshare'),
				unshareImage: OC.imagePath('core', 'actions/delete'),
				canShareLabel: t('core', 'can share'),
				canEditLabel: t('core', 'can edit'),
				createPermissionLabel: t('core', 'create'),
				updatePermissionLabel: t('core', 'change'),
				deletePermissionLabel: t('core', 'delete'),
				crudsLabel: t('core', 'access control'),
				triangleSImage: OC.imagePath('core', 'actions/triangle-s'),
				isResharingAllowed: this.configModel.get('isResharingAllowed'),
				sharePermissionPossible: this.model.sharePermissionPossible(),
				editPermissionPossible: this.model.editPermissionPossible(),
				createPermissionPossible: this.model.createPermissionPossible(),
				updatePermissionPossible: this.model.updatePermissionPossible(),
				deletePermissionPossible: this.model.deletePermissionPossible(),
				sharePermission: OC.PERMISSION_SHARE,
				createPermission: OC.PERMISSION_CREATE,
				updatePermission: OC.PERMISSION_UPDATE,
				deletePermission: OC.PERMISSION_DELETE
			};

			this._collections = {};

			if(!this.model.hasUserShares()) {
				return [];
			}

			var shares = this.model.get('shares');
			var list = [];
			for(var index = 0; index < shares.length; index++) {
				if(this.model.isCollection(index)) {
					this.processCollectionShare(index);
				} else {
					// first empty {} is necessary, otherwise we get in trouble
					// with references
					list.push(_.extend({}, universal, this.getShareeObject(index)));
				}
			}
			list = _.union(_.values(this._collections), list);

			return list;
		},

		render: function() {
			this.$el.html(this.template({
				cid: this.cid,
				sharees: this.getShareeList()
			}));

			if(this.configModel.areAvatarsEnabled()) {
				this.$el.find('.avatar').each(function() {
					var $this = $(this);
					if ($this.hasClass('imageplaceholderseed')) {
						$this.css({width: 32, height: 32});
						$this.imageplaceholder($this.data('seed'));
					} else {
						$this.avatar($this.data('username'), 32);
					}
				});
			}

			this.delegateEvents();

			return this;
		},

		/**
		 * @returns {Function} from Handlebars
		 * @private
		 */
		template: function (data) {
			if (!this._template) {
				this._template = Handlebars.compile(TEMPLATE);
			}
			return this._template(data);
		},

		onUnshare: function(event) {
			var $element = $(event.target);
			if (!$element.is('a')) {
				$element = $element.closest('a');
			}

			var $loading = $element.find('.icon-loading-small').eq(0);
			if(!$loading.hasClass('hidden')) {
				// in process
				return false;
			}
			$loading.removeClass('hidden');

			var $li = $element.closest('li');
			var shareType = $li.data('share-type');
			var shareWith = $li.attr('data-share-with');

			this.model.removeShare(shareType, shareWith);

			return false;
		},

		onPermissionChange: function(event) {
			var $element = $(event.target);
			var $li = $element.closest('li');
			var shareType = $li.data('share-type');
			var shareWith = $li.attr('data-share-with');

			// adjust checkbox states
			var $checkboxes = $('.permissions', $li).not('input[name="edit"]').not('input[name="share"]');
			var checked;
			if ($element.attr('name') === 'edit') {
				checked = $element.is(':checked');
				// Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck
				$($checkboxes).attr('checked', checked);
			} else {
				var numberChecked = $checkboxes.filter(':checked').length;
				checked = numberChecked > 0;
				$('input[name="edit"]', $li).attr('checked', checked);
			}

			var permissions = OC.PERMISSION_READ;
			$('.permissions', $li).not('input[name="edit"]').filter(':checked').each(function(index, checkbox) {
				permissions |= $(checkbox).data('permissions');
			});

			this.model.setPermissions(shareType, shareWith, permissions);
		},

		onCrudsToggle: function(event) {
			var $target = $(event.target);
			$target.closest('li').find('.cruds').toggleClass('hidden');
			return false;
		},

		onSendMailNotification: function(event) {
			var $target = $(event.target);
			var $li = $(event.target).closest('li');
			var shareType = $li.data('share-type');
			var shareWith = $li.attr('data-share-with');

			this.model.sendNotificationForShare(shareType, shareWith, $target.is(':checked'));
		}
	});

	OC.Share.ShareDialogShareeListView = ShareDialogShareeListView;

})();
<commit_msg>Allow setting can edit permissions on federated shares in webUI<commit_after>/*
 * Copyright (c) 2015
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

(function() {
	if (!OC.Share) {
		OC.Share = {};
	}

	var TEMPLATE =
			'
' +
			'{{#each sharees}}' +
				'{{#if isCollection}}' +
				'
{{text}}
' +
				'{{/if}}' +
				'{{#unless isCollection}}' +
				'
' +
					'' +
					'{{#if avatarEnabled}}' +
					'

' +
					'{{/if}}' +
					'{{shareWithDisplayName}}' +
					'{{#if mailNotificationEnabled}} {{#unless isRemoteShare}}' +
					'' +
						'<input id="mail-{{cid}}-{{shareWith}}" type="checkbox" name="mailNotification" class="mailNotification checkbox" {{#if wasMailSent}}checked="checked"{{/if}} />' +
						'{{notifyByMailLabel}}' +
					'' +
					'{{/unless}} {{/if}}' +
					'{{#if isResharingAllowed}} {{#if sharePermissionPossible}} {{#unless isRemoteShare}}' +
					'' +
						'<input id="canShare-{{cid}}-{{shareWith}}" type="checkbox" name="share" class="permissions checkbox" {{#if hasSharePermission}}checked="checked"{{/if}} data-permissions="{{sharePermission}}" />' +
						'{{canShareLabel}}' +
					'' +
					'{{/unless}} {{/if}} {{/if}}' +
					'{{#if editPermissionPossible}}' +
					'' +
						'<input id="canEdit-{{cid}}-{{shareWith}}" type="checkbox" name="edit" class="permissions checkbox" {{#if hasEditPermission}}checked="checked"{{/if}} />' +
						'{{canEditLabel}}' +
						'{{#unless isRemoteShare}}' +
						'' +
						'{{/unless}}' +
					'' +
					'{{/if}}' +
					'{{#unless isRemoteShare}}' +
					'
' +
						'{{#if createPermissionPossible}}' +
						'' +
							'<input id="canCreate-{{cid}}-{{shareWith}}" type="checkbox" name="create" class="permissions checkbox" {{#if hasCreatePermission}}checked="checked"{{/if}} data-permissions="{{createPermission}}"/>' +
							'{{createPermissionLabel}}' +
						'' +
						'{{/if}}' +
						'{{#if updatePermissionPossible}}' +
						'' +
							'<input id="canUpdate-{{cid}}-{{shareWith}}" type="checkbox" name="update" class="permissions checkbox" {{#if hasUpdatePermission}}checked="checked"{{/if}} data-permissions="{{updatePermission}}"/>' +
							'{{updatePermissionLabel}}' +
						'' +
						'{{/if}}' +
						'{{#if deletePermissionPossible}} {{#unless isRemoteShare}}' +
						'' +
							'<input id="canDelete-{{cid}}-{{shareWith}}" type="checkbox" name="delete" class="permissions checkbox" {{#if hasDeletePermission}}checked="checked"{{/if}} data-permissions="{{deletePermission}}"/>' +
							'{{deletePermissionLabel}}' +
						'' +
						'{{/unless}} {{/if}}' +
					'
' +
					'{{/unless}}' +
				'
' +
			 	'{{/unless}}' +
			'{{/each}}' +
		'
'
		;

	/**
	 * @class OCA.Share.ShareDialogShareeListView
	 * @member {OC.Share.ShareItemModel} model
	 * @member {jQuery} $el
	 * @memberof OCA.Sharing
	 * @classdesc
	 *
	 * Represents the sharee list part in the GUI of the share dialogue
	 *
	 */
	var ShareDialogShareeListView = OC.Backbone.View.extend({
		/** @type {string} **/
		id: 'shareDialogLinkShare',

		/** @type {OC.Share.ShareConfigModel} **/
		configModel: undefined,

		/** @type {Function} **/
		_template: undefined,

		/** @type {boolean} **/
		showLink: true,

		/** @type {object} **/
		_collections: {},

		events: {
			'click .unshare': 'onUnshare',
			'click .permissions': 'onPermissionChange',
			'click .showCruds': 'onCrudsToggle',
			'click .mailNotification': 'onSendMailNotification'
		},

		initialize: function(options) {
			if(!_.isUndefined(options.configModel)) {
				this.configModel = options.configModel;
			} else {
				throw 'missing OC.Share.ShareConfigModel';
			}

			var view = this;
			this.model.on('change:shares', function() {
				view.render();
			});
		},

		processCollectionShare: function(shareIndex) {
			var type = this.model.getCollectionType(shareIndex);
			var id = this.model.getCollectionPath(shareIndex);
			if(type !== 'file' && type !== 'folder') {
				id = this.model.getCollectionSource(shareIndex);
			}
			var displayName = this.model.getShareWithDisplayName(shareIndex);
			if(!_.isUndefined(this._collections[id])) {
				if (this.model.getShareType(shareIndex) === OC.Share.SHARE_TYPE_LINK) {
					this._collections[id].text = this._collections[id].text + ", " + t('core', 'by link');
				} else {
					this._collections[id].text = this._collections[id].text + ", " + displayName;
				}
			} else {
				this._collections[id] = {};
				if (this.model.getShareType(shareIndex) === OC.Share.SHARE_TYPE_LINK) {
					this._collections[id].text = t('core', 'Shared in {item} by link', {
						'item': id
					});
				} else {
					this._collections[id].text = t('core', 'Shared in {item} with {user}', {
						'item': id,
						user: displayName
					});
				}
				this._collections[id].id = id;
				this._collections[id].isCollection = true;
			}
		},

		/**
		 *
		 * @param {OC.Share.Types.ShareInfo} shareInfo
		 * @returns {object}
		 */
		getShareeObject: function(shareIndex) {
			var shareWith = this.model.getShareWith(shareIndex);
			var shareWithDisplayName = this.model.getShareWithDisplayName(shareIndex);
			var shareType = this.model.getShareType(shareIndex);

			var hasPermissionOverride = {};
			if (shareType === OC.Share.SHARE_TYPE_GROUP) {
				shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')';
			} else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
				shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'remote') + ')';
				hasPermissionOverride = {
					createPermissionPossible: true,
					updatePermissionPossible: true
				};
			}

			return _.extend(hasPermissionOverride, {
				cid: this.cid,
				hasSharePermission: this.model.hasSharePermission(shareIndex),
				hasEditPermission: this.model.hasEditPermission(shareIndex),
				hasCreatePermission: this.model.hasCreatePermission(shareIndex),
				hasUpdatePermission: this.model.hasUpdatePermission(shareIndex),
				hasDeletePermission: this.model.hasDeletePermission(shareIndex),
				wasMailSent: this.model.notificationMailWasSent(shareIndex),
				shareWith: shareWith,
				shareWithDisplayName: shareWithDisplayName,
				shareType: shareType,
				modSeed: shareType !== OC.Share.SHARE_TYPE_USER,
				isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE
			});
		},

		getShareeList: function() {
			var universal = {
				avatarEnabled: this.configModel.areAvatarsEnabled(),
				mailNotificationEnabled: this.configModel.isMailNotificationEnabled(),
				notifyByMailLabel: t('core', 'notify by email'),
				unshareLabel: t('core', 'Unshare'),
				unshareImage: OC.imagePath('core', 'actions/delete'),
				canShareLabel: t('core', 'can share'),
				canEditLabel: t('core', 'can edit'),
				createPermissionLabel: t('core', 'create'),
				updatePermissionLabel: t('core', 'change'),
				deletePermissionLabel: t('core', 'delete'),
				crudsLabel: t('core', 'access control'),
				triangleSImage: OC.imagePath('core', 'actions/triangle-s'),
				isResharingAllowed: this.configModel.get('isResharingAllowed'),
				sharePermissionPossible: this.model.sharePermissionPossible(),
				editPermissionPossible: this.model.editPermissionPossible(),
				createPermissionPossible: this.model.createPermissionPossible(),
				updatePermissionPossible: this.model.updatePermissionPossible(),
				deletePermissionPossible: this.model.deletePermissionPossible(),
				sharePermission: OC.PERMISSION_SHARE,
				createPermission: OC.PERMISSION_CREATE,
				updatePermission: OC.PERMISSION_UPDATE,
				deletePermission: OC.PERMISSION_DELETE
			};

			this._collections = {};

			if(!this.model.hasUserShares()) {
				return [];
			}

			var shares = this.model.get('shares');
			var list = [];
			for(var index = 0; index < shares.length; index++) {
				if(this.model.isCollection(index)) {
					this.processCollectionShare(index);
				} else {
					// first empty {} is necessary, otherwise we get in trouble
					// with references
					list.push(_.extend({}, universal, this.getShareeObject(index)));
				}
			}
			list = _.union(_.values(this._collections), list);

			return list;
		},

		render: function() {
			this.$el.html(this.template({
				cid: this.cid,
				sharees: this.getShareeList()
			}));

			if(this.configModel.areAvatarsEnabled()) {
				this.$el.find('.avatar').each(function() {
					var $this = $(this);
					if ($this.hasClass('imageplaceholderseed')) {
						$this.css({width: 32, height: 32});
						$this.imageplaceholder($this.data('seed'));
					} else {
						$this.avatar($this.data('username'), 32);
					}
				});
			}

			this.delegateEvents();

			return this;
		},

		/**
		 * @returns {Function} from Handlebars
		 * @private
		 */
		template: function (data) {
			if (!this._template) {
				this._template = Handlebars.compile(TEMPLATE);
			}
			return this._template(data);
		},

		onUnshare: function(event) {
			var $element = $(event.target);
			if (!$element.is('a')) {
				$element = $element.closest('a');
			}

			var $loading = $element.find('.icon-loading-small').eq(0);
			if(!$loading.hasClass('hidden')) {
				// in process
				return false;
			}
			$loading.removeClass('hidden');

			var $li = $element.closest('li');
			var shareType = $li.data('share-type');
			var shareWith = $li.attr('data-share-with');

			this.model.removeShare(shareType, shareWith);

			return false;
		},

		onPermissionChange: function(event) {
			var $element = $(event.target);
			var $li = $element.closest('li');
			var shareType = $li.data('share-type');
			var shareWith = $li.attr('data-share-with');

			// adjust checkbox states
			var $checkboxes = $('.permissions', $li).not('input[name="edit"]').not('input[name="share"]');
			var checked;
			if ($element.attr('name') === 'edit') {
				checked = $element.is(':checked');
				// Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck
				$($checkboxes).attr('checked', checked);
			} else {
				var numberChecked = $checkboxes.filter(':checked').length;
				checked = numberChecked > 0;
				$('input[name="edit"]', $li).attr('checked', checked);
			}

			var permissions = OC.PERMISSION_READ;
			$('.permissions', $li).not('input[name="edit"]').filter(':checked').each(function(index, checkbox) {
				permissions |= $(checkbox).data('permissions');
			});

			// The federated share UI is a bit different so handle it properly
			if (shareType === OC.Share.SHARE_TYPE_REMOTE &&
				$element.attr('name') === 'edit' &&
				$element.is(':checked')) {
				permissions |= OC.PERMISSION_UPDATE;

				if (this.model.deletePermissionPossible()) {
					permissions |= OC.PERMISSION_CREATE;
				}
			}

			this.model.setPermissions(shareType, shareWith, permissions);
		},

		onCrudsToggle: function(event) {
			var $target = $(event.target);
			$target.closest('li').find('.cruds').toggleClass('hidden');
			return false;
		},

		onSendMailNotification: function(event) {
			var $target = $(event.target);
			var $li = $(event.target).closest('li');
			var shareType = $li.data('share-type');
			var shareWith = $li.attr('data-share-with');

			this.model.sendNotificationForShare(shareType, shareWith, $target.is(':checked'));
		}
	});

	OC.Share.ShareDialogShareeListView = ShareDialogShareeListView;

})();
<|endoftext|>