<commit_before>
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, Blob, Worker */

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

    var CommandManager  = require("command/CommandManager");
    var Commands        = require("command/Commands");
    var async           = require("filesystem/impls/filer/lib/async");
    var StartupState    = require("bramble/StartupState");
    var JSZip           = require("thirdparty/jszip/dist/jszip.min");
    var FileSystemCache = require("filesystem/impls/filer/FileSystemCache");
    var Filer           = require("filesystem/impls/filer/BracketsFiler");
    var saveAs          = require("thirdparty/FileSaver");
    var Buffer          = Filer.Buffer;
    var Path            = Filer.Path;
    var fs              = Filer.fs();

    // Mac and Windows clutter zip files with extra files/folders we don't need
    function skipFile(filename) {
        var basename = Path.basename(filename);

        // Skip OS X additions we don't care about in the browser fs
        if(/^__MACOSX\//.test(filename)) {
            // http://superuser.com/questions/104500/what-is-macosx-folder
            return true;
        }
        if(basename === ".DS_Store") {
            // https://en.wikipedia.org/wiki/.DS_Store
            return true;
        }
        if(/^\._/.test(basename)) {
            // http://apple.stackexchange.com/questions/14980/why-are-dot-underscore-files-created-and-how-can-i-avoid-them
            return true;
        }

        // Skip Windows additions we don't care about in the browser fs
        if(/(Tt)humbs\.db/.test(basename)) {
            // https://en.wikipedia.org/wiki/Windows_thumbnail_cache
            return true;
        }
        if(basename === "desktop.ini") {
            // http://www.computerhope.com/issues/ch001060.htm
            return true;
        }

        // Include this file, don't skip.
        return false;
    }

    function _refreshFilesystem(callback) {
        // Update the file tree to show the new files
        CommandManager.execute(Commands.FILE_REFRESH).always(function() {
            // Generate Blob URLs for all the files we imported
            FileSystemCache.refresh(callback);
        });
    }

    // zipfile can be a path (string) to a zipfile, or raw binary data.
    function unzip(zipfile, options, callback) {
        var projectPrefix = StartupState.project("zipFilenamePrefix").replace(/\/?$/, "/");

        if(typeof options === 'function') {
            callback = options;
            options = {};
        }
        options = options || {};
        callback = callback || function(){};

        if(!zipfile) {
            callback(new Error("missing zipfile argument"));
            return;
        }

        var root = options.root || StartupState.project("root");
        var destination = Path.resolve(options.destination || root);

        function _unzip(data){
            // TODO: it would be great to move this bit to a worker.
            var archive = new JSZip(data);
            var filenames = [];

            archive.filter(function(relPath, file) {
                if(skipFile(file.name)) {
                    return;
                }

                var isDir = file.options.dir;
                var filename = removeThimbleProjectFolder(file.name);
                filenames.push({
                    absPath: Path.join(destination, filename),
                    isDirectory: isDir,
                    data: isDir ? null : new Buffer(file.asArrayBuffer())
                });
            });

            function removeThimbleProjectFolder(path) {
                // Nuke root folder `thimble-project/`, or whatever zip prefix was passed in
                // at startup, if exists so that project zip files can be re-imported without
                // adding an unnecessary folder.
                return path.replace(projectPrefix, "");
            }

            function decompress(path, callback) {
                var basedir = Path.dirname(path.absPath);

                if(path.isDirectory) {
                    fs.mkdirp(path.absPath, callback);
                } else {
                    // XXX: some zip files don't seem to be structured such that dirs
                    // get created before files. Create base dir if not there yet.
                    fs.stat(basedir, function(err, stats) {
                        if(err) {
                            if(err.code !== "ENOENT") {
                                return callback(err);
                            }

                            fs.mkdirp(basedir, function(err) {
                                if(err) {
                                    return callback(err);
                                }
                                fs.writeFile(path.absPath, path.data, callback);
                            });
                        } else {
                            fs.writeFile(path.absPath, path.data, callback);
                        }
                    });
                }
            }

            async.eachSeries(filenames, decompress, function(err) {
                if(err) {
                    return callback(err);
                }

                _refreshFilesystem(callback);
            });
        }

        if(typeof zipfile === "string") {
            fs.readFile(Path.resolve(root, zipfile), function(err, data) {
                if(err) {
                    return callback(err);
                }

                _unzip(data);
            });
        } else {
            // zipfile is raw zip data, process it directly
            _unzip(zipfile);
        }
    }

    // Zip specific file or folder structure, allowing an optional root name
    // folder to be passed in and used as the zip filename as well.  Use "thimble-project/"
    // or whatever is configured if a rootName isn't specified.
    function archive(path, rootName, callback) {
        var root = StartupState.project("root");
        var pathPrefix = path.replace(/\/?$/, "");

        if(typeof rootName === "function") {
            callback = rootName;
            rootName = null;
        }
        rootName = (rootName || StartupState.project("zipFilenamePrefix")).replace(/\/?$/, "");
        callback = callback || function() {};

        var zipFilename = rootName + ".zip";
        var rootFolder = rootName + "/";

        // TODO: we should try to move this to a worker
        var jszip = new JSZip();

        function toRelProjectPath(path) {
            // Make path relative within the zip, rooted in a `thimble-project/` dir
            return path.replace(pathPrefix, rootFolder);
        }

        function addFile(path, callback) {
            fs.readFile(path, {encoding: null}, function(err, data) {
                if(err) {
                    return callback(err);
                }

                jszip.file(toRelProjectPath(path), data.buffer, {binary: true});
                callback();
            });
        }

        function addDir(path, callback) {
            fs.readdir(path, function(err, list) {
                // Add the directory itself
                jszip.folder(toRelProjectPath(path));

                // Add all children of this dir, too
                async.eachSeries(list, function(entry, callback) {
                    add(Path.join(path, entry), callback);
                }, callback);
            });
        }

        function add(path, callback) {
            path = Path.resolve(root, path);

            fs.stat(path, function(err, stats) {
                if(err) {
                    return callback(err);
                }

                if(stats.type === "DIRECTORY") {
                    addDir(path, callback);
                } else {
                    addFile(path, callback);
                }
            });
        }

        add(path, function(err) {
            if(err) {
                return callback(err);
            }
            // Prepare folder for download
            var compressed = jszip.generate({type: 'arraybuffer'});
            var blob = new Blob([compressed], {type: "application/zip"});
            saveAs(blob, zipFilename);
            callback();
        });
    }

    function untar(tarArchive, options, callback) {
        if(typeof options === 'function') {
            callback = options;
            options = {};
        }
        options = options || {};
        callback = callback || function(){};

        var untarWorker = new Worker("thirdparty/bitjs/bitjs-untar.min.js");
        var root = options.root || StartupState.project("root");
        var pending = null;

        function extract(path, data, callback) {
            path = Path.resolve(root, path);
            var basedir = Path.dirname(path);

            if(skipFile(path)) {
                return callback();
            }

            fs.mkdirp(basedir, function(err) {
                if(err && err.code !== "EEXIST") {
                    return callback(err);
                }

                fs.writeFile(path, new Buffer(data), {encoding: null}, callback);
            });
        }

        function finish(err) {
            untarWorker.terminate();
            untarWorker = null;

            callback(err);
        }

        function writeCallback(err) {
            if(err) {
                console.error("[Bramble untar] couldn't extract file", err);
            }

            pending--;
            if(pending === 0) {
                _refreshFilesystem(finish);
            }
        }

        untarWorker.addEventListener("message", function(e) {
            var data = e.data;

            if(data.type === "progress" && pending === null) {
                // Set the total number of files we need to deal with so we know when we're done
                pending = data.totalFilesInArchive;
            } else if(data.type === "extract") {
                extract(data.unarchivedFile.filename, data.unarchivedFile.fileData, writeCallback);
            } else if(data.type === "error") {
                finish(new Error("[Bramble untar]: " + data.msg));
            }
        });

        untarWorker.postMessage({file: tarArchive.buffer});
    }

    exports.skipFile = skipFile;
    exports.archive = archive;
    exports.unzip = unzip;
    exports.untar = untar;
});
<commit_msg>Fix filesystem code used when unzipping so we auto resize images<commit_after>
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, Blob, Worker */

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

    var CommandManager  = require("command/CommandManager");
    var Commands        = require("command/Commands");
    var async           = require("filesystem/impls/filer/lib/async");
    var StartupState    = require("bramble/StartupState");
    var JSZip           = require("thirdparty/jszip/dist/jszip.min");
    var FileSystemCache = require("filesystem/impls/filer/FileSystemCache");
    var FileSystem      = require("filesystem/FileSystem");
    var Filer           = require("filesystem/impls/filer/BracketsFiler");
    var saveAs          = require("thirdparty/FileSaver");
    var Buffer          = Filer.Buffer;
    var Path            = Filer.Path;
    var fs              = Filer.fs();

    // Mac and Windows clutter zip files with extra files/folders we don't need
    function skipFile(filename) {
        var basename = Path.basename(filename);

        // Skip OS X additions we don't care about in the browser fs
        if(/^__MACOSX\//.test(filename)) {
            // http://superuser.com/questions/104500/what-is-macosx-folder
            return true;
        }
        if(basename === ".DS_Store") {
            // https://en.wikipedia.org/wiki/.DS_Store
            return true;
        }
        if(/^\._/.test(basename)) {
            // http://apple.stackexchange.com/questions/14980/why-are-dot-underscore-files-created-and-how-can-i-avoid-them
            return true;
        }

        // Skip Windows additions we don't care about in the browser fs
        if(/(Tt)humbs\.db/.test(basename)) {
            // https://en.wikipedia.org/wiki/Windows_thumbnail_cache
            return true;
        }
        if(basename === "desktop.ini") {
            // http://www.computerhope.com/issues/ch001060.htm
            return true;
        }

        // Include this file, don't skip.
        return false;
    }

    // We need to use the right level of abstraction in order to have images get resized.
    function writeBinaryFile(path, data, callback) {
        var file = FileSystem.getFileForPath(path);
        file.write(data, {encoding: null}, callback);
    }

    function _refreshFilesystem(callback) {
        // Update the file tree to show the new files
        CommandManager.execute(Commands.FILE_REFRESH).always(function() {
            // Generate Blob URLs for all the files we imported
            FileSystemCache.refresh(callback);
        });
    }

    // zipfile can be a path (string) to a zipfile, or raw binary data.
    function unzip(zipfile, options, callback) {
        var projectPrefix = StartupState.project("zipFilenamePrefix").replace(/\/?$/, "/");

        if(typeof options === 'function') {
            callback = options;
            options = {};
        }
        options = options || {};
        callback = callback || function(){};

        if(!zipfile) {
            callback(new Error("missing zipfile argument"));
            return;
        }

        var root = options.root || StartupState.project("root");
        var destination = Path.resolve(options.destination || root);

        function _unzip(data){
            // TODO: it would be great to move this bit to a worker.
            var archive = new JSZip(data);
            var filenames = [];

            archive.filter(function(relPath, file) {
                if(skipFile(file.name)) {
                    return;
                }

                var isDir = file.options.dir;
                var filename = removeThimbleProjectFolder(file.name);
                filenames.push({
                    absPath: Path.join(destination, filename),
                    isDirectory: isDir,
                    data: isDir ? null : new Buffer(file.asArrayBuffer())
                });
            });

            function removeThimbleProjectFolder(path) {
                // Nuke root folder `thimble-project/`, or whatever zip prefix was passed in
                // at startup, if exists so that project zip files can be re-imported without
                // adding an unnecessary folder.
                return path.replace(projectPrefix, "");
            }

            function decompress(path, callback) {
                var basedir = Path.dirname(path.absPath);

                if(path.isDirectory) {
                    fs.mkdirp(path.absPath, callback);
                } else {
                    // XXX: some zip files don't seem to be structured such that dirs
                    // get created before files. Create base dir if not there yet.
                    fs.stat(basedir, function(err, stats) {
                        if(err) {
                            if(err.code !== "ENOENT") {
                                return callback(err);
                            }

                            fs.mkdirp(basedir, function(err) {
                                if(err) {
                                    return callback(err);
                                }

                                writeBinaryFile(path.absPath, path.data, callback);
                            });
                        } else {
                            writeBinaryFile(path.absPath, path.data, callback);
                        }
                    });
                }
            }

            async.eachSeries(filenames, decompress, function(err) {
                if(err) {
                    return callback(err);
                }

                _refreshFilesystem(callback);
            });
        }

        if(typeof zipfile === "string") {
            fs.readFile(Path.resolve(root, zipfile), function(err, data) {
                if(err) {
                    return callback(err);
                }

                _unzip(data);
            });
        } else {
            // zipfile is raw zip data, process it directly
            _unzip(zipfile);
        }
    }

    // Zip specific file or folder structure, allowing an optional root name
    // folder to be passed in and used as the zip filename as well.  Use "thimble-project/"
    // or whatever is configured if a rootName isn't specified.
    function archive(path, rootName, callback) {
        var root = StartupState.project("root");
        var pathPrefix = path.replace(/\/?$/, "");

        if(typeof rootName === "function") {
            callback = rootName;
            rootName = null;
        }
        rootName = (rootName || StartupState.project("zipFilenamePrefix")).replace(/\/?$/, "");
        callback = callback || function() {};

        var zipFilename = rootName + ".zip";
        var rootFolder = rootName + "/";

        // TODO: we should try to move this to a worker
        var jszip = new JSZip();

        function toRelProjectPath(path) {
            // Make path relative within the zip, rooted in a `thimble-project/` dir
            return path.replace(pathPrefix, rootFolder);
        }

        function addFile(path, callback) {
            fs.readFile(path, {encoding: null}, function(err, data) {
                if(err) {
                    return callback(err);
                }

                jszip.file(toRelProjectPath(path), data.buffer, {binary: true});
                callback();
            });
        }

        function addDir(path, callback) {
            fs.readdir(path, function(err, list) {
                // Add the directory itself
                jszip.folder(toRelProjectPath(path));

                // Add all children of this dir, too
                async.eachSeries(list, function(entry, callback) {
                    add(Path.join(path, entry), callback);
                }, callback);
            });
        }

        function add(path, callback) {
            path = Path.resolve(root, path);

            fs.stat(path, function(err, stats) {
                if(err) {
                    return callback(err);
                }

                if(stats.type === "DIRECTORY") {
                    addDir(path, callback);
                } else {
                    addFile(path, callback);
                }
            });
        }

        add(path, function(err) {
            if(err) {
                return callback(err);
            }
            // Prepare folder for download
            var compressed = jszip.generate({type: 'arraybuffer'});
            var blob = new Blob([compressed], {type: "application/zip"});
            saveAs(blob, zipFilename);
            callback();
        });
    }

    function untar(tarArchive, options, callback) {
        if(typeof options === 'function') {
            callback = options;
            options = {};
        }
        options = options || {};
        callback = callback || function(){};

        var untarWorker = new Worker("thirdparty/bitjs/bitjs-untar.min.js");
        var root = options.root || StartupState.project("root");
        var pending = null;

        function extract(path, data, callback) {
            path = Path.resolve(root, path);
            var basedir = Path.dirname(path);

            if(skipFile(path)) {
                return callback();
            }

            fs.mkdirp(basedir, function(err) {
                if(err && err.code !== "EEXIST") {
                    return callback(err);
                }

                writeBinaryFile(path, new Buffer(data), callback);
            });
        }

        function finish(err) {
            untarWorker.terminate();
            untarWorker = null;

            callback(err);
        }

        function writeCallback(err) {
            if(err) {
                console.error("[Bramble untar] couldn't extract file", err);
            }

            pending--;
            if(pending === 0) {
                _refreshFilesystem(finish);
            }
        }

        untarWorker.addEventListener("message", function(e) {
            var data = e.data;

            if(data.type === "progress" && pending === null) {
                // Set the total number of files we need to deal with so we know when we're done
                pending = data.totalFilesInArchive;
            } else if(data.type === "extract") {
                extract(data.unarchivedFile.filename, data.unarchivedFile.fileData, writeCallback);
            } else if(data.type === "error") {
                finish(new Error("[Bramble untar]: " + data.msg));
            }
        });

        untarWorker.postMessage({file: tarArchive.buffer});
    }

    exports.skipFile = skipFile;
    exports.archive = archive;
    exports.unzip = unzip;
    exports.untar = untar;
});
<|endoftext|>


