<commit_before>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, appshell, $, window */

define(function (require, exports, module) {
    "use strict";

    var FileUtils           = require("file/FileUtils"),
        FileSystemStats     = require("filesystem/FileSystemStats"),
        FileSystemError     = require("filesystem/FileSystemError"),
        NodeConnection      = require("utils/NodeConnection");

    var FILE_WATCHER_BATCH_TIMEOUT = 200;   // 200ms - granularity of file watcher changes

    var _changeCallback,            // Callback to notify FileSystem of watcher changes
        _offlineCallback,           // Callback to notify FileSystem that watchers are offline
        _changeTimeout,             // Timeout used to batch up file watcher changes
        _pendingChanges = {};       // Pending file watcher changes

    var _bracketsPath   = FileUtils.getNativeBracketsDirectoryPath(),
        _modulePath     = FileUtils.getNativeModuleDirectoryPath(module),
        _nodePath       = "node/FileWatcherDomain",
        _domainPath     = [_bracketsPath, _modulePath, _nodePath].join("/"),
        _nodeConnection = new NodeConnection(),
        _domainLoaded   = false;    // Whether the fileWatcher domain has been loaded

    /**
     * A promise that resolves when the NodeConnection object is connected and
     * the fileWatcher domain has been loaded.
     *
     * @type {?jQuery.Promise}
     */
    var _nodeConnectionPromise;

    /**
     * Load the fileWatcher domain on the assumed-open NodeConnection object
     *
     * @private
     */
    function _loadDomains() {
        return _nodeConnection
            .loadDomains(_domainPath, true)
            .done(function () {
                _domainLoaded = true;
                _nodeConnectionPromise = null;
            });
    }

    // Initialize the connection and connection promise
    _nodeConnectionPromise = _nodeConnection.connect(true).then(_loadDomains);

    // Setup the close handler. Re-initializes the connection promise and
    // notifies the FileSystem that watchers have gone offline.
    $(_nodeConnection).on("close", function (event, promise) {
        _domainLoaded = false;
        _nodeConnectionPromise = promise.then(_loadDomains);

        if (_offlineCallback) {
            _offlineCallback();
        }
    });

    /**
     * Enqueue a file change event for eventual reporting back to the FileSystem.
     *
     * @param {string} changedPath The path that was changed
     * @param {boolean} needsStats Whether or not the eventual change event should include stats
     * @private
     */
    function _enqueueChange(changedPath, needsStats) {
        _pendingChanges[changedPath] = _pendingChanges[changedPath] || needsStats;

        if (!_changeTimeout) {
            _changeTimeout = window.setTimeout(function () {
                if (_changeCallback) {
                    Object.keys(_pendingChanges).forEach(function (path) {
                        var needsStats = _pendingChanges[path];
                        if (needsStats) {
                            exports.stat(path, function (err, stats) {
                                if (err) {
                                    console.warn("Unable to stat changed path: ", path, err);
                                    return;
                                }
                                _changeCallback(path, stats);
                            });
                        } else {
                            _changeCallback(path);
                        }
                    });
                }

                _changeTimeout = null;
                _pendingChanges = {};
            }, FILE_WATCHER_BATCH_TIMEOUT);
        }
    }

    /**
     * Event handler for the Node fileWatcher domain's change event.
     *
     * @param {jQuery.Event} The underlying change event
     * @param {string} path The path that is reported to have changed
     * @param {string} event The type of the event: either "change" or "rename"
     * @param {string=} filename The name of the file that changed.
     * @private
     */
    function _fileWatcherChange(evt, path, event, filename) {
        var change;

        if (event === "change") {
            // Only register change events if filename is passed
            if (filename) {
                // an existing file was created; stats are needed
                change = path + filename;
                _enqueueChange(change, true);
            }
        } else if (event === "rename") {
            // a new file was created; no stats are needed
            change = path;
            _enqueueChange(change, false);
        }
    }

    // Setup the change handler. This only needs to happen once.
    $(_nodeConnection).on("fileWatcher.change", _fileWatcherChange);

    /**
     * Execute the named function from the fileWatcher domain when the
     * NodeConnection is connected and the domain has been loaded. Additional
     * parameters are passed as arguments to the command.
     *
     * @param {string} name The name of the command to execute
     * @return {jQuery.Promise} Resolves with the results of the command.
     * @private
     */
    function _execWhenConnected(name) {
        var params = Array.prototype.slice.call(arguments, 1);

        function execConnected() {
            var domains = _nodeConnection.domains,
                domain = domains && domains.fileWatcher,
                fn = domain && domain[name];

            if (fn) {
                return fn.apply(domain, params);
            } else {
                return $.Deferred().reject().promise();
            }
        }

        if (_domainLoaded && _nodeConnection.connected()) {
            return execConnected();
        } else {
            return _nodeConnectionPromise.then(execConnected);
        }
    }

    /**
     * Convert appshell error codes to FileSystemError values.
     *
     * @param {?number} err An appshell error code
     * @return {?string} A FileSystemError string, or null if there was no error code.
     * @private
     */
    function _mapError(err) {
        if (!err) {
            return null;
        }

        switch (err) {
        case appshell.fs.ERR_INVALID_PARAMS:
            return FileSystemError.INVALID_PARAMS;
        case appshell.fs.ERR_NOT_FOUND:
            return FileSystemError.NOT_FOUND;
        case appshell.fs.ERR_CANT_READ:
            return FileSystemError.NOT_READABLE;
        case appshell.fs.ERR_CANT_WRITE:
            return FileSystemError.NOT_WRITABLE;
        case appshell.fs.ERR_UNSUPPORTED_ENCODING:
            return FileSystemError.NOT_READABLE;
        case appshell.fs.ERR_OUT_OF_SPACE:
            return FileSystemError.OUT_OF_SPACE;
        case appshell.fs.ERR_FILE_EXISTS:
            return FileSystemError.ALREADY_EXISTS;
        }
        return FileSystemError.UNKNOWN;
    }

    /**
     * Convert a callback to one that transforms its first parameter from an
     * appshell error code to a FileSystemError string.
     *
     * @param {function(?number)} cb A callback that expects an appshell error code
     * @return {function(?string)} A callback that expects a FileSystemError string
     * @private
     */
    function _wrap(cb) {
        return function (err) {
            var args = Array.prototype.slice.call(arguments);
            args[0] = _mapError(args[0]);
            cb.apply(null, args);
        };
    }

    function showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback) {
        appshell.fs.showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, _wrap(callback));
    }

    function showSaveDialog(title, initialPath, proposedNewFilename, callback) {
        appshell.fs.showSaveDialog(title, initialPath, proposedNewFilename, _wrap(callback));
    }

    function stat(path, callback) {
        appshell.fs.stat(path, function (err, stats) {
            if (err) {
                callback(_mapError(err));
            } else {
                var options = {
                    isFile: stats.isFile(),
                    mtime: stats.mtime,
                    size: stats.size,
                    realPath: stats.realPath,
                    hash: stats.mtime.getTime()
                };

                var fsStats = new FileSystemStats(options);

                callback(null, fsStats);
            }
        });
    }

    function exists(path, callback) {
        stat(path, function (err) {
            if (err) {
                if (err === FileSystemError.NOT_FOUND) {
                    callback(null, false);
                } else {
                    callback(err);
                }
                return;
            }

            callback(null, true);
        });
    }

    function readdir(path, callback) {
        appshell.fs.readdir(path, function (err, contents) {
            if (err) {
                callback(_mapError(err));
                return;
            }

            var count = contents.length;
            if (!count) {
                callback(null, [], []);
                return;
            }

            var stats = [];
            contents.forEach(function (val, idx) {
                stat(path + "/" + val, function (err, stat) {
                    stats[idx] = err || stat;
                    count--;
                    if (count <= 0) {
                        callback(null, contents, stats);
                    }
                });
            });
        });
    }

    function mkdir(path, mode, callback) {
        if (typeof mode === "function") {
            callback = mode;
            mode = parseInt("0755", 8);
        }
        appshell.fs.makedir(path, mode, function (err) {
            if (err) {
                callback(_mapError(err));
            } else {
                stat(path, function (err, stat) {
                    callback(err, stat);
                });
            }
        });
    }

    function rename(oldPath, newPath, callback) {
        appshell.fs.rename(oldPath, newPath, _wrap(callback));
        // No need to fake a file-watcher result here: FileSystem already updates index on rename()
    }

    /*
     * Note: if either the read or the stat call fails then neither the read data
     * or stat will be passed back, and the call should be considered to have failed.
     * If both calls fail, the error from the read call is passed back.
     */
    function readFile(path, options, callback) {
        var encoding = options.encoding || "utf8";

        // Execute the read and stat calls in parallel
        var done = false, data, stat, err;

        appshell.fs.readFile(path, encoding, function (_err, _data) {
            if (_err) {
                callback(_mapError(_err));
                return;
            }

            if (done) {
                callback(err, err ? null : _data, stat);
            } else {
                done = true;
                data = _data;
            }
        });

        exports.stat(path, function (_err, _stat) {
            if (done) {
                callback(_err, _err ? null : data, _stat);
            } else {
                done = true;
                stat = _stat;
                err = _err;
            }
        });
    }

    function writeFile(path, data, options, callback) {
        var encoding = options.encoding || "utf8";

        function _finishWrite(created) {
            appshell.fs.writeFile(path, data, encoding, function (err) {
                if (err) {
                    callback(_mapError(err));
                } else {
                    stat(path, function (err, stat) {
                        callback(err, stat, created);
                    });
                }
            });
        }

        stat(path, function (err, stats) {
            if (err) {
                switch (err) {
                case FileSystemError.NOT_FOUND:
                    _finishWrite(true);
                    break;
                default:
                    callback(err);
                }
                return;
            }

            if (options.hasOwnProperty("hash") && options.hash !== stats._hash) {
                console.warn("Blind write attempted: ", path, stats._hash, options.hash);
                callback(FileSystemError.CONTENTS_MODIFIED);
                return;
            }

            _finishWrite(false);
        });
    }

    function unlink(path, callback) {
        appshell.fs.unlink(path, function (err) {
            callback(_mapError(err));
        });
    }

    function moveToTrash(path, callback) {
        appshell.fs.moveToTrash(path, function (err) {
            callback(_mapError(err));
        });
    }

    function initWatchers(changeCallback, offlineCallback) {
        _changeCallback = changeCallback;
        _offlineCallback = offlineCallback;
    }

    function watchPath(path, callback) {
        appshell.fs.isNetworkDrive(path, function (err, isNetworkDrive) {
            if (err || isNetworkDrive) {
                callback(FileSystemError.UNKNOWN);
                return;
            }

            _execWhenConnected("watchPath", path)
                .done(callback.bind(undefined, null))
                .fail(callback);
        });
    }

    function unwatchPath(path, callback) {
        appshell.fs.isNetworkDrive(path, function (err, isNetworkDrive) {
            if (err || isNetworkDrive) {
                callback(FileSystemError.UNKNOWN);
                return;
            }

            _execWhenConnected("unwatchPath", path)
                .done(callback.bind(undefined, null))
                .fail(callback);
        });
    }

    function unwatchAll(callback) {
        appshell.fs.isNetworkDrive(path, function (err, isNetworkDrive) {
            if (err || isNetworkDrive) {
                callback(FileSystemError.UNKNOWN);
                return;
            }

            _execWhenConnected("unwatchAll")
                .done(callback.bind(undefined, null))
                .fail(callback);
        });
    }

    // Export public API
    exports.showOpenDialog  = showOpenDialog;
    exports.showSaveDialog  = showSaveDialog;
    exports.exists          = exists;
    exports.readdir         = readdir;
    exports.mkdir           = mkdir;
    exports.rename          = rename;
    exports.stat            = stat;
    exports.readFile        = readFile;
    exports.writeFile       = writeFile;
    exports.unlink          = unlink;
    exports.moveToTrash     = moveToTrash;
    exports.initWatchers    = initWatchers;
    exports.watchPath       = watchPath;
    exports.unwatchPath     = unwatchPath;
    exports.unwatchAll      = unwatchAll;

    // Node only supports recursive file watching on the Darwin
    exports.recursiveWatch = appshell.platform === "mac";

    // Only perform UNC path normalization on Windows
    exports.normalizeUNCPaths = appshell.platform === "win";
});
<commit_msg>Handle a potential race in which the socket has disconnected but the reconnect has not yet begun.<commit_after>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, appshell, $, window */