<commit_before>// JavaScript Document

// Scripts written by __gulp_init_author_name__ @ __gulp_init_author_company__

module.exports = {
    media(gulp, plugins, ran_tasks, on_error) {
        // task-specific plugins
        const imagemin = require("gulp-imagemin");
        const pngquant = require("imagemin-pngquant");

        // media task, compresses images, copies other media
        return new Promise ((resolve) => {
            // set media directory
            const media_directory = plugins.argv.dist ? global.settings.paths.dist : global.settings.paths.dev;

            // merge both steams back in to one
            gulp.src(global.settings.paths.src + "/**/*.{jpg,png,svg}")
                // prevent breaking on error
                .pipe(plugins.plumber({errorHandler: on_error}))
                // check if source is newer than destination
                .pipe(plugins.gulpif(!plugins.argv.dist, plugins.newer(media_directory)))
                // compress images
                .pipe(imagemin({
                    progressive: true,
                    svgoPlugins: [{cleanupIDs: false, removeViewBox: false}],
                    use:         [pngquant()],
                }))
                // output to compiled directory
                .pipe(gulp.dest(media_directory))
                // prevent breaking on error
                .pipe(plugins.plumber({errorHandler: on_error}))
                // notify that task is complete, if not part of default or watch
                .pipe(plugins.gulpif(gulp.seq.indexOf("media") > gulp.seq.indexOf("default"), plugins.notify({title: "Success!", message: "Media task complete!", onLast: true})))
                // push task to ran_tasks array
                .on("data", () => {
                    if (ran_tasks.indexOf("media") < 0) {
                        ran_tasks.push("media");
                    }
                })
                .on("end", () => {
                    resolve();
                });
        });
    }
};
<commit_msg>Ensure fonts get copied over properly in media task<commit_after>// JavaScript Document

// Scripts written by __gulp_init_author_name__ @ __gulp_init_author_company__

module.exports = {
    media(gulp, plugins, ran_tasks, on_error) {
        // task-specific plugins
        const IMAGEMIN = require("gulp-imagemin");
        const PNGQUANT = require("imagemin-pngquant");

        // media task, compresses images, copies other media
        return new Promise ((resolve) => {
            // set media directory
            const MEDIA_DIRECTORY = plugins.argv.dist ? global.settings.paths.dist : global.settings.paths.dev;

            // copy fonts
            const COPY_FONTS = gulp.src(global.settings.paths.src + "/assets/media/fonts/**/*.{otf,ttf,woff,woff2}")
                // prevent breaking on error
                .pipe(plugins.plumber({errorHandler: on_error}))
                // check if source is newer than destination
                .pipe(plugins.gulpif(!plugins.argv.dist, plugins.newer(MEDIA_DIRECTORY + "/assets/media/fonts", {extra: [MEDIA_DIRECTORY + "/assets/media/fonts/**/*.{otf,ttf,woff,woff2}"]})))
                // output to compiled directory
                .pipe(gulp.dest(MEDIA_DIRECTORY + "/assets/media/fonts"));

            // process images
            const PROCESS_IMAGES = gulp.src(global.settings.paths.src + "/**/*.{jpg,png,svg}")
                // prevent breaking on error
                .pipe(plugins.plumber({errorHandler: on_error}))
                // check if source is newer than destination
                .pipe(plugins.gulpif(!plugins.argv.dist, plugins.newer(MEDIA_DIRECTORY)))
                // compress images
                .pipe(IMAGEMIN({
                    progressive: true,
                    svgoPlugins: [{cleanupIDs: false, removeViewBox: false}],
                    use:         [PNGQUANT()],
                }))
                // output to compiled directory
                .pipe(gulp.dest(MEDIA_DIRECTORY));

            // merge both streams back in to one
            plugins.merge(COPY_FONTS, PROCESS_IMAGES)
                // prevent breaking on error
                .pipe(plugins.plumber({errorHandler: on_error}))
                // notify that task is complete, if not part of default or watch
                .pipe(plugins.gulpif(gulp.seq.indexOf("media") > gulp.seq.indexOf("default"), plugins.notify({title: "Success!", message: "Media task complete!", onLast: true})))
                // push task to ran_tasks array
                .on("data", () => {
                    if (ran_tasks.indexOf("media") < 0) {
                        ran_tasks.push("media");
                    }
                })
                .on("end", () => {
                    resolve();
                });
        });
    }
};
<|endoftext|>


<commit_before>import React from 'react';
import { PAYMENT_METHOD_TYPES, PAYMENT_TOKEN_STATUS } from 'proton-shared/lib/constants';
import { getTokenStatus, createToken } from 'proton-shared/lib/api/payments';
import { wait } from 'proton-shared/lib/helpers/promise';
import { c } from 'ttag';
import { PaymentVerificationModal } from 'react-components';
import { getHostname } from 'proton-shared/lib/helpers/url';

const {
    STATUS_PENDING,
    STATUS_CHARGEABLE,
    STATUS_FAILED,
    STATUS_CONSUMED,
    STATUS_NOT_SUPPORTED
} = PAYMENT_TOKEN_STATUS;

const { TOKEN, BITCOIN, CASH } = PAYMENT_METHOD_TYPES;

const DELAY_PULLING = 5000;
const DELAY_LISTENING = 1000;

/**
 * Recursive function to check token status
 * @param {Number} timer
 * @param {String} Token
 * @param {Object} api useApi
 * @param {AbortSignal} signal instance
 * @returns {Promise}
 */
const pull = async ({ timer = 0, Token, api, signal }) => {
    if (signal.aborted) {
        throw new Error(c('Error').t`Process aborted`);
    }

    if (timer > DELAY_PULLING * 30) {
        throw new Error(c('Error').t`Payment process cancelled`);
    }

    const { Status } = await api({ ...getTokenStatus(Token), signal });

    if (Status === STATUS_FAILED) {
        throw new Error(c('Error').t`Payment process failed`);
    }

    if (Status === STATUS_CONSUMED) {
        throw new Error(c('Error').t`Payment process consumed`);
    }

    if (Status === STATUS_NOT_SUPPORTED) {
        throw new Error(c('Error').t`Payment process not supported`);
    }

    if (Status === STATUS_CHARGEABLE) {
        return;
    }

    if (Status === STATUS_PENDING) {
        await wait(DELAY_PULLING);
        return pull({ Token, api, timer: timer + DELAY_PULLING });
    }

    throw new Error(c('Error').t`Unknown payment token status`);
};

/**
 * Initialize new tab and listen it
 * @param {String} Token from API
 * @param {Object} api useApi
 * @param {String} ApprovalURL from API
 * @param {String} ReturnHost from API
 * @param {AbortSignal} signal instance
 * @returns {Promise}
 */
export const process = ({ Token, api, ApprovalURL, ReturnHost, signal }) => {
    const tab = window.open(ApprovalURL);

    return new Promise((resolve, reject) => {
        let listen = false;

        const reset = () => {
            listen = false;
            window.removeEventListener('message', onMessage, false);
            signal.removeEventListener('abort', abort);
        };

        const listenTab = async () => {
            if (!listen) {
                return;
            }

            if (tab.closed) {
                reset();
                const error = new Error(c('Error').t`Tab closed`);
                error.tryAgain = true;
                return reject(error);
            }

            await wait(DELAY_LISTENING);
            return listenTab(tab);
        };

        const onMessage = (event) => {
            const origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object.

            if (getHostname(origin) !== getHostname(ReturnHost)) {
                return;
            }

            reset();
            tab.close();

            const { cancel } = event.data;

            if (cancel === '1') {
                return reject();
            }

            pull({ Token, api, signal })
                .then(resolve)
                .catch(reject);
        };

        const abort = () => {
            reset();
            tab.close();
            reject(new Error(c('Error').t`Process aborted`));
        };

        signal.addEventListener('abort', abort);
        window.addEventListener('message', onMessage, false);
        listen = true;
        listenTab();
    });
};

/**
 * Prepare parameters to be sent to API
 * @param {Object} params
 * @param {String} Token
 * @returns {Object}
 */
export const toParams = (params, Token) => {
    return {
        ...params,
        Payment: {
            Type: TOKEN,
            Details: {
                Token
            }
        }
    };
};

export const handlePaymentToken = async ({ params, api, createModal }) => {
    const { Payment, Amount, Currency, PaymentMethodID } = params;
    const { Type } = Payment || {};

    if (Amount === 0) {
        return params;
    }

    if ([CASH, BITCOIN, TOKEN].includes(Type)) {
        return params;
    }

    const { Token, Status, ApprovalURL, ReturnHost } = await api(
        createToken({
            Payment,
            Amount,
            Currency,
            PaymentMethodID
        })
    );

    if (Status === STATUS_CHARGEABLE) {
        return toParams(params, Token);
    }

    return new Promise((resolve, reject) => {
        createModal(
            <PaymentVerificationModal
                payment={Payment}
                params={params}
                returnHost={ReturnHost}
                approvalURL={ApprovalURL}
                token={Token}
                onSubmit={resolve}
                onClose={reject}
            />
        );
    });
};
<commit_msg>Hotfix - ReturnHost is already a host<commit_after>import React from 'react';
import { PAYMENT_METHOD_TYPES, PAYMENT_TOKEN_STATUS } from 'proton-shared/lib/constants';
import { getTokenStatus, createToken } from 'proton-shared/lib/api/payments';
import { wait } from 'proton-shared/lib/helpers/promise';
import { c } from 'ttag';
import { PaymentVerificationModal } from 'react-components';
import { getHostname } from 'proton-shared/lib/helpers/url';

const {
    STATUS_PENDING,
    STATUS_CHARGEABLE,
    STATUS_FAILED,
    STATUS_CONSUMED,
    STATUS_NOT_SUPPORTED
} = PAYMENT_TOKEN_STATUS;

const { TOKEN, BITCOIN, CASH } = PAYMENT_METHOD_TYPES;

const DELAY_PULLING = 5000;
const DELAY_LISTENING = 1000;

/**
 * Recursive function to check token status
 * @param {Number} timer
 * @param {String} Token
 * @param {Object} api useApi
 * @param {AbortSignal} signal instance
 * @returns {Promise}
 */