define(function (require, exports, module) {
    "use strict";

    var FileUtils           = require("file/FileUtils"),
        FileSystemStats     = require("filesystem/FileSystemStats"),
        FileSystemError     = require("filesystem/FileSystemError"),
        NodeConnection      = require("utils/NodeConnection");

    var FILE_WATCHER_BATCH_TIMEOUT = 200;   // 200ms - granularity of file watcher changes

    var _changeCallback,            // Callback to notify FileSystem of watcher changes
        _offlineCallback,           // Callback to notify FileSystem that watchers are offline
        _changeTimeout,             // Timeout used to batch up file watcher changes
        _pendingChanges = {};       // Pending file watcher changes

    var _bracketsPath   = FileUtils.getNativeBracketsDirectoryPath(),
        _modulePath     = FileUtils.getNativeModuleDirectoryPath(module),
        _nodePath       = "node/FileWatcherDomain",
        _domainPath     = [_bracketsPath, _modulePath, _nodePath].join("/"),
        _nodeConnection = new NodeConnection(),
        _domainLoaded   = false;    // Whether the fileWatcher domain has been loaded

    /**
     * A promise that resolves when the NodeConnection object is connected and
     * the fileWatcher domain has been loaded.
     *
     * @type {?jQuery.Promise}
     */
    var _nodeConnectionPromise;

    /**
     * Load the fileWatcher domain on the assumed-open NodeConnection object
     *
     * @private
     */
    function _loadDomains() {
        return _nodeConnection
            .loadDomains(_domainPath, true)
            .done(function () {
                _domainLoaded = true;
                _nodeConnectionPromise = null;
            });
    }

    // Initialize the connection and connection promise
    _nodeConnectionPromise = _nodeConnection.connect(true).then(_loadDomains);

    // Setup the close handler. Re-initializes the connection promise and
    // notifies the FileSystem that watchers have gone offline.
    $(_nodeConnection).on("close", function (event, promise) {
        _domainLoaded = false;
        _nodeConnectionPromise = promise.then(_loadDomains);

        if (_offlineCallback) {
            _offlineCallback();
        }
    });

    /**
     * Enqueue a file change event for eventual reporting back to the FileSystem.
     *
     * @param {string} changedPath The path that was changed
     * @param {boolean} needsStats Whether or not the eventual change event should include stats
     * @private
     */
    function _enqueueChange(changedPath, needsStats) {
        _pendingChanges[changedPath] = _pendingChanges[changedPath] || needsStats;

        if (!_changeTimeout) {
            _changeTimeout = window.setTimeout(function () {
                if (_changeCallback) {
                    Object.keys(_pendingChanges).forEach(function (path) {
                        var needsStats = _pendingChanges[path];
                        if (needsStats) {
                            exports.stat(path, function (err, stats) {
                                if (err) {
                                    console.warn("Unable to stat changed path: ", path, err);
                                    return;
                                }
                                _changeCallback(path, stats);
                            });
                        } else {
                            _changeCallback(path);
                        }
                    });
                }

                _changeTimeout = null;
                _pendingChanges = {};
            }, FILE_WATCHER_BATCH_TIMEOUT);
        }
    }

    /**
     * Event handler for the Node fileWatcher domain's change event.
     *
     * @param {jQuery.Event} The underlying change event
     * @param {string} path The path that is reported to have changed
     * @param {string} event The type of the event: either "change" or "rename"
     * @param {string=} filename The name of the file that changed.
     * @private
     */
    function _fileWatcherChange(evt, path, event, filename) {
        var change;

        if (event === "change") {
            // Only register change events if filename is passed
            if (filename) {
                // an existing file was created; stats are needed
                change = path + filename;
                _enqueueChange(change, true);
            }
        } else if (event === "rename") {
            // a new file was created; no stats are needed
            change = path;
            _enqueueChange(change, false);
        }
    }

    // Setup the change handler. This only needs to happen once.
    $(_nodeConnection).on("fileWatcher.change", _fileWatcherChange);

    /**
     * Execute the named function from the fileWatcher domain when the
     * NodeConnection is connected and the domain has been loaded. Additional
     * parameters are passed as arguments to the command.
     *
     * @param {string} name The name of the command to execute
     * @return {jQuery.Promise} Resolves with the results of the command.
     * @private
     */
    function _execWhenConnected(name) {
        var params = Array.prototype.slice.call(arguments, 1);

        function execConnected() {
            var domains = _nodeConnection.domains,
                domain = domains && domains.fileWatcher,
                fn = domain && domain[name];

            if (fn) {
                return fn.apply(domain, params);
            } else {
                return $.Deferred().reject().promise();
            }
        }

        if (_domainLoaded && _nodeConnection.connected()) {
            return execConnected();
        } else if (_nodeConnectionPromise) {
            return _nodeConnectionPromise.then(execConnected);
        } else {
            return $.Deferred().reject().promise();
        }
    }

    /**
     * Convert appshell error codes to FileSystemError values.
     *
     * @param {?number} err An appshell error code
     * @return {?string} A FileSystemError string, or null if there was no error code.
     * @private
     */
    function _mapError(err) {
        if (!err) {
            return null;
        }

        switch (err) {
        case appshell.fs.ERR_INVALID_PARAMS:
            return FileSystemError.INVALID_PARAMS;
        case appshell.fs.ERR_NOT_FOUND:
            return FileSystemError.NOT_FOUND;
        case appshell.fs.ERR_CANT_READ:
            return FileSystemError.NOT_READABLE;
        case appshell.fs.ERR_CANT_WRITE:
            return FileSystemError.NOT_WRITABLE;
        case appshell.fs.ERR_UNSUPPORTED_ENCODING:
            return FileSystemError.NOT_READABLE;
        case appshell.fs.ERR_OUT_OF_SPACE:
            return FileSystemError.OUT_OF_SPACE;
        case appshell.fs.ERR_FILE_EXISTS:
            return FileSystemError.ALREADY_EXISTS;
        }
        return FileSystemError.UNKNOWN;
    }

    /**
     * Convert a callback to one that transforms its first parameter from an
     * appshell error code to a FileSystemError string.
     *
     * @param {function(?number)} cb A callback that expects an appshell error code
     * @return {function(?string)} A callback that expects a FileSystemError string
     * @private
     */
    function _wrap(cb) {
        return function (err) {
            var args = Array.prototype.slice.call(arguments);
            args[0] = _mapError(args[0]);
            cb.apply(null, args);
        };
    }

    function showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback) {
        appshell.fs.showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, _wrap(callback));
    }

    function showSaveDialog(title, initialPath, proposedNewFilename, callback) {
        appshell.fs.showSaveDialog(title, initialPath, proposedNewFilename, _wrap(callback));
    }

    function stat(path, callback) {
        appshell.fs.stat(path, function (err, stats) {
            if (err) {
                callback(_mapError(err));
            } else {
                var options = {
                    isFile: stats.isFile(),
                    mtime: stats.mtime,
                    size: stats.size,
                    realPath: stats.realPath,
                    hash: stats.mtime.getTime()
                };

                var fsStats = new FileSystemStats(options);

                callback(null, fsStats);
            }
        });
    }

    function exists(path, callback) {
        stat(path, function (err) {
            if (err) {
                if (err === FileSystemError.NOT_FOUND) {
                    callback(null, false);
                } else {
                    callback(err);
                }
                return;
            }

            callback(null, true);
        });
    }

    function readdir(path, callback) {
        appshell.fs.readdir(path, function (err, contents) {
            if (err) {
                callback(_mapError(err));
                return;
            }

            var count = contents.length;
            if (!count) {
                callback(null, [], []);
                return;
            }

            var stats = [];
            contents.forEach(function (val, idx) {
                stat(path + "/" + val, function (err, stat) {
                    stats[idx] = err || stat;
                    count--;
                    if (count <= 0) {
                        callback(null, contents, stats);
                    }
                });
            });
        });
    }

    function mkdir(path, mode, callback) {
        if (typeof mode === "function") {
            callback = mode;
            mode = parseInt("0755", 8);
        }
        appshell.fs.makedir(path, mode, function (err) {
            if (err) {
                callback(_mapError(err));
            } else {
                stat(path, function (err, stat) {
                    callback(err, stat);
                });
            }
        });
    }

    function rename(oldPath, newPath, callback) {
        appshell.fs.rename(oldPath, newPath, _wrap(callback));
        // No need to fake a file-watcher result here: FileSystem already updates index on rename()
    }

    /*
     * Note: if either the read or the stat call fails then neither the read data
     * or stat will be passed back, and the call should be considered to have failed.
     * If both calls fail, the error from the read call is passed back.
     */
    function readFile(path, options, callback) {
        var encoding = options.encoding || "utf8";

        // Execute the read and stat calls in parallel
        var done = false, data, stat, err;

        appshell.fs.readFile(path, encoding, function (_err, _data) {
            if (_err) {
                callback(_mapError(_err));
                return;
            }

            if (done) {
                callback(err, err ? null : _data, stat);
            } else {
                done = true;
                data = _data;
            }
        });

        exports.stat(path, function (_err, _stat) {
            if (done) {
                callback(_err, _err ? null : data, _stat);
            } else {
                done = true;
                stat = _stat;
                err = _err;
            }
        });
    }

    function writeFile(path, data, options, callback) {
        var encoding = options.encoding || "utf8";

        function _finishWrite(created) {
            appshell.fs.writeFile(path, data, encoding, function (err) {
                if (err) {
                    callback(_mapError(err));
                } else {
                    stat(path, function (err, stat) {
                        callback(err, stat, created);
                    });
                }
            });
        }

        stat(path, function (err, stats) {
            if (err) {
                switch (err) {
                case FileSystemError.NOT_FOUND:
                    _finishWrite(true);
                    break;
                default:
                    callback(err);
                }
                return;
            }

            if (options.hasOwnProperty("hash") && options.hash !== stats._hash) {
                console.warn("Blind write attempted: ", path, stats._hash, options.hash);
                callback(FileSystemError.CONTENTS_MODIFIED);
                return;
            }

            _finishWrite(false);
        });
    }

    function unlink(path, callback) {
        appshell.fs.unlink(path, function (err) {
            callback(_mapError(err));
        });
    }

    function moveToTrash(path, callback) {
        appshell.fs.moveToTrash(path, function (err) {
            callback(_mapError(err));
        });
    }

    function initWatchers(changeCallback, offlineCallback) {
        _changeCallback = changeCallback;
        _offlineCallback = offlineCallback;
    }

    function watchPath(path, callback) {
        appshell.fs.isNetworkDrive(path, function (err, isNetworkDrive) {
            if (err || isNetworkDrive) {
                callback(FileSystemError.UNKNOWN);
                return;
            }

            _execWhenConnected("watchPath", path)
                .done(callback.bind(undefined, null))
                .fail(callback);
        });
    }

    function unwatchPath(path, callback) {
        appshell.fs.isNetworkDrive(path, function (err, isNetworkDrive) {
            if (err || isNetworkDrive) {
                callback(FileSystemError.UNKNOWN);
                return;
            }

            _execWhenConnected("unwatchPath", path)
                .done(callback.bind(undefined, null))
                .fail(callback);
        });
    }

    function unwatchAll(callback) {
        appshell.fs.isNetworkDrive(path, function (err, isNetworkDrive) {
            if (err || isNetworkDrive) {
                callback(FileSystemError.UNKNOWN);
                return;
            }

            _execWhenConnected("unwatchAll")
                .done(callback.bind(undefined, null))
                .fail(callback);
        });
    }

    // Export public API
    exports.showOpenDialog  = showOpenDialog;
    exports.showSaveDialog  = showSaveDialog;
    exports.exists          = exists;
    exports.readdir         = readdir;
    exports.mkdir           = mkdir;
    exports.rename          = rename;
    exports.stat            = stat;
    exports.readFile        = readFile;
    exports.writeFile       = writeFile;
    exports.unlink          = unlink;
    exports.moveToTrash     = moveToTrash;
    exports.initWatchers    = initWatchers;
    exports.watchPath       = watchPath;
    exports.unwatchPath     = unwatchPath;
    exports.unwatchAll      = unwatchAll;

    // Node only supports recursive file watching on the Darwin
    exports.recursiveWatch = appshell.platform === "mac";

    // Only perform UNC path normalization on Windows
    exports.normalizeUNCPaths = appshell.platform === "win";
});
<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @author       Felipe Alfonso <@bitnenfer>
 * @copyright    2019 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * Renders this Game Object with the WebGL Renderer to the given Camera.
 * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
 * This method should not be called directly. It is a utility function of the Render module.
 *
 * @method Phaser.GameObjects.Container#renderWebGL
 * @since 3.4.0
 * @private
 *
 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the current active WebGL renderer.
 * @param {Phaser.GameObjects.Container} container - The Game Object being rendered in this call.
 * @param {number} interpolationPercentage - Reserved for future use and custom pipelines.
 * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object.
 * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested
 */