const pull = async ({ timer = 0, Token, api, signal }) => {
    if (signal.aborted) {
        throw new Error(c('Error').t`Process aborted`);
    }

    if (timer > DELAY_PULLING * 30) {
        throw new Error(c('Error').t`Payment process cancelled`);
    }

    const { Status } = await api({ ...getTokenStatus(Token), signal });

    if (Status === STATUS_FAILED) {
        throw new Error(c('Error').t`Payment process failed`);
    }

    if (Status === STATUS_CONSUMED) {
        throw new Error(c('Error').t`Payment process consumed`);
    }

    if (Status === STATUS_NOT_SUPPORTED) {
        throw new Error(c('Error').t`Payment process not supported`);
    }

    if (Status === STATUS_CHARGEABLE) {
        return;
    }

    if (Status === STATUS_PENDING) {
        await wait(DELAY_PULLING);
        return pull({ Token, api, timer: timer + DELAY_PULLING });
    }

    throw new Error(c('Error').t`Unknown payment token status`);
};

/**
 * Initialize new tab and listen it
 * @param {String} Token from API
 * @param {Object} api useApi
 * @param {String} ApprovalURL from API
 * @param {String} ReturnHost from API
 * @param {AbortSignal} signal instance
 * @returns {Promise}
 */
export const process = ({ Token, api, ApprovalURL, ReturnHost, signal }) => {
    const tab = window.open(ApprovalURL);

    return new Promise((resolve, reject) => {
        let listen = false;

        const reset = () => {
            listen = false;
            window.removeEventListener('message', onMessage, false);
            signal.removeEventListener('abort', abort);
        };

        const listenTab = async () => {
            if (!listen) {
                return;
            }

            if (tab.closed) {
                reset();
                const error = new Error(c('Error').t`Tab closed`);
                error.tryAgain = true;
                return reject(error);
            }

            await wait(DELAY_LISTENING);
            return listenTab(tab);
        };

        const onMessage = (event) => {
            const origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object.

            if (getHostname(origin) !== ReturnHost) {
                return;
            }

            reset();
            tab.close();

            const { cancel } = event.data;

            if (cancel === '1') {
                return reject();
            }

            pull({ Token, api, signal })
                .then(resolve)
                .catch(reject);
        };

        const abort = () => {
            reset();
            tab.close();
            reject(new Error(c('Error').t`Process aborted`));
        };

        signal.addEventListener('abort', abort);
        window.addEventListener('message', onMessage, false);
        listen = true;
        listenTab();
    });
};

/**
 * Prepare parameters to be sent to API
 * @param {Object} params
 * @param {String} Token
 * @returns {Object}
 */
export const toParams = (params, Token) => {
    return {
        ...params,
        Payment: {
            Type: TOKEN,
            Details: {
                Token
            }
        }
    };
};

export const handlePaymentToken = async ({ params, api, createModal }) => {
    const { Payment, Amount, Currency, PaymentMethodID } = params;
    const { Type } = Payment || {};

    if (Amount === 0) {
        return params;
    }

    if ([CASH, BITCOIN, TOKEN].includes(Type)) {
        return params;
    }

    const { Token, Status, ApprovalURL, ReturnHost } = await api(
        createToken({
            Payment,
            Amount,
            Currency,
            PaymentMethodID
        })
    );

    if (Status === STATUS_CHARGEABLE) {
        return toParams(params, Token);
    }

    return new Promise((resolve, reject) => {
        createModal(
            <PaymentVerificationModal
                payment={Payment}
                params={params}
                returnHost={ReturnHost}
                approvalURL={ApprovalURL}
                token={Token}
                onSubmit={resolve}
                onClose={reject}
            />
        );
    });
};
<|endoftext|>


<commit_before>/**
 * AccessibleBlockly
 *
 * Copyright 2016 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Angular2 Service that handles tree keyboard navigation.
 * This is a singleton service for the entire application.
 *
 * @author madeeha@google.com (Madeeha Ghori)
 */

blocklyApp.TreeService = ng.core
  .Class({
    constructor: function() {
      // Stores active descendant ids for each tree in the page.
      this.activeDescendantIds_ = {};
    },
    getToolboxTreeNode_: function() {
      return document.getElementById('blockly-toolbox-tree');
    },
    getWorkspaceToolbarButtonNodes_: function() {
      return Array.from(document.querySelectorAll(
          'button.blocklyWorkspaceToolbarButton'));
    },
    // Returns a list of all top-level workspace tree nodes on the page.
    getWorkspaceTreeNodes_: function() {
      return Array.from(document.querySelectorAll('ol.blocklyWorkspaceTree'));
    },
    // Returns a list of all top-level tree nodes on the page.
    getAllTreeNodes_: function() {
      var treeNodes = [this.getToolboxTreeNode_()];
      treeNodes = treeNodes.concat(this.getWorkspaceToolbarButtonNodes_());
      treeNodes = treeNodes.concat(this.getWorkspaceTreeNodes_());
      return treeNodes;
    },
    isTopLevelWorkspaceTree: function(treeId) {
      return this.getWorkspaceTreeNodes_().some(function(tree) {
        return tree.id == treeId;
      });
    },
    getNodeToFocusOnWhenTreeIsDeleted: function(deletedTreeId) {
      // This returns the node to focus on after the deletion happens.
      // We shift focus to the next tree (if it exists), otherwise we shift
      // focus to the previous tree.
      var trees = this.getAllTreeNodes_();
      for (var i = 0; i < trees.length; i++) {
        if (trees[i].id == deletedTreeId) {
          if (i + 1 < trees.length) {
            return trees[i + 1];
          } else if (i > 0) {
            return trees[i - 1];
          }
        }
      }

      return this.getToolboxTreeNode_();
    },
    focusOnCurrentTree_: function(treeId) {
      var trees = this.getAllTreeNodes_();
      for (var i = 0; i < trees.length; i++) {
        if (trees[i].id == treeId) {
          trees[i].focus();
          return true;
        }
      }
      return false;
    },
    focusOnNextTree_: function(treeId) {
      var trees = this.getAllTreeNodes_();
      for (var i = 0; i < trees.length - 1; i++) {
        if (trees[i].id == treeId) {
          trees[i + 1].focus();
          return true;
        }
      }
      return false;
    },
    focusOnPreviousTree_: function(treeId) {
      var trees = this.getAllTreeNodes_();
      for (var i = trees.length - 1; i > 0; i--) {
        if (trees[i].id == treeId) {
          trees[i - 1].focus();
          return true;
        }
      }
      return false;
    },
    getActiveDescId: function(treeId) {
      return this.activeDescendantIds_[treeId] || '';
    },
    unmarkActiveDesc_: function(activeDescId) {
      var activeDesc = document.getElementById(activeDescId);
      if (activeDesc) {
        activeDesc.classList.remove('blocklyActiveDescendant');
        activeDesc.setAttribute('aria-selected', 'false');
      }
    },
    markActiveDesc_: function(activeDescId) {
      var newActiveDesc = document.getElementById(activeDescId);
      newActiveDesc.classList.add('blocklyActiveDescendant');
      newActiveDesc.setAttribute('aria-selected', 'true');
    },
    // Runs the given function while preserving the focus and active descendant
    // for the given tree.
    runWhilePreservingFocus: function(func, treeId, optionalNewActiveDescId) {
      var oldDescId = this.getActiveDescId(treeId);
      var newDescId = optionalNewActiveDescId || oldDescId;
      this.unmarkActiveDesc_(oldDescId);
      func();

      // The timeout is needed in order to give the DOM time to stabilize
      // before setting the new active descendant, especially in cases like
      // pasteAbove().
      var that = this;
      setTimeout(function() {
        that.markActiveDesc_(newDescId);
        that.activeDescendantIds_[treeId] = newDescId;
        document.getElementById(treeId).focus();
      }, 0);
    },
    // Make a given node the active descendant of a given tree.
    setActiveDesc: function(newActiveDescId, treeId) {
      this.unmarkActiveDesc_(this.getActiveDescId(treeId));
      this.markActiveDesc_(newActiveDescId);
      this.activeDescendantIds_[treeId] = newActiveDescId;
    },
    onWorkspaceToolbarKeypress: function(e, treeId) {
      if (e.keyCode == 9) {
        // Tab key.
        if (e.shiftKey) {
          this.focusOnPreviousTree_(treeId);
        } else {
          this.focusOnNextTree_(treeId);
        }
        e.preventDefault();
        e.stopPropagation();
      }
    },
    isButtonOrFieldNode_: function(node) {
      return ['BUTTON', 'INPUT'].indexOf(node.tagName) != -1;
    },
    getNextActiveDescWhenBlockIsDeleted: function(blockRootNode) {
      // Go up a level, if possible.
      var nextNode = blockRootNode.parentNode;
      while (nextNode && nextNode.tagName != 'LI') {
        nextNode = nextNode.parentNode;
      }
      if (nextNode) {
        return nextNode;
      }

      // Otherwise, go to the next sibling.
      var nextSibling = this.getNextSibling(blockRootNode);
      if (nextSibling) {
        return nextSibling;
      }

      // Otherwise, go to the previous sibling.
      var previousSibling = this.getPreviousSibling(blockRootNode);
      if (previousSibling) {
        return previousSibling;
      }

      // Otherwise, this is a top-level isolated block, which means that
      // something's gone wrong and this function should not have been called
      // in the first place.
      console.error('Could not handle deletion of block.' + blockRootNode);
    },
    onKeypress: function(e, tree) {
      var treeId = tree.id;
      var activeDesc = document.getElementById(this.getActiveDescId(treeId));
      if (!activeDesc) {
        console.error('ERROR: no active descendant for current tree.');

        // TODO(sll): Generalize this to other trees (outside the workspace).
        var workspaceTreeNodes = this.getWorkspaceTreeNodes_();
        for (var i = 0; i < workspaceTreeNodes.length; i++) {
          if (workspaceTreeNodes[i].id == treeId) {
            // Set the active desc to the first child in this tree.
            this.setActiveDesc(
                this.getFirstChild(workspaceTreeNodes[i]).id, treeId);
            break;
          }
        }
        return;
      }

      var isFocusingIntoField = false;

      if (e.keyCode == 13) {
        // Enter key. The user wants to interact with a child.
        if (activeDesc.children.length == 1) {
          var child = activeDesc.children[0];
          if (child.tagName == 'BUTTON') {
            child.click();
            this.isFocusingIntoField = true;
          } else if (child.tagName == 'INPUT') {
            child.focus();
          }
        }
      } else if (e.keyCode == 9) {
        // Tab key.
        if (e.shiftKey) {
          this.focusOnPreviousTree_(treeId);
        } else {
          this.focusOnNextTree_(treeId);
        }
        e.preventDefault();
        e.stopPropagation();
      } else if (e.keyCode >= 35 && e.keyCode <= 40) {
        // End, home, and arrow keys.
        if (e.keyCode == 35) {
          // End key. Go to the last sibling in the subtree.
          var finalSibling = this.getFinalSibling(activeDesc);
          if (finalSibling) {
            this.setActiveDesc(finalSibling.id, treeId);
          }
        } else if (e.keyCode == 36) {
          // Home key. Go to the first sibling in the subtree.
          var initialSibling = this.getInitialSibling(activeDesc);
          if (initialSibling) {
            this.setActiveDesc(initialSibling.id, treeId);
          }
        } else if (e.keyCode == 37) {
          // Left arrow key. Go up a level, if possible.
          var nextNode = activeDesc.parentNode;
          if (this.isButtonOrFieldNode_(activeDesc)) {
            nextNode = nextNode.parentNode;
          }
          while (nextNode && nextNode.tagName != 'LI') {
            nextNode = nextNode.parentNode;
          }
          if (nextNode) {
            this.setActiveDesc(nextNode.id, treeId);
          }
        } else if (e.keyCode == 38) {
          // Up arrow key. Go to the previous sibling, if possible.
          var prevSibling = this.getPreviousSibling(activeDesc);
          if (prevSibling) {
            this.setActiveDesc(prevSibling.id, treeId);
          }
        } else if (e.keyCode == 39) {
          // Right arrow key. Go down a level, if possible.
          var firstChild = this.getFirstChild(activeDesc);
          if (firstChild) {
            this.setActiveDesc(firstChild.id, treeId);
          }
        } else if (e.keyCode == 40) {
          // Down arrow key. Go to the next sibling, if possible.
          var nextSibling = this.getNextSibling(activeDesc);
          if (nextSibling) {
            this.setActiveDesc(nextSibling.id, treeId);
          }
        }

        e.preventDefault();
        e.stopPropagation();
      }
    },
    getFirstChild: function(element) {
      if (!element) {
        return element;
      } else {
        var childList = element.children;
        for (var i = 0; i < childList.length; i++) {
          if (childList[i].tagName == 'LI') {
            return childList[i];
          } else {
            var potentialElement = this.getFirstChild(childList[i]);
            if (potentialElement) {
              return potentialElement;
            }
          }
        }
        return null;
      }
    },
    getFinalSibling: function(element) {
      while (true) {
        var nextSibling = this.getNextSibling(element);
        if (nextSibling && nextSibling.id != element.id) {
          element = nextSibling;
        } else {
          return element;
        }
      }
    },
    getInitialSibling: function(element) {
      while (true) {
        var previousSibling = this.getPreviousSibling(element);
        if (previousSibling && previousSibling.id != element.id) {
          element = previousSibling;
        } else {
          return element;
        }
      }
    },
    getNextSibling: function(element) {
      if (element.nextElementSibling) {
        // If there is a sibling, find the list element child of the sibling.
        var node = element.nextElementSibling;
        if (node.tagName == 'LI') {
          return node;
        } else {
          // getElementsByTagName returns in DFS order, therefore the first
          // element is the first relevant list child.
          return node.getElementsByTagName('li')[0];
        }
      } else {
        var parent = element.parentNode;
        while (parent && parent.tagName != 'OL') {
          if (parent.nextElementSibling) {
            var node = parent.nextElementSibling;
            if (node.tagName == 'LI') {
              return node;
            } else {
              return this.getFirstChild(node);
            }
          } else {
            parent = parent.parentNode;
          }
        }
        return null;
      }
    },
    getPreviousSibling: function(element) {
      if (element.previousElementSibling) {
        var sibling = element.previousElementSibling;
        if (sibling.tagName == 'LI') {
          return sibling;
        } else {
          return this.getLastChild(sibling);
        }
      } else {
        var parent = element.parentNode;
        while (parent) {
          if (parent.tagName == 'OL') {
            break;
          }
          if (parent.previousElementSibling) {
            var node = parent.previousElementSibling;
            if (node.tagName == 'LI') {
              return node;
            } else {
              // Find the last list element child of the sibling of the parent.
              return this.getLastChild(node);
            }
          } else {
            parent = parent.parentNode;
          }
        }
        return null;
      }
    },
    getLastChild: function(element) {
      if (!element) {
        return element;
      } else {
        var childList = element.children;
        for (var i = childList.length - 1; i >= 0; i--) {
          // Find the last child that is a list element.
          if (childList[i].tagName == 'LI') {
            return childList[i];
          } else {
            var potentialElement = this.getLastChild(childList[i]);
            if (potentialElement) {
              return potentialElement;
            }
          }
        }
        return null;
      }
    }
});
<commit_msg>Implement correct keystroke handling for text input fields.<commit_after>/**
 * AccessibleBlockly
 *
 * Copyright 2016 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Angular2 Service that handles tree keyboard navigation.
 * This is a singleton service for the entire application.
 *
 * @author madeeha@google.com (Madeeha Ghori)
 */