var ContainerWebGLRenderer = function (renderer, container, interpolationPercentage, camera, parentMatrix)
{
    var children = container.list;

    if (children.length === 0)
    {
        return;
    }

    var transformMatrix = container.localTransform;

    if (parentMatrix)
    {
        transformMatrix.loadIdentity();
        transformMatrix.multiply(parentMatrix);
        transformMatrix.translate(container.x, container.y);
        transformMatrix.rotate(container.rotation);
        transformMatrix.scale(container.scaleX, container.scaleY);
    }
    else
    {
        transformMatrix.applyITRS(container.x, container.y, container.rotation, container.scaleX, container.scaleY);
    }

    var containerHasBlendMode = (container.blendMode !== -1);

    if (!containerHasBlendMode)
    {
        //  If Container is SKIP_TEST then set blend mode to be Normal
        renderer.setBlendMode(0);
    }

    var alpha = container._alpha;
    var scrollFactorX = container.scrollFactorX;
    var scrollFactorY = container.scrollFactorY;

    for (var i = 0; i < children.length; i++)
    {
        var child = children[i];

        if (!child.willRender(camera))
        {
            continue;
        }

        var childAlpha = child.alpha;
        var childScrollFactorX = child.scrollFactorX;
        var childScrollFactorY = child.scrollFactorY;

        if (!containerHasBlendMode && child.blendMode !== renderer.currentBlendMode)
        {
            //  If Container doesn't have its own blend mode, then a child can have one
            renderer.setBlendMode(child.blendMode);
        }

        if (child.mask)
        {
            child.mask.preRenderWebGL(renderer, child, camera);
        }

        //  Set parent values
        child.setScrollFactor(childScrollFactorX * scrollFactorX, childScrollFactorY * scrollFactorY);
        child.setAlpha(childAlpha * alpha);

        //  Render
        child.renderWebGL(renderer, child, interpolationPercentage, camera, transformMatrix);

        //  Restore original values
        child.setAlpha(childAlpha);
        child.setScrollFactor(childScrollFactorX, childScrollFactorY);

        if (child.mask)
        {
            child.mask.postRenderWebGL(renderer, camera);
        }
    }
};

module.exports = ContainerWebGLRenderer;
<commit_msg>Container now can apply alpha quads<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @author       Felipe Alfonso <@bitnenfer>
 * @copyright    2019 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * Renders this Game Object with the WebGL Renderer to the given Camera.
 * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
 * This method should not be called directly. It is a utility function of the Render module.
 *
 * @method Phaser.GameObjects.Container#renderWebGL
 * @since 3.4.0
 * @private
 *
 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the current active WebGL renderer.
 * @param {Phaser.GameObjects.Container} container - The Game Object being rendered in this call.
 * @param {number} interpolationPercentage - Reserved for future use and custom pipelines.
 * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object.
 * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested
 */
var ContainerWebGLRenderer = function (renderer, container, interpolationPercentage, camera, parentMatrix)
{
    var children = container.list;

    if (children.length === 0)
    {
        return;
    }

    var transformMatrix = container.localTransform;

    if (parentMatrix)
    {
        transformMatrix.loadIdentity();
        transformMatrix.multiply(parentMatrix);
        transformMatrix.translate(container.x, container.y);
        transformMatrix.rotate(container.rotation);
        transformMatrix.scale(container.scaleX, container.scaleY);
    }
    else
    {
        transformMatrix.applyITRS(container.x, container.y, container.rotation, container.scaleX, container.scaleY);
    }

    var containerHasBlendMode = (container.blendMode !== -1);

    if (!containerHasBlendMode)
    {
        //  If Container is SKIP_TEST then set blend mode to be Normal
        renderer.setBlendMode(0);
    }

    var alpha = container._alpha;

    var alphaTopRight = container.alphaTopRight;
    var alphaBottomLeft = container.alphaBottomLeft;
    var alphaBottomRight = container.alphaBottomRight;
    var usingQuadAlpha = (alphaBottomLeft !== alpha || alphaBottomRight !== alpha || alphaTopRight !== alpha);

    var scrollFactorX = container.scrollFactorX;
    var scrollFactorY = container.scrollFactorY;

    for (var i = 0; i < children.length; i++)
    {
        var child = children[i];

        if (!child.willRender(camera))
        {
            continue;
        }

        var childAlpha = child.alpha;
        var childAlphaTopRight = child.alphaTopRight;
        var childAlphaBottomLeft = child.alphaBottomLeft;
        var childAlphaBottomRight = child.alphaBottomRight;
        var usingChildQuadAlpha = (childAlphaBottomLeft !== childAlpha || childAlphaBottomRight !== childAlpha || childAlphaTopRight !== childAlpha);

        var childScrollFactorX = child.scrollFactorX;
        var childScrollFactorY = child.scrollFactorY;

        if (!containerHasBlendMode && child.blendMode !== renderer.currentBlendMode)
        {
            //  If Container doesn't have its own blend mode, then a child can have one
            renderer.setBlendMode(child.blendMode);
        }

        if (child.mask)
        {
            child.mask.preRenderWebGL(renderer, child, camera);
        }

        //  Set parent values
        child.setScrollFactor(childScrollFactorX * scrollFactorX, childScrollFactorY * scrollFactorY);

        if(usingQuadAlpha)
        {
            if(usingChildQuadAlpha)
            {
                child.setAlpha(childAlpha * alpha, childAlphaTopRight * alphaTopRight, childAlphaBottomLeft * alphaBottomLeft, childAlphaBottomRight * alphaBottomRight);
            }
            else
            {
                child.setAlpha(childAlpha * alpha, childAlpha * alphaTopRight, childAlpha * alphaBottomLeft, childAlpha * alphaBottomRight);
            }
        }
        else
        {
            if(usingChildQuadAlpha)
            {
                child.setAlpha(childAlpha * alpha, childAlphaTopRight * alpha, childAlphaBottomLeft * alpha, childAlphaBottomRight * alpha);
            }
            else
            {
                child.setAlpha(childAlpha * alpha);
            }
        }

        //  Render
        child.renderWebGL(renderer, child, interpolationPercentage, camera, transformMatrix);

        //  Restore original values
        if(usingChildQuadAlpha)
        {
            child.setAlpha(childAlpha, childAlphaTopRight, childAlphaBottomLeft, childAlphaBottomRight);
        }
        else
        {
            child.setAlpha(childAlpha);
        }
        child.setScrollFactor(childScrollFactorX, childScrollFactorY);

        if (child.mask)
        {
            child.mask.postRenderWebGL(renderer, camera);
        }
    }
};