blocklyApp.TreeService = ng.core
  .Class({
    constructor: function() {
      // Stores active descendant ids for each tree in the page.
      this.activeDescendantIds_ = {};
    },
    getToolboxTreeNode_: function() {
      return document.getElementById('blockly-toolbox-tree');
    },
    getWorkspaceToolbarButtonNodes_: function() {
      return Array.from(document.querySelectorAll(
          'button.blocklyWorkspaceToolbarButton'));
    },
    // Returns a list of all top-level workspace tree nodes on the page.
    getWorkspaceTreeNodes_: function() {
      return Array.from(document.querySelectorAll('ol.blocklyWorkspaceTree'));
    },
    // Returns a list of all top-level tree nodes on the page.
    getAllTreeNodes_: function() {
      var treeNodes = [this.getToolboxTreeNode_()];
      treeNodes = treeNodes.concat(this.getWorkspaceToolbarButtonNodes_());
      treeNodes = treeNodes.concat(this.getWorkspaceTreeNodes_());
      return treeNodes;
    },
    isTopLevelWorkspaceTree: function(treeId) {
      return this.getWorkspaceTreeNodes_().some(function(tree) {
        return tree.id == treeId;
      });
    },
    getNodeToFocusOnWhenTreeIsDeleted: function(deletedTreeId) {
      // This returns the node to focus on after the deletion happens.
      // We shift focus to the next tree (if it exists), otherwise we shift
      // focus to the previous tree.
      var trees = this.getAllTreeNodes_();
      for (var i = 0; i < trees.length; i++) {
        if (trees[i].id == deletedTreeId) {
          if (i + 1 < trees.length) {
            return trees[i + 1];
          } else if (i > 0) {
            return trees[i - 1];
          }
        }
      }

      return this.getToolboxTreeNode_();
    },
    focusOnCurrentTree_: function(treeId) {
      var trees = this.getAllTreeNodes_();
      for (var i = 0; i < trees.length; i++) {
        if (trees[i].id == treeId) {
          trees[i].focus();
          return true;
        }
      }
      return false;
    },
    focusOnNextTree_: function(treeId) {
      var trees = this.getAllTreeNodes_();
      for (var i = 0; i < trees.length - 1; i++) {
        if (trees[i].id == treeId) {
          trees[i + 1].focus();
          return true;
        }
      }
      return false;
    },
    focusOnPreviousTree_: function(treeId) {
      var trees = this.getAllTreeNodes_();
      for (var i = trees.length - 1; i > 0; i--) {
        if (trees[i].id == treeId) {
          trees[i - 1].focus();
          return true;
        }
      }
      return false;
    },
    getActiveDescId: function(treeId) {
      return this.activeDescendantIds_[treeId] || '';
    },
    unmarkActiveDesc_: function(activeDescId) {
      var activeDesc = document.getElementById(activeDescId);
      if (activeDesc) {
        activeDesc.classList.remove('blocklyActiveDescendant');
        activeDesc.setAttribute('aria-selected', 'false');
      }
    },
    markActiveDesc_: function(activeDescId) {
      var newActiveDesc = document.getElementById(activeDescId);
      newActiveDesc.classList.add('blocklyActiveDescendant');
      newActiveDesc.setAttribute('aria-selected', 'true');
    },
    // Runs the given function while preserving the focus and active descendant
    // for the given tree.
    runWhilePreservingFocus: function(func, treeId, optionalNewActiveDescId) {
      var oldDescId = this.getActiveDescId(treeId);
      var newDescId = optionalNewActiveDescId || oldDescId;
      this.unmarkActiveDesc_(oldDescId);
      func();

      // The timeout is needed in order to give the DOM time to stabilize
      // before setting the new active descendant, especially in cases like
      // pasteAbove().
      var that = this;
      setTimeout(function() {
        that.markActiveDesc_(newDescId);
        that.activeDescendantIds_[treeId] = newDescId;
        document.getElementById(treeId).focus();
      }, 0);
    },
    // Make a given node the active descendant of a given tree.
    setActiveDesc: function(newActiveDescId, treeId) {
      this.unmarkActiveDesc_(this.getActiveDescId(treeId));
      this.markActiveDesc_(newActiveDescId);
      this.activeDescendantIds_[treeId] = newActiveDescId;
    },
    onWorkspaceToolbarKeypress: function(e, treeId) {
      if (e.keyCode == 9) {
        // Tab key.
        if (e.shiftKey) {
          this.focusOnPreviousTree_(treeId);
        } else {
          this.focusOnNextTree_(treeId);
        }
        e.preventDefault();
        e.stopPropagation();
      }
    },
    isButtonOrFieldNode_: function(node) {
      return ['BUTTON', 'INPUT'].indexOf(node.tagName) != -1;
    },
    getNextActiveDescWhenBlockIsDeleted: function(blockRootNode) {
      // Go up a level, if possible.
      var nextNode = blockRootNode.parentNode;
      while (nextNode && nextNode.tagName != 'LI') {
        nextNode = nextNode.parentNode;
      }
      if (nextNode) {
        return nextNode;
      }

      // Otherwise, go to the next sibling.
      var nextSibling = this.getNextSibling(blockRootNode);
      if (nextSibling) {
        return nextSibling;
      }

      // Otherwise, go to the previous sibling.
      var previousSibling = this.getPreviousSibling(blockRootNode);
      if (previousSibling) {
        return previousSibling;
      }

      // Otherwise, this is a top-level isolated block, which means that
      // something's gone wrong and this function should not have been called
      // in the first place.
      console.error('Could not handle deletion of block.' + blockRootNode);
    },
    onKeypress: function(e, tree) {
      var treeId = tree.id;
      var activeDesc = document.getElementById(this.getActiveDescId(treeId));
      if (!activeDesc) {
        console.error('ERROR: no active descendant for current tree.');

        // TODO(sll): Generalize this to other trees (outside the workspace).
        var workspaceTreeNodes = this.getWorkspaceTreeNodes_();
        for (var i = 0; i < workspaceTreeNodes.length; i++) {
          if (workspaceTreeNodes[i].id == treeId) {
            // Set the active desc to the first child in this tree.
            this.setActiveDesc(
                this.getFirstChild(workspaceTreeNodes[i]).id, treeId);
            break;
          }
        }
        return;
      }

      if (document.activeElement.tagName == 'INPUT') {
        // For input fields, only Esc and Tab keystrokes are handled specially.
        if (e.keyCode == 27 || e.keyCode == 9) {
          // For Esc and Tab keys, the focus is removed from the input field.
          this.focusOnCurrentTree_(treeId);

          // In addition, for Tab keys, the user tabs to the previous/next tree.
          if (e.keyCode == 9) {
            if (e.shiftKey) {
              this.focusOnPreviousTree_(treeId);
            } else {
              this.focusOnNextTree_(treeId);
            }
          }

          e.preventDefault();
          e.stopPropagation();
        }
      } else {
        // Outside an input field, Enter, Tab and navigation keys are all
        // recognized.
        if (e.keyCode == 13) {
          // Enter key. The user wants to interact with a button or an input
          // field.
          if (activeDesc.children.length == 1) {
            var child = activeDesc.children[0];
            if (child.tagName == 'BUTTON') {
              child.click();
            } else if (child.tagName == 'INPUT') {
              child.focus();
            }
          }
        } else if (e.keyCode == 9) {
          // Tab key.
          if (e.shiftKey) {
            this.focusOnPreviousTree_(treeId);
          } else {
            this.focusOnNextTree_(treeId);
          }
          e.preventDefault();
          e.stopPropagation();
        } else if (e.keyCode >= 35 && e.keyCode <= 40) {
          // End, home, and arrow keys.
          if (e.keyCode == 35) {
            // End key. Go to the last sibling in the subtree.
            var finalSibling = this.getFinalSibling(activeDesc);
            if (finalSibling) {
              this.setActiveDesc(finalSibling.id, treeId);
            }
          } else if (e.keyCode == 36) {
            // Home key. Go to the first sibling in the subtree.
            var initialSibling = this.getInitialSibling(activeDesc);
            if (initialSibling) {
              this.setActiveDesc(initialSibling.id, treeId);
            }
          } else if (e.keyCode == 37) {
            // Left arrow key. Go up a level, if possible.
            var nextNode = activeDesc.parentNode;
            if (this.isButtonOrFieldNode_(activeDesc)) {
              nextNode = nextNode.parentNode;
            }
            while (nextNode && nextNode.tagName != 'LI') {
              nextNode = nextNode.parentNode;
            }
            if (nextNode) {
              this.setActiveDesc(nextNode.id, treeId);
            }
          } else if (e.keyCode == 38) {
            // Up arrow key. Go to the previous sibling, if possible.
            var prevSibling = this.getPreviousSibling(activeDesc);
            if (prevSibling) {
              this.setActiveDesc(prevSibling.id, treeId);
            }
          } else if (e.keyCode == 39) {
            // Right arrow key. Go down a level, if possible.
            var firstChild = this.getFirstChild(activeDesc);
            if (firstChild) {
              this.setActiveDesc(firstChild.id, treeId);
            }
          } else if (e.keyCode == 40) {
            // Down arrow key. Go to the next sibling, if possible.
            var nextSibling = this.getNextSibling(activeDesc);
            if (nextSibling) {
              this.setActiveDesc(nextSibling.id, treeId);
            }
          }

          e.preventDefault();
          e.stopPropagation();
        }
      }
    },
    getFirstChild: function(element) {
      if (!element) {
        return element;
      } else {
        var childList = element.children;
        for (var i = 0; i < childList.length; i++) {
          if (childList[i].tagName == 'LI') {
            return childList[i];
          } else {
            var potentialElement = this.getFirstChild(childList[i]);
            if (potentialElement) {
              return potentialElement;
            }
          }
        }
        return null;
      }
    },
    getFinalSibling: function(element) {
      while (true) {
        var nextSibling = this.getNextSibling(element);
        if (nextSibling && nextSibling.id != element.id) {
          element = nextSibling;
        } else {
          return element;
        }
      }
    },
    getInitialSibling: function(element) {
      while (true) {
        var previousSibling = this.getPreviousSibling(element);
        if (previousSibling && previousSibling.id != element.id) {
          element = previousSibling;
        } else {
          return element;
        }
      }
    },
    getNextSibling: function(element) {
      if (element.nextElementSibling) {
        // If there is a sibling, find the list element child of the sibling.
        var node = element.nextElementSibling;
        if (node.tagName == 'LI') {
          return node;
        } else {
          // getElementsByTagName returns in DFS order, therefore the first
          // element is the first relevant list child.
          return node.getElementsByTagName('li')[0];
        }
      } else {
        var parent = element.parentNode;
        while (parent && parent.tagName != 'OL') {
          if (parent.nextElementSibling) {
            var node = parent.nextElementSibling;
            if (node.tagName == 'LI') {
              return node;
            } else {
              return this.getFirstChild(node);
            }
          } else {
            parent = parent.parentNode;
          }
        }
        return null;
      }
    },
    getPreviousSibling: function(element) {
      if (element.previousElementSibling) {
        var sibling = element.previousElementSibling;
        if (sibling.tagName == 'LI') {
          return sibling;
        } else {
          return this.getLastChild(sibling);
        }
      } else {
        var parent = element.parentNode;
        while (parent) {
          if (parent.tagName == 'OL') {
            break;
          }
          if (parent.previousElementSibling) {
            var node = parent.previousElementSibling;
            if (node.tagName == 'LI') {
              return node;
            } else {
              // Find the last list element child of the sibling of the parent.
              return this.getLastChild(node);
            }
          } else {
            parent = parent.parentNode;
          }
        }
        return null;
      }
    },
    getLastChild: function(element) {
      if (!element) {
        return element;
      } else {
        var childList = element.children;
        for (var i = childList.length - 1; i >= 0; i--) {
          // Find the last child that is a list element.
          if (childList[i].tagName == 'LI') {
            return childList[i];
          } else {
            var potentialElement = this.getLastChild(childList[i]);
            if (potentialElement) {
              return potentialElement;
            }
          }
        }
        return null;
      }
    }
});
<|endoftext|>