module.exports = ContainerWebGLRenderer;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Header extends Component {
    super(props) {}
    render() {
        const { guest } = this.props;
        // const id = guest.id ? guest.id : '';
        return (

                                K / D

        );
    }
}

Header.propTypes = {
    guest: PropTypes.object,
};

Header.defaultProps = {
    guest: {},
};

export default Header;

//

//

//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#the-details`}
//                                 >
//                                     The details
//
//

//                             {!guest.rsvp && (
//

//                                     <a
//                                         href={`https://daleandkatie.co.nz/#/${id}#rsvp`}
//                                     >
//                                         RSVP
//
//

//                             )}
//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#travel`}
//                                 >
//                                     Travel
//
//

//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#accommodation`}
//                                 >
//                                     Accommodation
//
//

//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#the-extras`}
//                                 >
//                                     The extras
//
//

//

//

<commit_msg>changing to stateless<commit_after>import React from 'react';
// import PropTypes from 'prop-types';

const Header = () => {
    // const id = guest.id ? guest.id : '';
    return (

                            K / D

    );
};

// Header.propTypes = {
//     guest: PropTypes.object,
// };

// Header.defaultProps = {
//     guest: {},
// };

export default Header;

//

//

//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#the-details`}
//                                 >
//                                     The details
//
//

//                             {!guest.rsvp && (
//

//                                     <a
//                                         href={`https://daleandkatie.co.nz/#/${id}#rsvp`}
//                                     >
//                                         RSVP
//
//

//                             )}
//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#travel`}
//                                 >
//                                     Travel
//
//

//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#accommodation`}
//                                 >
//                                     Accommodation
//
//

//

//                                 <a
//                                     href={`https://daleandkatie.co.nz/#/${id}#the-extras`}
//                                 >
//                                     The extras
//
//

//

//

<|endoftext|>


<commit_before>require('../../../arrayExtensions')

import ko from 'knockout'

const ajax = require('../../../get-api-data')
const browser = require('../../../browser')
const endpoints = require('../../../api')
const querystring = require('../../../get-url-parameter')
const listToDropdown = require('../../../list-to-dropdown')

import { getProvidersForListing } from '../../../pages/find-help/provider-listing/helpers'
import FindHelp from './FindHelp'
import { categories } from '../../../../data/generated/service-categories'

class CatFilter {
  constructor (data, container) {
    this.id = data.key
    this.name = data.name
    this.container = container
    this.isSelected = ko.observable(false)
    this.subCategories = ko.observableArray(data.subCategories ? data.subCategories.map((sc) => new SubCatFilter(sc)) : [])
  }

  filter (isInit = false) {
    // If is isInit == true it means we load page first time and we need to set selected categories to TRUE.
    this.isSelected(isInit === true ? true : !this.isSelected())
    this.setSubcategories()
    this.container.onCatFilter(this.id, isInit === true)
  }

  filterMobile () {
    this.isSelected(true)
    this.setSubcategories()
    this.container.onCatMobileFilter(this.id)
  }

  filterOnCheck () {
    this.setSubcategories()
    this.container.onCatFilter(this.id)
    return true
  }

  setSubcategories () {
    this.subCategories().forEach((x) => {
      x.isSelected(this.isSelected())
    })
  }
}

class SubCatFilter {
  constructor (data) {
    this.id = data.key
    this.name = data.name
    this.isSelected = ko.observable(false)
  }
}

export default class FindHelpByClientGroup extends FindHelp {
  constructor (pageSize = 25) {
    super([{
      qsKey: 'catIds',
      getValue: () => {
        const selectedCatFilters = this.catFilters().filter((c) => c.id !== undefined && c.isSelected()).map((c) => c.id)
        return selectedCatFilters && selectedCatFilters.length
          ? selectedCatFilters.join(',')
          : undefined
      }
    },
    {
      qsKey: 'subCatIds',
      getValue: () => {
        var categories = this.catFilters().filter((c) => c.id !== undefined && c.isSelected())
        if (categories && categories.length) {
          const selectedSubCatFilters = categories.map((c) => c.subCategories())
                                                  .reduce(function (a, b) { return a.concat(b) })
                                                  .map((sc) => sc.id)
          return selectedSubCatFilters && selectedSubCatFilters.length
                  ? selectedSubCatFilters.join(',')
                  : undefined
        }
        return undefined
      }
    }])

    this.allItems = ko.observableArray([])
    this.allOriginalItems = ko.observableArray([])
    this.isLoaded = false
    this.pageSize = pageSize
    this.pageIndex = ko.observable(0)
    this.totalItems = ko.observable(0)
    this.hasMorePages = ko.computed(() => (this.pageIndex() + this.pageSize) < this.totalItems(), this)
    this.catFilters = ko.computed(this.getCatFilters, this)

    this.shouldShowCatFilter = ko.computed(() => {
      return this.catFilters().length > 1
    }, this)

    const postcodeInQuerystring = querystring.parameter('postcode')
    if (postcodeInQuerystring) {
      this.proximitySearch.postcode(postcodeInQuerystring)
      this.proximitySearch.search()
    }

    if (this.proximitySearch.hasCoords() && !this.isLoaded) {
      this.onProximitySearch(false, true)
    }

    browser.setOnHistoryPop((e) => {
      this.onBrowserHistoryBack(this, e)
    })
  }

  loadMore () {
    this.pageIndex(this.pageIndex() + this.pageSize)
    this.onProximitySearch(true, false)
  }

  clearCategoriesFilter () {
    this.catFilters().forEach((x) => {
      x.isSelected(false)
      x.setSubcategories()
    })
  }

  initMoblileDropdown () {
    listToDropdown.init(
      () => {},
      (event) => {
        this.onCatFilterByName(event.target.value)
      }
    )
  }

  onProximitySearch (isLoadMore, isInit) {
    // We have static list of filter-categories. As result we need to initialize it once.
    if (!this.isLoaded) {
      this.initMoblileDropdown()
    }

    this.isLoaded = true
    browser.loading()
    var catIdsInQuerystring = querystring.parameter('catIds')
    var subCatIdsInQuerystring = querystring.parameter('subCatIds')
    var isEmptyQuery = false

    // If isInit == true it means we load page first time and need to get url parameters.
    if (isInit && this.catFilters().length > 1 && !catIdsInQuerystring) {
      this.catFilters()[1].isSelected(true)
      this.catFilters()[1].setSubcategories()
      this.pushHistory()
      catIdsInQuerystring = querystring.parameter('catIds')
      subCatIdsInQuerystring = querystring.parameter('subCatIds')
      isEmptyQuery = true
    } else if (!catIdsInQuerystring) {
      this.pushHistory()
      isEmptyQuery = true
    }

    if (isLoadMore !== true) {
      this.pageIndex(0)
    }

    ajax
      .data(`${endpoints.serviceCategories}${this.proximitySearch.latitude}/${this.proximitySearch.longitude}?range=${this.proximitySearch.range()}&pageSize=${this.pageSize}&index=${this.pageIndex()}&clientGroup=${this.encodeClientGroupKey(this.clientGroup.clientGroupKey)}&catIds=${catIdsInQuerystring}&subCatIds=${subCatIdsInQuerystring}`)
      .then((result) => {
        if (isLoadMore !== true) {
          this.totalItems(result.data.total)
          this.allOriginalItems([])
        }

        this.allOriginalItems(this.allOriginalItems().concat(result.data.providers))
        this.allItems(getProvidersForListing(this.allOriginalItems()))
        this.items(this.allItems())

        if (isInit && !isEmptyQuery) {
          this.setCatFilter(catIdsInQuerystring)
        }

        browser.loaded()
      }, (_) => {
        browser.redirect('/500')
      })
  }

  onBrowserHistoryBack (thisDoobrey, e) {
    if (e.state && e.state) {
      this.clearCategoriesFilter()
      this.setCatFilter(e.state.catIds)
    }

    if (e.state && e.state.postcode !== thisDoobrey.proximitySearch.postcode()) {
      this.proximitySearch.postcode(e.state.postcode)
      this.proximitySearch.search()
    }
  }

  onCatFilter (catId, isInit = false) {
    if (!catId) {
      this.catFilters()
      .forEach((c) => c.isSelected(this.catFilters().find((f) => c.categoryId === undefined).isSelected()))
    } else {
      if (this.catFilters().filter((f) => f.id !== undefined).every((e) => e.isSelected() === true)) {
        this.catFilters().find((f) => f.id === undefined).isSelected(true)
      } else {
        this.catFilters().find((f) => f.id === undefined).isSelected(false)
      }
    }

    this.pushHistory()

    // If isInit == true it means we have just called onProximitySearch() and we don't need to call onProximitySearch() again.
    if (!isInit) {
      this.onProximitySearch(false, false)
    }
  }

  onCatFilterByName (catName) {
    const category = this.catFilters().find((c) => c.name === catName.trim())
    category.filterMobile()
  }

  onCatMobileFilter (catId) {
    let isSelected = !(catId !== undefined)
    this.catFilters()
      .filter((c) => c.id !== catId)
      .forEach((c) => c.isSelected(isSelected))
    this.pushHistory()
    this.onProximitySearch(false, false)
  }

  getCatFilters () {
    const showAll = new CatFilter({ name: 'Show All', isSelected: false }, this)
    const cats = [showAll, ...categories.map((c) => new CatFilter(c, this)).sortAsc('name')]
    return cats
  }

  setCatFilter (catIds) {
    var listIds = catIds.split(',')
    const selectedCatFilters = this.catFilters()
      .filter((c) => listIds.some((i) => i === c.id))

    if (selectedCatFilters) {
      selectedCatFilters.forEach((f) => f.filter(true))
    }
  }
}
<commit_msg>Init mobile filters on loading page<commit_after>require('../../../arrayExtensions')

import ko from 'knockout'
import utils from '../../../utils'

const ajax = require('../../../get-api-data')
const browser = require('../../../browser')
const endpoints = require('../../../api')
const querystring = require('../../../get-url-parameter')
const listToDropdown = require('../../../list-to-dropdown')

import { getProvidersForListing } from '../../../pages/find-help/provider-listing/helpers'
import FindHelp from './FindHelp'
import { categories } from '../../../../data/generated/service-categories'

class CatFilter {
  constructor (data, container) {
    this.id = data.key
    this.name = data.name
    this.container = container
    this.isSelected = ko.observable(false)
    this.subCategories = ko.observableArray(data.subCategories ? data.subCategories.map((sc) => new SubCatFilter(sc)) : [])
  }

  filter (isInit = false) {
    // If is isInit == true it means we load page first time and we need to set selected categories to TRUE.
    this.isSelected(isInit === true ? true : !this.isSelected())
    this.setSubcategories()
    this.container.onCatFilter(this.id, isInit === true)
  }

  filterMobile () {
    this.isSelected(true)
    this.setSubcategories()
    this.container.onCatMobileFilter(this.id)
  }

  filterOnCheck () {
    this.setSubcategories()
    this.container.onCatFilter(this.id)
    return true
  }

  setSubcategories () {
    this.subCategories().forEach((x) => {
      x.isSelected(this.isSelected())
    })
  }
}

class SubCatFilter {
  constructor (data) {
    this.id = data.key
    this.name = data.name
    this.isSelected = ko.observable(false)
  }
}

export default class FindHelpByClientGroup extends FindHelp {
  constructor (pageSize = 25) {
    super([{
      qsKey: 'catIds',
      getValue: () => {
        const selectedCatFilters = this.catFilters().filter((c) => c.id !== undefined && c.isSelected()).map((c) => c.id)
        return selectedCatFilters && selectedCatFilters.length
          ? selectedCatFilters.join(',')
          : undefined
      }
    },
    {
      qsKey: 'subCatIds',
      getValue: () => {
        var categories = this.catFilters().filter((c) => c.id !== undefined && c.isSelected())
        if (categories && categories.length) {
          const selectedSubCatFilters = categories.map((c) => c.subCategories())
                                                  .reduce(function (a, b) { return a.concat(b) })
                                                  .map((sc) => sc.id)
          return selectedSubCatFilters && selectedSubCatFilters.length
                  ? selectedSubCatFilters.join(',')
                  : undefined
        }
        return undefined
      }
    }])

    this.allItems = ko.observableArray([])
    this.allOriginalItems = ko.observableArray([])
    this.isLoaded = false
    this.pageSize = pageSize
    this.pageIndex = ko.observable(0)
    this.totalItems = ko.observable(0)
    this.hasMorePages = ko.computed(() => (this.pageIndex() + this.pageSize) < this.totalItems(), this)
    this.catFilters = ko.computed(this.getCatFilters, this)

    this.shouldShowCatFilter = ko.computed(() => {
      return this.catFilters().length > 1
    }, this)

    const postcodeInQuerystring = querystring.parameter('postcode')
    if (postcodeInQuerystring) {
      this.proximitySearch.postcode(postcodeInQuerystring)
      this.proximitySearch.search()
    }

    if (this.proximitySearch.hasCoords() && !this.isLoaded) {
      this.onProximitySearch(false, true)
    }

    browser.setOnHistoryPop((e) => {
      this.onBrowserHistoryBack(this, e)
    })
  }

  loadMore () {
    this.pageIndex(this.pageIndex() + this.pageSize)
    this.onProximitySearch(true, false)
  }

  clearCategoriesFilter () {
    this.catFilters().forEach((x) => {
      x.isSelected(false)
      x.setSubcategories()
    })
  }

  initMoblileDropdown () {
    listToDropdown.init(
      () => {},
      (event) => {
        this.onCatFilterByName(event.target.value)
      }
    )
  }

  onProximitySearch (isLoadMore, isInit) {
    // We have static list of filter-categories. As result we need to initialize it once.
    if (!this.isLoaded) {
      this.initMoblileDropdown()
    }

    this.isLoaded = true
    browser.loading()
    var catIdsInQuerystring = querystring.parameter('catIds')
    var subCatIdsInQuerystring = querystring.parameter('subCatIds')
    var isEmptyQuery = false

    // If isInit == true it means we load page first time and need to get url parameters.
    if (isInit && this.catFilters().length > 1 && !catIdsInQuerystring) {
      if (!utils.isSmallscreen()) {
        this.catFilters()[1].isSelected(true)
        this.catFilters()[1].setSubcategories()
      } else if (utils.isSmallscreen()) {
        this.catFilters().forEach((x) => {
          x.isSelected(true)
        })
      }
      this.pushHistory()
      catIdsInQuerystring = querystring.parameter('catIds')
      subCatIdsInQuerystring = querystring.parameter('subCatIds')
      isEmptyQuery = true
    } else if (!catIdsInQuerystring) {
      this.pushHistory()
      isEmptyQuery = true
    }

    if (isLoadMore !== true) {
      this.pageIndex(0)
    }

    ajax
      .data(`${endpoints.serviceCategories}${this.proximitySearch.latitude}/${this.proximitySearch.longitude}?range=${this.proximitySearch.range()}&pageSize=${this.pageSize}&index=${this.pageIndex()}&clientGroup=${this.encodeClientGroupKey(this.clientGroup.clientGroupKey)}&catIds=${catIdsInQuerystring}&subCatIds=${subCatIdsInQuerystring}`)
      .then((result) => {
        if (isLoadMore !== true) {
          this.totalItems(result.data.total)
          this.allOriginalItems([])
        }

        this.allOriginalItems(this.allOriginalItems().concat(result.data.providers))
        this.allItems(getProvidersForListing(this.allOriginalItems()))
        this.items(this.allItems())

        if (isInit && !isEmptyQuery) {
          this.setCatFilter(catIdsInQuerystring)
        }

        browser.loaded()
      }, (_) => {
        browser.redirect('/500')
      })
  }

  onBrowserHistoryBack (thisDoobrey, e) {
    if (e.state && e.state) {
      this.clearCategoriesFilter()
      this.setCatFilter(e.state.catIds)
    }

    if (e.state && e.state.postcode !== thisDoobrey.proximitySearch.postcode()) {
      this.proximitySearch.postcode(e.state.postcode)
      this.proximitySearch.search()
    }
  }

  onCatFilter (catId, isInit = false) {
    if (!catId) {
      this.catFilters()
      .forEach((c) => c.isSelected(this.catFilters().find((f) => c.categoryId === undefined).isSelected()))
    } else {
      if (this.catFilters().filter((f) => f.id !== undefined).every((e) => e.isSelected() === true)) {
        this.catFilters().find((f) => f.id === undefined).isSelected(true)
      } else {
        this.catFilters().find((f) => f.id === undefined).isSelected(false)
      }
    }

    this.pushHistory()

    // If isInit == true it means we have just called onProximitySearch() and we don't need to call onProximitySearch() again.
    if (!isInit) {
      this.onProximitySearch(false, false)
    }
  }

  onCatFilterByName (catName) {
    const category = this.catFilters().find((c) => c.name === catName.trim())
    category.filterMobile()
  }

  onCatMobileFilter (catId) {
    let isSelected = !(catId !== undefined)
    this.catFilters()
      .filter((c) => c.id !== catId)
      .forEach((c) => c.isSelected(isSelected))
    this.pushHistory()
    this.onProximitySearch(false, false)
  }

  getCatFilters () {
    const showAll = new CatFilter({ name: 'Show All', isSelected: false }, this)
    const cats = [showAll, ...categories.map((c) => new CatFilter(c, this)).sortAsc('name')]
    return cats
  }

  setCatFilter (catIds) {
    var listIds = catIds.split(',')
    const selectedCatFilters = this.catFilters()
      .filter((c) => listIds.some((i) => i === c.id))

    if (selectedCatFilters) {
      selectedCatFilters.forEach((f) => f.filter(true))
    }
  }
}
<|endoftext|>


<commit_before>var chat = chat || {};

chat.User = Backbone.Model.extend({

});

chat.firebasePeopleRef = new Firebase(firebaseRootUrl + '/people');
chat.firebaseMessagesRef = new Firebase(firebaseRootUrl + '/messages');

chat.UserList = Backbone.Firebase.Collection.extend({
  model: chat.User,

  firebase: chat.firebasePeopleRef, // pass the ref here instead of string so we can listen for disconnect.

  getOnlineUsers: function() {
    return this.filter(function(user) {
      return user.get('isOnline');
    });
  }
});

chat.activeUsers  = new chat.UserList();

chat.UserView = Backbone.View.extend({
  tagName: 'li',

  template: _.template($('#user-presence-template').html()),

  initialize: function() {
    this.listenTo(this.model, 'change', this.render);
    this.listenTo(this.model, 'remove', this.remove);
  },

  render: function() {
    var self = this;

    R.request({
      method: 'get',
      content: {
        keys: this.model.get('id'),
        extras: 'shortUrl'
      },
      success: function(response) {
        var data = _.extend({
          'user': response.result[self.model.get('id')]
        });
        self.$el.html(self.template(data));
        self.$el.show();
      },
      error: function(response) {
        console.log('Unable to get user information for', self.model.get('id'));
      }
    });

    return this;
  }
});

chat.UserListView = Backbone.View.extend({
  el: '#user-list',

  initialize: function() {
    this.presenceStats = $('#presence-stats');

    // listen to when new users are added
    this.listenTo(chat.activeUsers, 'add', this.onListChanged);

    // and to when users change from online to offline
    this.listenTo(chat.activeUsers, 'change', this.onListChanged);

    //probably should render the activeUsers  on init so we have a starting point too.
    this.redraw(chat.activeUsers , {});
  },

  drawUser: function(model, collection, options) {
    if (model.get('isOnline')) {
      console.log("rendering user: ", model);
      var view = new chat.UserView({ model: model });
      this.$el.append(view.render().el);
    }
  },

  redraw: function(collection, options) {
    this.$el.empty();
    collection.each(this.drawUser, this);
  },

  onListChanged: function(model, options) {
    console.log("list changed with model: ", model);
    // this is inefficient, but we don't have another hook to the dom element.
    this.redraw(chat.activeUsers , {});
  }
});

/* chat messages! */
chat.Message = Backbone.Model.extend({
});

chat.MessageHistoryList = Backbone.Firebase.Collection.extend({
  model: chat.Message,

  firebase: chat.firebaseMessagesRef
});