<commit_before>var gulp = require('gulp');

// Fabric Helper Modules
var Banners = require('./modules/Banners');
var Config = require('./modules/Config');
var BuildConfig = require('./modules/BuildConfig');
var ConsoleHelper = require('./modules/ConsoleHelper');
var ErrorHandling = require('./modules/ErrorHandling');
var Plugins = require('./modules/Plugins');
var pkg = require('../package.json');

var versionParts = pkg.version.split('.');

var version = {
    major: versionParts[0],
    minor: versionParts[1],
    patch: versionParts[2]
}

//
// Clean/Delete Tasks
// ----------------------------------------------------------------------------

// Clean out the distribution folder.
gulp.task('Fabric-nuke', function () {
    return Plugins.del.sync([Config.paths.distCSS, Config.paths.distSass, Config.paths.temp]);
});

//
// Copying Files Tasks
// ----------------------------------------------------------------------------

// Copy all Sass files to distribution folder.
gulp.task('Fabric-copyAssets', function () {
     var moveSass =  gulp.src([Config.paths.srcSass + '/**/*', !Config.paths.srcSass + '/Fabric.Scoped.scss'])
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.changed(Config.paths.distSass))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Moving Sass files over to Dist"
            })))
            .pipe(gulp.dest(Config.paths.distSass));
     return moveSass;
});

//
// Sass tasks
// ----------------------------------------------------------------------------

// Build Sass files for core Fabric into LTR and RTL CSS files.

gulp.task('Fabric-scopedPreBuild', function () {
    var fabricScopedPreBuild = gulp.src(Config.paths.srcTemplate + '/' + 'scoped-fabric-template.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.data(function () {
              return { "version": version };
            }))
            .pipe(Plugins.template())
            .pipe(Plugins.rename('_Fabric.Scoped.Temp.scss'))
            .pipe(gulp.dest(Config.paths.temp));

    return fabricScopedPreBuild;
});

gulp.task('Fabric-buildStyles', function () {
    var fabric = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
              title: "Building Core Fabric " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.rename('fabric.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
              browsers: ['last 2 versions', 'ie >= 9'],
              cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.min.css'))
            .pipe(Plugins.cssMinify({
                safe: true
            }))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS));

    var fabricScoped = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.Scoped.' + BuildConfig.fileExtension)
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
              title: "Building Core Fabric Scoped " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.rename('fabric-' + version.major + '.scoped.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
              browsers: ['last 2 versions', 'ie >= 9'],
              cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric-' + version.major + '.scoped.min.css'))
            .pipe(Plugins.cssMinify({
                safe: true
            }))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS));

    // Build full and minified Fabric RTL CSS.
    var fabricRtl = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.Rtl.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
              title: "Building RTL Fabric " + BuildConfig.processorName + " " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.flipper())
            .pipe(Plugins.rename('fabric.rtl.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
              browsers: ['last 2 versions', 'ie >= 9'],
              cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.rtl.min.css'))
            .pipe(Plugins.cssMinify({
              safe: true
            }))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS));

    // Merge all current streams into one.
    return Plugins.mergeStream(fabric, fabricScoped, fabricRtl);
});

//
// Rolled up Build tasks
// ----------------------------------------------------------------------------

gulp.task('Fabric', ['Fabric-copyAssets','Fabric-scopedPreBuild', 'Fabric-buildStyles']);
BuildConfig.buildTasks.push('Fabric');
BuildConfig.nukeTasks.push('Fabric-nuke');<commit_msg>Add scoped pre-build task as dependency of Fabric-buildStyles. Remove gulp-changed from task since it was causing changes to not be reflected<commit_after>var gulp = require('gulp');

// Fabric Helper Modules
var Banners = require('./modules/Banners');
var Config = require('./modules/Config');
var BuildConfig = require('./modules/BuildConfig');
var ConsoleHelper = require('./modules/ConsoleHelper');
var ErrorHandling = require('./modules/ErrorHandling');
var Plugins = require('./modules/Plugins');
var pkg = require('../package.json');

var versionParts = pkg.version.split('.');

var version = {
    major: versionParts[0],
    minor: versionParts[1],
    patch: versionParts[2]
}

//
// Clean/Delete Tasks
// ----------------------------------------------------------------------------

// Clean out the distribution folder.
gulp.task('Fabric-nuke', function () {
    return Plugins.del.sync([Config.paths.distCSS, Config.paths.distSass, Config.paths.temp]);
});

//
// Copying Files Tasks
// ----------------------------------------------------------------------------

// Copy all Sass files to distribution folder.
gulp.task('Fabric-copyAssets', function () {
     var moveSass =  gulp.src([Config.paths.srcSass + '/**/*', !Config.paths.srcSass + '/Fabric.Scoped.scss'])
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.changed(Config.paths.distSass))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
                    title: "Moving Sass files over to Dist"
            })))
            .pipe(gulp.dest(Config.paths.distSass));
     return moveSass;
});

//
// Sass tasks
// ----------------------------------------------------------------------------

// Build Sass files for core Fabric into LTR and RTL CSS files.

gulp.task('Fabric-scopedPreBuild', function () {
    var fabricScopedPreBuild = gulp.src(Config.paths.srcTemplate + '/' + 'scoped-fabric-template.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.data(function () {
              return { "version": version };
            }))
            .pipe(Plugins.template())
            .pipe(Plugins.rename('_Fabric.Scoped.Temp.scss'))
            .pipe(gulp.dest(Config.paths.temp));

    return fabricScopedPreBuild;
});

gulp.task('Fabric-buildStyles', ['Fabric-scopedPreBuild'], function () {
    var fabric = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
              title: "Building Core Fabric " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.rename('fabric.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
              browsers: ['last 2 versions', 'ie >= 9'],
              cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.min.css'))
            .pipe(Plugins.cssMinify({
                safe: true
            }))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS));

    var fabricScoped = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.Scoped.' + BuildConfig.fileExtension)
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
              title: "Building Core Fabric Scoped " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.rename('fabric-' + version.major + '.scoped.css'))
            .pipe(Plugins.autoprefixer({
              browsers: ['last 2 versions', 'ie >= 9'],
              cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric-' + version.major + '.scoped.min.css'))
            .pipe(Plugins.cssMinify({
                safe: true
            }))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS));

    // Build full and minified Fabric RTL CSS.
    var fabricRtl = gulp.src(BuildConfig.srcPath + '/' + 'Fabric.Rtl.' + BuildConfig.fileExtension)
            .pipe(Plugins.plumber(ErrorHandling.onErrorInPipe))
            .pipe(Plugins.gulpif(Config.debugMode, Plugins.debug({
              title: "Building RTL Fabric " + BuildConfig.processorName + " " + BuildConfig.fileExtension + " File"
            })))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(BuildConfig.processorPlugin().on('error', BuildConfig.compileErrorHandler))
            .pipe(Plugins.flipper())
            .pipe(Plugins.rename('fabric.rtl.css'))
            .pipe(Plugins.changed(Config.paths.distCSS, {extension: '.css'}))
            .pipe(Plugins.autoprefixer({
              browsers: ['last 2 versions', 'ie >= 9'],
              cascade: false
            }))
            .pipe(Plugins.cssbeautify())
            .pipe(Plugins.csscomb())
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS))
            .pipe(Plugins.rename('fabric.rtl.min.css'))
            .pipe(Plugins.cssMinify({
              safe: true
            }))
            .pipe(Plugins.header(Banners.getBannerTemplate(), Banners.getBannerData()))
            .pipe(Plugins.header(Banners.getCSSCopyRight(), Banners.getBannerData()))
            .pipe(gulp.dest(Config.paths.distCSS));

    // Merge all current streams into one.
    return Plugins.mergeStream(fabric, fabricScoped, fabricRtl);
});

//
// Rolled up Build tasks
// ----------------------------------------------------------------------------

gulp.task('Fabric', ['Fabric-copyAssets','Fabric-scopedPreBuild', 'Fabric-buildStyles']);
BuildConfig.buildTasks.push('Fabric');
BuildConfig.nukeTasks.push('Fabric-nuke');<|endoftext|>


<commit_before>'use strict';

var React = require('react-native');

var {
  WebView,
  Component,
  NativeModules: {
    WebViewManager
  }
} = React;