chat.UserMessageView = Backbone.View.extend({
  tagName: 'li',

  template: _.template($('#chat-user-message-template').html()),

  render: function() {
    var user = chat.activeUsers.get(this.model.get('userKey'))
    if (user) {
      icon = user.attributes.icon
    } else {
        icon = ''
    }
    var data = {
      fullName: this.model.get('fullName'),
      icon: icon
    };
    this.$el.html(this.template(data));
    this.$el.find('.chat-message').text(this.model.get('message'));
    this.$el.show();

    return this;
  }
});

chat.NewTrackMessageView = Backbone.View.extend({
  tagName: 'li',

  template: _.template($('#chat-new-track-template').html()),

  render: function() {
    var data = {
      title: this.model.get('title'),
      artist: this.model.get('artist'),
      iconUrl: this.model.get('iconUrl'),
      trackUrl: this.model.get('trackUrl'),
      trackKey: this.model.get('trackKey')
    };
    this.$el.html(this.template(data));
    this.$el.show();

    return this;
  }
});

chat.MessagesView = Backbone.View.extend({
  el: '.chat-messages',

  initialize: function() {
    // there's probably a better way to do this with inheritance...
    this.listenTo(chat.messageHistory, 'add', this.onMessageAdded);

    console.log('chat view initialized');
  },

  onMessageAdded: function(model, options) {
    var messageType = model.get('type');
    if (messageType == 'User') {
      var messageView = new chat.UserMessageView({ model: model });

      this.$el.append(messageView.render().el);
      this.el.parentElement.scrollTop = this.el.parentElement.scrollHeight;

    } else if (messageType == 'NewTrack') {
      var trackView = new chat.NewTrackMessageView({ model: model });

      this.$el.append(trackView.render().el);
      this.el.parentElement.scrollTop = this.el.parentElement.scrollHeight;
    }
  }
});

R.ready(function() {
  chat.currentUser = R.currentUser;

  var userKey = R.currentUser.get('key');

  // add current user to activeUsers list, if they're not already
  var user = chat.activeUsers.get(userKey);
  if (user === undefined) {
    chat.activeUsers.add({
      id: userKey,
      isOnline: true,
      icon: R.currentUser.get('icon'),
      fullName: R.currentUser.get('firstName') + ' ' + R.currentUser.get('lastName')
    });
    console.log("added user ", chat.activeUsers.get(userKey), " to chat");
  }

  var isOnlineRef = chat.activeUsers.firebase.child(userKey).child('isOnline');
  console.log('online presence:', isOnlineRef.toString());

  // Mark yourself as offline on disconnect
  isOnlineRef.onDisconnect().set(false);

  //Mark myself as online on reconnect
  var connectedRef = new Firebase("https://rdiodj.firebaseio.com/.info/connected");
  connectedRef.on("value", function(snap) {
    if (snap.val() === true) {
      isOnlineRef.set(true);
    }
  });
  // Mark yourself as online
  isOnlineRef.set(true);

  // draw user list view (after marking yourself as online)
  var userListView = new chat.UserListView();

  // Set up chat

  // user chat entry
  var chatEntryText = $('.chat-entry-text');
  chatEntryText.keypress(function(e) {
    if (e.keyCode == 13) { // listen for enter key
      var message = chatEntryText.val();
      chat.sendMessage(message);
      chatEntryText.val('');
    }
  });

  // we set up track change messages in rdiodj.js
  chat.messageHistory = new chat.MessageHistoryList();
  var chatView = new chat.MessagesView();
});

chat.sendMessage = function(message) {
  var fullName = chat.currentUser.get('firstName') + ' ' + chat.currentUser.get('lastName');
  var messageData = {
    type: 'User',
    fullName: fullName,
    userKey: chat.currentUser.get('key'),
    message: message,
    timestamp: (new Date()).toISOString()
  };

  chat.messageHistory.add(messageData);
}
<commit_msg>Revert "Prevent ghost users"<commit_after>var chat = chat || {};

chat.User = Backbone.Model.extend({

});

chat.firebasePeopleRef = new Firebase(firebaseRootUrl + '/people');
chat.firebaseMessagesRef = new Firebase(firebaseRootUrl + '/messages');

chat.UserList = Backbone.Firebase.Collection.extend({
  model: chat.User,

  firebase: chat.firebasePeopleRef, // pass the ref here instead of string so we can listen for disconnect.

  getOnlineUsers: function() {
    return this.filter(function(user) {
      return user.get('isOnline');
    });
  }
});

chat.activeUsers  = new chat.UserList();

chat.UserView = Backbone.View.extend({
  tagName: 'li',

  template: _.template($('#user-presence-template').html()),

  initialize: function() {
    this.listenTo(this.model, 'change', this.render);
    this.listenTo(this.model, 'remove', this.remove);
  },

  render: function() {
    var self = this;

    R.request({
      method: 'get',
      content: {
        keys: this.model.get('id'),
        extras: 'shortUrl'
      },
      success: function(response) {
        var data = _.extend({
          'user': response.result[self.model.get('id')]
        });
        self.$el.html(self.template(data));
        self.$el.show();
      },
      error: function(response) {
        console.log('Unable to get user information for', self.model.get('id'));
      }
    });

    return this;
  }
});

chat.UserListView = Backbone.View.extend({
  el: '#user-list',

  initialize: function() {
    this.presenceStats = $('#presence-stats');

    // listen to when new users are added
    this.listenTo(chat.activeUsers, 'add', this.onListChanged);

    // and to when users change from online to offline
    this.listenTo(chat.activeUsers, 'change', this.onListChanged);

    //probably should render the activeUsers  on init so we have a starting point too.
    this.redraw(chat.activeUsers , {});
  },

  drawUser: function(model, collection, options) {
    if (model.get('isOnline')) {
      console.log("rendering user: ", model);
      var view = new chat.UserView({ model: model });
      this.$el.append(view.render().el);
    }
  },

  redraw: function(collection, options) {
    this.$el.empty();
    collection.each(this.drawUser, this);
  },

  onListChanged: function(model, options) {
    console.log("list changed with model: ", model);
    // this is inefficient, but we don't have another hook to the dom element.
    this.redraw(chat.activeUsers , {});
  }
});

/* chat messages! */
chat.Message = Backbone.Model.extend({
});

chat.MessageHistoryList = Backbone.Firebase.Collection.extend({
  model: chat.Message,

  firebase: chat.firebaseMessagesRef
});

chat.UserMessageView = Backbone.View.extend({
  tagName: 'li',

  template: _.template($('#chat-user-message-template').html()),

  render: function() {
    var user = chat.activeUsers.get(this.model.get('userKey'))
    if (user) {
      icon = user.attributes.icon
    } else {
        icon = ''
    }
    var data = {
      fullName: this.model.get('fullName'),
      icon: icon
    };
    this.$el.html(this.template(data));
    this.$el.find('.chat-message').text(this.model.get('message'));
    this.$el.show();

    return this;
  }
});

chat.NewTrackMessageView = Backbone.View.extend({
  tagName: 'li',

  template: _.template($('#chat-new-track-template').html()),

  render: function() {
    var data = {
      title: this.model.get('title'),
      artist: this.model.get('artist'),
      iconUrl: this.model.get('iconUrl'),
      trackUrl: this.model.get('trackUrl'),
      trackKey: this.model.get('trackKey')
    };
    this.$el.html(this.template(data));
    this.$el.show();

    return this;
  }
});

chat.MessagesView = Backbone.View.extend({
  el: '.chat-messages',

  initialize: function() {
    // there's probably a better way to do this with inheritance...
    this.listenTo(chat.messageHistory, 'add', this.onMessageAdded);

    console.log('chat view initialized');
  },

  onMessageAdded: function(model, options) {
    var messageType = model.get('type');
    if (messageType == 'User') {
      var messageView = new chat.UserMessageView({ model: model });

      this.$el.append(messageView.render().el);
      this.el.parentElement.scrollTop = this.el.parentElement.scrollHeight;

    } else if (messageType == 'NewTrack') {
      var trackView = new chat.NewTrackMessageView({ model: model });

      this.$el.append(trackView.render().el);
      this.el.parentElement.scrollTop = this.el.parentElement.scrollHeight;
    }
  }
});

R.ready(function() {
  chat.currentUser = R.currentUser;

  var userKey = R.currentUser.get('key');

  // add current user to activeUsers list, if they're not already
  var user = chat.activeUsers.get(userKey);
  if (user === undefined) {
    chat.activeUsers.add({
      id: userKey,
      isOnline: true,
      icon: R.currentUser.get('icon'),
      fullName: R.currentUser.get('firstName') + ' ' + R.currentUser.get('lastName')
    });
    console.log("added user ", chat.activeUsers.get(userKey), " to chat");
  }

  var isOnlineRef = chat.activeUsers.firebase.child(userKey).child('isOnline');
  console.log('online presence:', isOnlineRef.toString());

  // Mark yourself as offline on disconnect
  isOnlineRef.onDisconnect().set(false);

  // Mark yourself as online
  isOnlineRef.set(true);

  // draw user list view (after marking yourself as online)
  var userListView = new chat.UserListView();

  // Set up chat

  // user chat entry
  var chatEntryText = $('.chat-entry-text');
  chatEntryText.keypress(function(e) {
    if (e.keyCode == 13) { // listen for enter key
      var message = chatEntryText.val();
      chat.sendMessage(message);
      chatEntryText.val('');
    }
  });

  // we set up track change messages in rdiodj.js
  chat.messageHistory = new chat.MessageHistoryList();
  var chatView = new chat.MessagesView();
});

chat.sendMessage = function(message) {
  var fullName = chat.currentUser.get('firstName') + ' ' + chat.currentUser.get('lastName');
  var messageData = {
    type: 'User',
    fullName: fullName,
    userKey: chat.currentUser.get('key'),
    message: message,
    timestamp: (new Date()).toISOString()
  };

  chat.messageHistory.add(messageData);
}
<|endoftext|>


<commit_before>//= require jquery
//= require gl_field_errors