var WEB_VIEW_BRIDGE_REF = 'WEBVIEW_BRIDGE';

class WebViewBridge extends Component {
  constructor(props) {
    super(props);
  }

  onMessage(cb) {
    var ref = this.refs[WEB_VIEW_BRIDGE_REF];
    WebViewManager.onMessage(ref.getWebWiewHandle(), (messages) => {
      messages.forEach((message) => {
        cb(message);
      });

      //re-register the callback again
      this.onMessage(cb);
    });
  }

  evalScript(value) {
    var ref = this.refs[WEB_VIEW_BRIDGE_REF];
    WebViewManager.eval(ref.getWebWiewHandle(), value);
  }

  send(message) {
    var ref = this.refs[WEB_VIEW_BRIDGE_REF];

    if (typeof message !== 'string') {
      message = JSON.stringify(message);
    }

    WebViewManager.send(ref.getWebWiewHandle(), message);
  }

  componentDidMount() {
    //setup the internal variables of webview bridge
    var ref = this.refs[WEB_VIEW_BRIDGE_REF];
    WebViewManager.bridgeSetup(ref.getWebWiewHandle());

    //inject script into webView
    WebViewManager.injectBridgeScript(ref.getWebWiewHandle());
  }

  componentWillUnmount() {
    var ref = this.refs[WEB_VIEW_BRIDGE_REF];
    WebViewManager.callbackCleanup(ref.getWebWiewHandle());
  }

  render() {
    return (
      <WebView
        ref={WEB_VIEW_BRIDGE_REF}
        {...this.props}/>
    );
  }
}

module.exports = WebViewBridge;
<commit_msg>updated script<commit_after>'use strict';

var React = require('react-native');

var {
  WebView,
  Component,
  NativeModules: {
    WebViewManager
  }
} = React;

var WEB_VIEW_BRIDGE_REF = 'WEBVIEW_BRIDGE';

class WebViewBridge extends Component {
  constructor(props) {
    super(props);
  }

 /*
  * returns internal handler id
  */
  getWebViewBridgeHandler() {
    var ref = this.refs[WEB_VIEW_BRIDGE_REF];
    return ref.getWebWiewHandle();
  }

 /*
  * inject script into webView
  */
  injectBridgeScript() {
    WebViewManager.injectBridgeScript(this.getWebViewBridgeHandler());
  }

  onMessage(cb) {
    WebViewManager.onMessage(this.getWebViewBridgeHandler(), (messages) => {
      messages.forEach((message) => {
        cb(message);
      });

      //re-register the callback again
      this.onMessage(cb);
    });
  }

  evalScript(value) {
    WebViewManager.eval(this.getWebViewBridgeHandler(), value);
  }

  send(message) {
    if (typeof message !== 'string') {
      message = JSON.stringify(message);
    }

    WebViewManager.send(this.getWebViewBridgeHandler(), message);
  }

  componentDidMount() {
    //setup the internal variables of webview bridge
    WebViewManager.bridgeSetup(this.getWebViewBridgeHandler());
  }

  componentWillUnmount() {
    //removed the internal variables from objective-c side related to
    //handler id
    WebViewManager.callbackCleanup(this.getWebViewBridgeHandler());
  }

  render() {
    return (
      <WebView
        ref={WEB_VIEW_BRIDGE_REF}
        {...this.props}/>
    );
  }
}

module.exports = WebViewBridge;
<|endoftext|>


<commit_before>import extractUsedProps from '../extractUsedProps'

describe('Extracting used props', () => {
  it('should return used props', () => {
    expect(
      extractUsedProps(props => {
        props.foo, props.bar
      })
    ).toEqual(['foo', 'bar'])
  })

  it('should return an empty array if the rule throws an exception', () => {
    expect(
      extractUsedProps(props => {
        throw new Error('test')
      })
    ).toEqual([])
  })
})
<commit_msg>Fix test<commit_after>import extractUsedProps from '../extractUsedProps'

describe('Extracting used props', () => {
  it('should return used props', () => {
    expect(
      extractUsedProps(props => ({
        foo: props.foo,
        bar: props.bar,
      }))
    ).toEqual(['foo', 'bar'])
  })

  it('should return an empty array if the rule throws an exception', () => {
    expect(
      extractUsedProps(() => {
        throw new Error('test')
      })
    ).toEqual([])
  })
})
<|endoftext|>


<commit_before>/*
 * script dependencies
 *
 * jQuery
 * socket.io
 */

$(document).ready( function($) {
  var socket = io();

  var $loginWrap = $('#login-wrap')
    , $appWrap = $('#app-wrap')
    , $loginError = $('#login-error-display')
    , $messageForm = $('#send-message')
    , $loginForm = $('#send-login')
    , $usernameBox = $('#username-box')
    , $messageBox = $('#message-box')
    , $messageDisplay = $('#chat-display')
    , $userlist = $('#userlist')
  ;

  // Start in login view.
  $appWrap.hide();

  $loginWrap.show();

  $loginForm.submit(function(_event) {
    _event.preventDefault();

    socket.emit('user-login', {
      'time': Date.now()
    , 'user': $usernameBox.val()
    });

  });

  $messageForm.submit(function(_event) {
    _event.preventDefault();

    var message = rp.msgCmd.parser.parseMessage($messageBox.val());
    message['time'] = Date.now();

    socket.emit('message-server', message);

    $messageBox.val('');
  });

  // Userlist
  var userlist = new rp.html.ListDiv($userlist.prop('id'), {});
  userlist.initHtml();

  socket.on('user-login-response', function(_data) {
    if(_data.successful === true) {
      // Switch to chat view.
      $loginWrap.hide();
      $appWrap.show();

    } else {
      $loginError.text(_data.error);
    }
  });

  socket.on('message-client', function(_data) {
    if(_data.error) {
      $messageDisplay.append('
' + '' + _data.error + '' + '
');
      return;
    }

    $messageDisplay.append('
' + '' + _data.user + ': ' + '' + _data.message + '
');
  });

  socket.on('userlist', function(_data) {
    for(var i = 0; i < _data.users.length; i++) {
      userlist.addToList(_data.users[i].name);
    }

    return;
  });

  socket.on('user-signing', function(_data) {
    if(_data.type === 'SIGNIN') {
      userlist.addToList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' joined' + '' + '
');
    } else if (_data.type === 'SIGNOUT') {
      userlist.removeFromList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' left' + '' + '
');
    }
  });
});
<commit_msg>Added public notification when a user has executed a command.<commit_after>/*
 * script dependencies
 *
 * jQuery
 * socket.io
 */

$(document).ready( function($) {
  var socket = io();

  var $loginWrap = $('#login-wrap')
    , $appWrap = $('#app-wrap')
    , $loginError = $('#login-error-display')
    , $messageForm = $('#send-message')
    , $loginForm = $('#send-login')
    , $usernameBox = $('#username-box')
    , $messageBox = $('#message-box')
    , $messageDisplay = $('#chat-display')
    , $userlist = $('#userlist')
  ;

  // Start in login view.
  $appWrap.hide();

  $loginWrap.show();

  $loginForm.submit(function(_event) {
    _event.preventDefault();

    socket.emit('user-login', {
      'time': Date.now()
    , 'user': $usernameBox.val()
    });

  });

  $messageForm.submit(function(_event) {
    _event.preventDefault();

    var message = rp.msgCmd.parser.parseMessage($messageBox.val());
    message['time'] = Date.now();

    socket.emit('message-server', message);

    $messageBox.val('');
  });

  // Userlist
  var userlist = new rp.html.ListDiv($userlist.prop('id'), {});
  userlist.initHtml();

  socket.on('user-login-response', function(_data) {
    if(_data.successful === true) {
      // Switch to chat view.
      $loginWrap.hide();
      $appWrap.show();

    } else {
      $loginError.text(_data.error);
    }
  });

  socket.on('message-client', function(_data) {
    if(_data.error) {
      $messageDisplay.append('
' + '' + _data.error + '' + '
');
      return;
    }

    if(_data.commands && _data.commands.length > 0) {
      $messageDisplay.append( displayCommandsReceived(_data) );
    }

    $messageDisplay.append('
' + '' + _data.user + ': ' + '' + _data.message + '
');
  });

  socket.on('userlist', function(_data) {
    for(var i = 0; i < _data.users.length; i++) {
      userlist.addToList(_data.users[i].name);
    }

    return;
  });

  socket.on('user-signing', function(_data) {
    if(_data.type === 'SIGNIN') {
      userlist.addToList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' joined' + '' + '
');
    } else if (_data.type === 'SIGNOUT') {
      userlist.removeFromList(_data.user);
      $messageDisplay.append('
' + '' + _data.user + ' left' + '' + '
');
    }
  });
});

function displayCommandsReceived(_data) {
  var commandsHtml = '';

  for(var i = 0; i < _data.commands.length; i++) {
    commandsHtml += '
' + '' + 'Command ' + _data.commands[i].cmd + ' received from ' + _data.user + '' + '
';
  }

  return commandsHtml;
}<|endoftext|>