((global) => {
  fixture.preload('gl_field_errors.html');

  describe('GL Style Field Errors', function() {
    beforeEach(function() {
      fixture.load('gl_field_errors.html');
      const $form = this.$form = $('form.show-gl-field-errors');
      this.fieldErrors = new global.GlFieldErrors($form);
    });

    it('should properly initialize the form', function() {
      expect(this.$form).toBeDefined();
      expect(this.$form.length).toBe(1);
      expect(this.fieldErrors).toBeDefined();
      const inputs = this.fieldErrors.state.inputs;
      expect(inputs.length).toBe(5);
    });

    it('should ignore elements with custom error handling', function() {
      const customErrorFlag = 'no-gl-field-errors';
      const customErrorElem = $(`.${customErrorFlag}`);

      expect(customErrorElem.length).toBe(1);

      const customErrors = this.fieldErrors.state.inputs.filter((input) => {
       return input.inputElement.hasClass(customErrorFlag);
      });
      expect(customErrors.length).toBe(0);
    });

    it('should not show any errors before submit attempt', function() {
      this.$form.find('.email').val('not-a-valid-email').keyup();
      this.$form.find('.text-required').val('').keyup();
      this.$form.find('.alphanumberic').val('?---*').keyup();

      const errorsShown = this.$form.find('.gl-field-error-outline');
      expect(errorsShown.length).toBe(0);
    });

    it('should show errors when input valid is submitted', function() {
      this.$form.find('.email').val('not-a-valid-email').keyup();
      this.$form.find('.text-required').val('').keyup();
      this.$form.find('.alphanumberic').val('?---*').keyup();

      this.$form.submit();

      const errorsShown = this.$form.find('.gl-field-error-outline');
      expect(errorsShown.length).toBe(4);
    });

    it('should properly track validity state on input after invalid submission attempt', function() {
      this.$form.submit();

      const emailInputModel = this.fieldErrors.state.inputs[1];
      const fieldState = emailInputModel.state;
      const emailInputElement = emailInputModel.inputElement;

      // No input
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(true);
      expect(fieldState.valid).toBe(false);

      // Then invalid input
      emailInputElement.val('not-a-valid-email').keyup();
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(false);

      // Then valid input
      emailInputElement.val('email@gitlab.com').keyup();
      expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(true);

      // Then invalid input
      emailInputElement.val('not-a-valid-email').keyup();
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(false);

      // Then empty input
      emailInputElement.val('').keyup();
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(true);
      expect(fieldState.valid).toBe(false);

      // Then valid input
      emailInputElement.val('email@gitlab.com').keyup();
      expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(true);
    });

    it('should properly infer error messages', function() {
      this.$form.submit();
      const trackedInputs = this.fieldErrors.state.inputs;
      const inputHasTitle = trackedInputs[1];
      const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error');
      const inputNoTitle = trackedInputs[2];
      const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error');

      expect(noTitleErrorElem.text()).toBe('This field is required.');
      expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.');
    });

  });

})(window.gl || (window.gl = {}));
<commit_msg>Update gl_field_error tests for better input filtering.<commit_after>//= require jquery
//= require gl_field_errors

((global) => {
  fixture.preload('gl_field_errors.html');

  describe('GL Style Field Errors', function() {
    beforeEach(function() {
      fixture.load('gl_field_errors.html');
      const $form = this.$form = $('form.show-gl-field-errors');
      this.fieldErrors = new global.GlFieldErrors($form);
    });

    it('should select the correct input elements', function() {
      expect(this.$form).toBeDefined();
      expect(this.$form.length).toBe(1);
      expect(this.fieldErrors).toBeDefined();
      const inputs = this.fieldErrors.state.inputs;
      expect(inputs.length).toBe(4);
    });

    it('should ignore elements with custom error handling', function() {
      const customErrorFlag = 'no-gl-field-errors';
      const customErrorElem = $(`.${customErrorFlag}`);

      expect(customErrorElem.length).toBe(1);

      const customErrors = this.fieldErrors.state.inputs.filter((input) => {
       return input.inputElement.hasClass(customErrorFlag);
      });
      expect(customErrors.length).toBe(0);
    });

    it('should not show any errors before submit attempt', function() {
      this.$form.find('.email').val('not-a-valid-email').keyup();
      this.$form.find('.text-required').val('').keyup();
      this.$form.find('.alphanumberic').val('?---*').keyup();

      const errorsShown = this.$form.find('.gl-field-error-outline');
      expect(errorsShown.length).toBe(0);
    });

    it('should show errors when input valid is submitted', function() {
      this.$form.find('.email').val('not-a-valid-email').keyup();
      this.$form.find('.text-required').val('').keyup();
      this.$form.find('.alphanumberic').val('?---*').keyup();

      this.$form.submit();

      const errorsShown = this.$form.find('.gl-field-error-outline');
      expect(errorsShown.length).toBe(4);
    });

    it('should properly track validity state on input after invalid submission attempt', function() {
      this.$form.submit();

      const emailInputModel = this.fieldErrors.state.inputs[1];
      const fieldState = emailInputModel.state;
      const emailInputElement = emailInputModel.inputElement;

      // No input
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(true);
      expect(fieldState.valid).toBe(false);

      // Then invalid input
      emailInputElement.val('not-a-valid-email').keyup();
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(false);

      // Then valid input
      emailInputElement.val('email@gitlab.com').keyup();
      expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(true);

      // Then invalid input
      emailInputElement.val('not-a-valid-email').keyup();
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(false);

      // Then empty input
      emailInputElement.val('').keyup();
      expect(emailInputElement).toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(true);
      expect(fieldState.valid).toBe(false);

      // Then valid input
      emailInputElement.val('email@gitlab.com').keyup();
      expect(emailInputElement).not.toHaveClass('gl-field-error-outline');
      expect(fieldState.empty).toBe(false);
      expect(fieldState.valid).toBe(true);
    });

    it('should properly infer error messages', function() {
      this.$form.submit();
      const trackedInputs = this.fieldErrors.state.inputs;
      const inputHasTitle = trackedInputs[1];
      const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error');
      const inputNoTitle = trackedInputs[2];
      const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error');

      expect(noTitleErrorElem.text()).toBe('This field is required.');
      expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.');
    });

  });

})(window.gl || (window.gl = {}));
<|endoftext|>


<commit_before>/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, padded-blocks, max-len */

require('~/gl_dropdown');
require('~/search_autocomplete');
require('~/lib/utils/common_utils');
require('~/lib/utils/type_utility');
require('vendor/fuzzaldrin-plus');

(function() {
  var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;

  widget = null;

  userId = 1;

  window.gon || (window.gon = {});

  window.gon.current_user_id = userId;

  dashboardIssuesPath = '/dashboard/issues';

  dashboardMRsPath = '/dashboard/merge_requests';

  projectIssuesPath = '/gitlab-org/gitlab-ce/issues';

  projectMRsPath = '/gitlab-org/gitlab-ce/merge_requests';

  groupIssuesPath = '/groups/gitlab-org/issues';

  groupMRsPath = '/groups/gitlab-org/merge_requests';

  projectName = 'GitLab Community Edition';

  groupName = 'Gitlab Org';

  // Add required attributes to body before starting the test.
  // section would be dashboard|group|project
  addBodyAttributes = function(section) {
    var $body;
    if (section == null) {
      section = 'dashboard';
    }
    $body = $('body');
    $body.removeAttr('data-page');
    $body.removeAttr('data-project');
    $body.removeAttr('data-group');
    switch (section) {
      case 'dashboard':
        return $body.data('page', 'root:index');
      case 'group':
        $body.data('page', 'groups:show');
        return $body.data('group', 'gitlab-org');
      case 'project':
        $body.data('page', 'projects:show');
        return $body.data('project', 'gitlab-ce');
    }
  };

  // Mock `gl` object in window for dashboard specific page. App code will need it.
  mockDashboardOptions = function() {
    window.gl || (window.gl = {});
    return window.gl.dashboardOptions = {
      issuesPath: dashboardIssuesPath,
      mrPath: dashboardMRsPath
    };
  };

  // Mock `gl` object in window for project specific page. App code will need it.
  mockProjectOptions = function() {
    window.gl || (window.gl = {});
    return window.gl.projectOptions = {
      'gitlab-ce': {
        issuesPath: projectIssuesPath,
        mrPath: projectMRsPath,
        projectName: projectName
      }
    };
  };

  mockGroupOptions = function() {
    window.gl || (window.gl = {});
    return window.gl.groupOptions = {
      'gitlab-org': {
        issuesPath: groupIssuesPath,
        mrPath: groupMRsPath,
        projectName: groupName
      }
    };
  };

  assertLinks = function(list, issuesPath, mrsPath) {
    var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
    issuesAssignedToMeLink = issuesPath + "/?assignee_id=" + userId;
    issuesIHaveCreatedLink = issuesPath + "/?author_id=" + userId;
    mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId;
    mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId;
    a1 = "a[href='" + issuesAssignedToMeLink + "']";
    a2 = "a[href='" + issuesIHaveCreatedLink + "']";
    a3 = "a[href='" + mrsAssignedToMeLink + "']";
    a4 = "a[href='" + mrsIHaveCreatedLink + "']";
    expect(list.find(a1).length).toBe(1);
    expect(list.find(a1).text()).toBe('Issues assigned to me');
    expect(list.find(a2).length).toBe(1);
    expect(list.find(a2).text()).toBe("Issues I've created");
    expect(list.find(a3).length).toBe(1);
    expect(list.find(a3).text()).toBe('Merge requests assigned to me');
    expect(list.find(a4).length).toBe(1);
    return expect(list.find(a4).text()).toBe("Merge requests I've created");
  };

  describe('Search autocomplete dropdown', function() {
    preloadFixtures('static/search_autocomplete.html.raw');
    beforeEach(function() {
      loadFixtures('static/search_autocomplete.html.raw');
      return widget = new gl.SearchAutocomplete;
    });
    it('should show Dashboard specific dropdown menu', function() {
      var list;
      addBodyAttributes();
      mockDashboardOptions();
      widget.searchInput.focus();
      list = widget.wrap.find('.dropdown-menu').find('ul');
      return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
    });
    it('should show Group specific dropdown menu', function() {
      var list;
      addBodyAttributes('group');
      mockGroupOptions();
      widget.searchInput.focus();
      list = widget.wrap.find('.dropdown-menu').find('ul');
      return assertLinks(list, groupIssuesPath, groupMRsPath);
    });
    it('should show Project specific dropdown menu', function() {
      var list;
      addBodyAttributes('project');
      mockProjectOptions();
      widget.searchInput.focus();
      list = widget.wrap.find('.dropdown-menu').find('ul');
      return assertLinks(list, projectIssuesPath, projectMRsPath);
    });
    it('should not show category related menu if there is text in the input', function() {
      var link, list;
      addBodyAttributes('project');
      mockProjectOptions();
      widget.searchInput.val('help');
      widget.searchInput.focus();
      list = widget.wrap.find('.dropdown-menu').find('ul');
      link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']";
      return expect(list.find(link).length).toBe(0);
    });
    return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
      var ENTER = 13;
      var DOWN = 40;
      addBodyAttributes();
      mockDashboardOptions(true);
      var submitSpy = spyOnEvent('form', 'submit');
      widget.searchInput.focus();
      widget.wrap.trigger($.Event('keydown', { which: DOWN }));
      var enterKeyEvent = $.Event('keydown', { which: ENTER });
      widget.searchInput.trigger(enterKeyEvent);
      // This does not currently catch failing behavior. For security reasons,
      // browsers will not trigger default behavior (form submit, in this
      // example) on JavaScript-created keypresses.
      expect(submitSpy).not.toHaveBeenTriggered();
      // Does a worse job at capturing the intent of the test, but works.
      expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
    });
  });

}).call(this);
<commit_msg>bypass Chrome "focus" event bugs and prevent Turbolinks from triggering<commit_after>/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, padded-blocks, max-len */

require('~/gl_dropdown');
require('~/search_autocomplete');
require('~/lib/utils/common_utils');
require('~/lib/utils/type_utility');
require('vendor/fuzzaldrin-plus');

(function() {
  var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;

  widget = null;

  userId = 1;

  window.gon || (window.gon = {});

  window.gon.current_user_id = userId;

  dashboardIssuesPath = '/dashboard/issues';

  dashboardMRsPath = '/dashboard/merge_requests';

  projectIssuesPath = '/gitlab-org/gitlab-ce/issues';

  projectMRsPath = '/gitlab-org/gitlab-ce/merge_requests';

  groupIssuesPath = '/groups/gitlab-org/issues';

  groupMRsPath = '/groups/gitlab-org/merge_requests';

  projectName = 'GitLab Community Edition';

  groupName = 'Gitlab Org';

  // Add required attributes to body before starting the test.
  // section would be dashboard|group|project
  addBodyAttributes = function(section) {
    var $body;
    if (section == null) {
      section = 'dashboard';
    }
    $body = $('body');
    $body.removeAttr('data-page');
    $body.removeAttr('data-project');
    $body.removeAttr('data-group');
    switch (section) {
      case 'dashboard':
        return $body.data('page', 'root:index');
      case 'group':
        $body.data('page', 'groups:show');
        return $body.data('group', 'gitlab-org');
      case 'project':
        $body.data('page', 'projects:show');
        return $body.data('project', 'gitlab-ce');
    }
  };

  // Mock `gl` object in window for dashboard specific page. App code will need it.
  mockDashboardOptions = function() {
    window.gl || (window.gl = {});
    return window.gl.dashboardOptions = {
      issuesPath: dashboardIssuesPath,
      mrPath: dashboardMRsPath
    };
  };

  // Mock `gl` object in window for project specific page. App code will need it.
  mockProjectOptions = function() {
    window.gl || (window.gl = {});
    return window.gl.projectOptions = {
      'gitlab-ce': {
        issuesPath: projectIssuesPath,
        mrPath: projectMRsPath,
        projectName: projectName
      }
    };
  };

  mockGroupOptions = function() {
    window.gl || (window.gl = {});
    return window.gl.groupOptions = {
      'gitlab-org': {
        issuesPath: groupIssuesPath,
        mrPath: groupMRsPath,
        projectName: groupName
      }
    };
  };

  assertLinks = function(list, issuesPath, mrsPath) {
    var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
    issuesAssignedToMeLink = issuesPath + "/?assignee_id=" + userId;
    issuesIHaveCreatedLink = issuesPath + "/?author_id=" + userId;
    mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId;
    mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId;
    a1 = "a[href='" + issuesAssignedToMeLink + "']";
    a2 = "a[href='" + issuesIHaveCreatedLink + "']";
    a3 = "a[href='" + mrsAssignedToMeLink + "']";
    a4 = "a[href='" + mrsIHaveCreatedLink + "']";
    expect(list.find(a1).length).toBe(1);
    expect(list.find(a1).text()).toBe('Issues assigned to me');
    expect(list.find(a2).length).toBe(1);
    expect(list.find(a2).text()).toBe("Issues I've created");
    expect(list.find(a3).length).toBe(1);
    expect(list.find(a3).text()).toBe('Merge requests assigned to me');
    expect(list.find(a4).length).toBe(1);
    return expect(list.find(a4).text()).toBe("Merge requests I've created");
  };

  describe('Search autocomplete dropdown', function() {
    preloadFixtures('static/search_autocomplete.html.raw');
    beforeEach(function() {
      loadFixtures('static/search_autocomplete.html.raw');
      widget = new gl.SearchAutocomplete;
      // Prevent turbolinks from triggering within gl_dropdown
      spyOn(window.Turbolinks, 'visit').and.returnValue(true);
    });
    it('should show Dashboard specific dropdown menu', function() {
      var list;
      addBodyAttributes();
      mockDashboardOptions();
      widget.searchInput.triggerHandler('focus');
      list = widget.wrap.find('.dropdown-menu').find('ul');
      return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
    });
    it('should show Group specific dropdown menu', function() {
      var list;
      addBodyAttributes('group');
      mockGroupOptions();
      widget.searchInput.triggerHandler('focus');
      list = widget.wrap.find('.dropdown-menu').find('ul');
      return assertLinks(list, groupIssuesPath, groupMRsPath);
    });
    it('should show Project specific dropdown menu', function() {
      var list;
      addBodyAttributes('project');
      mockProjectOptions();
      widget.searchInput.triggerHandler('focus');
      list = widget.wrap.find('.dropdown-menu').find('ul');
      return assertLinks(list, projectIssuesPath, projectMRsPath);
    });
    it('should not show category related menu if there is text in the input', function() {
      var link, list;
      addBodyAttributes('project');
      mockProjectOptions();
      widget.searchInput.val('help');
      widget.searchInput.triggerHandler('focus');
      list = widget.wrap.find('.dropdown-menu').find('ul');
      link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']";
      return expect(list.find(link).length).toBe(0);
    });
    return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
      var ENTER = 13;
      var DOWN = 40;
      addBodyAttributes();
      mockDashboardOptions(true);
      var submitSpy = spyOnEvent('form', 'submit');
      widget.searchInput.triggerHandler('focus');
      widget.wrap.trigger($.Event('keydown', { which: DOWN }));
      var enterKeyEvent = $.Event('keydown', { which: ENTER });
      widget.searchInput.trigger(enterKeyEvent);
      // This does not currently catch failing behavior. For security reasons,
      // browsers will not trigger default behavior (form submit, in this
      // example) on JavaScript-created keypresses.
      expect(submitSpy).not.toHaveBeenTriggered();
      // Does a worse job at capturing the intent of the test, but works.
      expect(enterKeyEvent.isDefaultPrevented()).toBe(true);
    });
  });

}).call(this);
<|endoftext|>


<commit_before>/**
 * @module Lyria
 */
define('lyria/log', ['root'], function(root) {
  'use strict';

  /**
   * @class Log
   */
  var Log = (function() {

    var Log = {};

    Log.connector = null;

    Log.plugins = {};

    Log.plugins.console = {
      e: function() {
        if (root.console && root.console.error) {
          return root.console.error.apply(console, arguments);
        }
      },
      w: function() {
        if (root.console && root.console.warn) {
          return root.console.warn.apply(console, arguments);
        }
      },
      i: function() {
        if (root.console && root.console.info) {
          return root.console.info.apply(console, arguments);
        }
      },
      d: function() {
        if (root.console && root.console.log) {
          return root.console.log.apply(console, arguments);
        }
      },
      v: function() {
        if (root.console && root.console.log) {
          return root.console.log.apply(console, arguments);
        }
      }
    };

    Log.connector = Log.plugins.console;

    Log.logLevelMap = {
      'error': ['e'],
      'warn': ['w', 'e'],
      'info': ['i', 'w', 'e'],
      'debug': ['d', 'i', 'w', 'e'],
      'verbose': ['v', 'd', 'i', 'w', 'e']
    };

    Log.logLevel = 'verbose';

    var logFunctions = ['v', 'd', 'i', 'w', 'e'];

    for (var i = 0, j = logFunctions.length; i < j; i++) {

      (function(iterator) {
        Log[iterator] = function() {
          if (Log.logLevelMap[Log.logLevel].indexOf(iterator) >= 0) {
            Log.connector[iterator].apply(this, arguments);
          }
        };
      })(logFunctions[i]);

    }

    return Log;

  })();

  // Map shorthand functions to Log.i
  root.log = root.out = Log.i;

  return Log;
});<commit_msg>lyria/log is now an anonymous module<commit_after>/**
 * @module Lyria
 */
define(['root'], function(root) {
  'use strict';

  /**
   * @class Log
   */
  var Log = (function() {

    var Log = {};

    Log.connector = null;

    Log.plugins = {};

    Log.plugins.console = {
      e: function() {
        if (root.console && root.console.error) {
          return root.console.error.apply(console, arguments);
        }
      },
      w: function() {
        if (root.console && root.console.warn) {
          return root.console.warn.apply(console, arguments);
        }
      },
      i: function() {
        if (root.console && root.console.info) {
          return root.console.info.apply(console, arguments);
        }
      },
      d: function() {
        if (root.console && root.console.log) {
          return root.console.log.apply(console, arguments);
        }
      },
      v: function() {
        if (root.console && root.console.log) {
          return root.console.log.apply(console, arguments);
        }
      }
    };

    Log.connector = Log.plugins.console;

    Log.logLevelMap = {
      'error': ['e'],
      'warn': ['w', 'e'],
      'info': ['i', 'w', 'e'],
      'debug': ['d', 'i', 'w', 'e'],
      'verbose': ['v', 'd', 'i', 'w', 'e']
    };

    Log.logLevel = 'verbose';

    var logFunctions = ['v', 'd', 'i', 'w', 'e'];

    for (var i = 0, j = logFunctions.length; i < j; i++) {

      (function(iterator) {
        Log[iterator] = function() {
          if (Log.logLevelMap[Log.logLevel].indexOf(iterator) >= 0) {
            Log.connector[iterator].apply(this, arguments);
          }
        };
      })(logFunctions[i]);

    }

    return Log;

  })();

  // Map shorthand functions to Log.i
  root.log = root.out = Log.i;

  return Log;
});<|endoftext|>