<commit_before>/**
 * Created by JetBrains WebStorm.
 * User: saar
 * Date: 06/03/12
 * Time: 17:23
 * To change this template use File | Settings | File Templates.
 */

var resources = require('jest'),
    util = require('util'),
    models = require('../models'),
    async = require('async'),
    common = require('./common'),
    _ = require('underscore');

var ActionResource = module.exports = common.GamificationMongooseResource.extend(
    {
        init:function () {
            this._super(models.Action, null, common.getGamificationTokenPrice('vote'));
            this.allowed_methods = ['get', 'post', 'put'];
            this.filtering = {cycle_id:null, is_approved:null, grade:null, num_of_going: null};
            this.authentication = new common.SessionAuthentication();
            this.default_query = function(query){
                return query.sort('execution_date','descending');
            }
        },

        get_objects: function (req, filters, sorts, limit, offset, callback) {

            if(req.query.get == "myUru"){
                filters.users = req.user._id;
            }

            this._super(req, filters, sorts, limit, offset, callback);
        },

        create_obj:function (req, fields, callback) {
            var user_id = req.session.user_id;
            var self = this;
            var action_object = new self.model();
            var user = req.user;

            fields.creator_id = user_id;
            fields.first_name = user.first_name;
            fields.last_name = user.last_name;
            fields.users = user_id;
            for (var field in fields) {
                action_object.set(field, fields[field]);
            }
            self.authorization.edit_object(req, action_object, function (err, action_obj) {
                if (err) callback(err);
                else {
                    var cycle_id = action_obj._doc.cycle_id;
                    action_obj.save(function (err, action) {
                        if (!err) {

                            async.parallel([
                                function(cbk){
                                    req.gamification_type = "action";
                                    //user.tokens -= ACTION_PRICE;
                                    // add discussion_id and action_id to the lists in user
                                    models.User.update({_id:user_id}, {$addToSet:{/*cycles: cycle_id, */actions: action._doc._id}}, cbk)
                                },

                                function(cbk){
                                    models.Cycle.findById(cycle_id, cbk);
                                }

                            ], function(err, arg){
                                var cycle_obj = arg[1];

                                if (cycle_obj.upcoming_action){
                                    async.waterfall([
                                        function(cbk){
                                            models.Action.findById(cycle_obj.upcoming_action, cbk);
                                        },

                                        function(action2, cbk){
                                            if(action2.execution_date > action.execution_date){
                                                cycle_obj.upcoming_action = action._id;
                                                cycle_obj.save(cbk);
                                            }
                                        }
                                    ], callback(err, action))
                                }else{
                                    cycle_obj.upcoming_action = action._id;
                                    cycle_obj.save(callback(err, action));
                                }
                            })

                        } else {
                            callback(self.elaborate_mongoose_errors(err), null);
                        }
                    });
                }
            });
        }

        //i have added Join Resource instead
        /* //this happens when user clicks the Join button, the user get the action id, and action
         update_obj:function (req, object, callback) {
         var self = this;
         var user_id = req.session.user_id;
         var action_id = req._id;

         async.waterfall([

         function(cbk){
         models.Action.findById(action_id, cbk);
         },

         function(action_obj, cbk){
         if (common.isArgIsInList(user_id, action_obj.going_users) == false){

         async.parallel([

         function(cbk2){
         models.Action.update({_id:action_id},{$addToSet: {going_users: user_id, users: user_id},$inc:{num_of_going: 1}}, cbk2);
         },

         function(cbk2){
         models.User.update({_id:user_id},{$addToSet: {actions: action_id}}, cbk2);
         }

         ], cbk);
         }else{
         cbk({message:"user has already joined this action",code:401}, null);
         }
         }
         ], function(err, result){
         req.gamification_type = "join_action";
         callback(self.elaborate_mongoose_errors(err), object);
         });
         }*/
    });

<commit_msg>action is follower<commit_after>/**
 * Created by JetBrains WebStorm.
 * User: saar
 * Date: 06/03/12
 * Time: 17:23
 * To change this template use File | Settings | File Templates.
 */

var resources = require('jest'),
    util = require('util'),
    models = require('../models'),
    async = require('async'),
    common = require('./common'),
    _ = require('underscore');

var ActionResource = module.exports = common.GamificationMongooseResource.extend(
    {
        init:function () {
            this._super(models.Action, null, common.getGamificationTokenPrice('vote'));
            this.allowed_methods = ['get', 'post', 'put'];
            this.filtering = {cycle_id:null, is_approved:null, grade:null, num_of_going: null};
            this.authentication = new common.SessionAuthentication();
            this.fields = {
                _id: null,
                title: null,
                text_field: null,
                text_field_preview: null,
                image_field: null,
                image_field_preview: null,
                description: null,
                creator_id: null,
                creator_id: null,
                action_resources: null,
                tags: null,
                creation_date: null,
                execution_date: null,
                required_participants: null,
                grade: null,
                evaluate_counter: null,
                is_follower: null
            }
            this.default_query = function(query){
                return query.sort('execution_date','descending');
            }
        },

        get_objects: function (req, filters, sorts, limit, offset, callback) {

            if(req.query.get == "myUru"){
                filters.users = req.user._id;
            }

            this._super(req, filters, sorts, limit, offset, function(err, response){
                var user_id;

                _.each(response.objects, function(object){
                    object.is_follower = false;
                    if(req.user){
                        user_id = req.user._id;
                        if(_.any(response.objects.going_users, function(user){
                            user.user_id = user_id;
                        }))
                            object.is_follower = true;
                    }
                });
                callback(err, response);
            });
        },

        create_obj:function (req, fields, callback) {
            var user_id = req.session.user_id;
            var self = this;
            var action_object = new self.model();
            var user = req.user;

            fields.creator_id = user_id;
            fields.first_name = user.first_name;
            fields.last_name = user.last_name;
            fields.users = user_id;
            for (var field in fields) {
                action_object.set(field, fields[field]);
            }
            self.authorization.edit_object(req, action_object, function (err, action_obj) {
                if (err) callback(err);
                else {
                    var cycle_id = action_obj._doc.cycle_id;
                    action_obj.save(function (err, action) {
                        if (!err) {

                            async.parallel([
                                function(cbk){
                                    req.gamification_type = "action";
                                    //user.tokens -= ACTION_PRICE;
                                    // add discussion_id and action_id to the lists in user
                                    models.User.update({_id:user_id}, {$addToSet:{/*cycles: cycle_id, */actions: action._doc._id}}, cbk)
                                },

                                function(cbk){
                                    models.Cycle.findById(cycle_id, cbk);
                                }

                            ], function(err, arg){
                                var cycle_obj = arg[1];

                                if (cycle_obj.upcoming_action){
                                    async.waterfall([
                                        function(cbk){
                                            models.Action.findById(cycle_obj.upcoming_action, cbk);
                                        },

                                        function(action2, cbk){
                                            if(action2.execution_date > action.execution_date){
                                                cycle_obj.upcoming_action = action._id;
                                                cycle_obj.save(cbk);
                                            }
                                        }
                                    ], callback(err, action))
                                }else{
                                    cycle_obj.upcoming_action = action._id;
                                    cycle_obj.save(callback(err, action));
                                }
                            })

                        } else {
                            callback(self.elaborate_mongoose_errors(err), null);
                        }
                    });
                }
            });
        }

        //i have added Join Resource instead
        /* //this happens when user clicks the Join button, the user get the action id, and action
         update_obj:function (req, object, callback) {
         var self = this;
         var user_id = req.session.user_id;
         var action_id = req._id;

         async.waterfall([

         function(cbk){
         models.Action.findById(action_id, cbk);
         },

         function(action_obj, cbk){
         if (common.isArgIsInList(user_id, action_obj.going_users) == false){

         async.parallel([

         function(cbk2){
         models.Action.update({_id:action_id},{$addToSet: {going_users: user_id, users: user_id},$inc:{num_of_going: 1}}, cbk2);
         },

         function(cbk2){
         models.User.update({_id:user_id},{$addToSet: {actions: action_id}}, cbk2);
         }

         ], cbk);
         }else{
         cbk({message:"user has already joined this action",code:401}, null);
         }
         }
         ], function(err, result){
         req.gamification_type = "join_action";
         callback(self.elaborate_mongoose_errors(err), object);
         });
         }*/
    });

<|endoftext|>


<commit_before>var child_process = require('child_process'); // http://nodejs.org/api/child_process.html
var path = require('path'); // File path processing. http://nodejs.org/api/path.html
var _ = require('lodash'); // Utilities. http://underscorejs.org/
var Backbone = require('backbone'); // Data model utilities. http://backbonejs.org/
var moment = require('moment'); // Date processing. http://momentjs.com/
var request = require('request'); // Simpler HTTP. https://github.com/mikeal/request
var progress = require('request-progress'); // Progress events on downloads. https://github.com/IndigoUnited/node-request-progress
var XRegExp = require('xregexp').XRegExp; // Fancy regular expressions. http://xregexp.com/
var rimraf = require('rimraf'); // Recursive directory delete. https://github.com/isaacs/rimraf
var fs = require('node-fs'); // Recursive directory creation. https://github.com/bpedro/node-fs
var ncp = require('ncp').ncp; // Recursive directory copy. https://npmjs.org/package/ncp
var winston = require('winston'); // Logging. https://github.com/flatiron/winston
var BaseModel = require('./baseModel.js').BaseModel;

// Update content for the application, as well as the application itself.
exports.ContentUpdater = BaseModel.extend({

    defaults: {
        // When the content was last updated.
        downloaded: null,
        updated: null,

        // The remote URL to the root XML.
        remote: null,

        // The final local path for content.
        local: 'content/',

        // The temp path for content.
        temp: 'content.tmp/',

        // A collection of file objects that are being processed.
        files: null,
        needsUpdate: false,
    },

    // A callback to call when updating is complete.
    _callback: null,

    initialize: function() {
        BaseModel.prototype.initialize.apply(this);
        this.set('local', path.join(this.get('local'), '/'));
        this.set('temp', path.join(path.dirname(this.get('local')), path.basename(this.get('local')) + '.tmp', '/'));
    },

    // Download new content to the temp folder.
    download: function(callback) {
        if (!this.get('remote')) {
            return;
        }

        this.set('needsUpdate', false);
        this._callback = callback;
        this._initDirectories(_.bind(function() {
            if (this.get('remote').indexOf('http') === 0) {
                // We're going to pull down an XML file from the web and parse it for other files.
                request(this.get('remote'), _.bind(this._processContentRoot, this));
            } else {
                // We're going to just robocopy from another folder instead.
                this._robocopy(this.get('remote'), path.resolve(this.get('temp')), null, _.bind(function(code) {
                    this.set('needsUpdate', code > 0 && code <= 8);
                    this._callback(code > 8 ? code : 0);
                    if (code > 8) {
                        // Something bad happened.
                        logger.error('Robocopy failed with code ' + code);
                    }
                }, this));
            }
        }, this));
    },

    // Set up the temp and output directories.
    _initDirectories: function(callback) {
        // Make the temp directory.
        fs.mkdir(this.get('temp'), 0777, true, _.bind(function(error) {
            this._handleError('Error creating temp directory.', error);

            fs.exists(this.get('local'), _.bind(function(exists) {
                if (exists) {
                    callback();
                    return;
                }

                // Make the ouput directory.
                fs.mkdir(this.get('local'), 0777, true, _.bind(function(error) {
                    this._handleError('Error creating ouput directory.', error);
                    callback();
                }, this));
            }, this));
        }, this));
    },

    // Process the XML file to extract files to load.
    _processContentRoot: function(error, response, body) {
        if (response.statusCode != 200) {
            this._handleError('Error loading root XML -- bad password?');
            return;
        }

        this._handleError('Error loading root XML.', error);

        // Write the root XML file.
        fs.writeFile(this.get('temp') + 'content.xml', body, _.bind(function(error) {
            this._handleError('Error writing root XML.', error);
            this.set('files', new exports.ContentFiles());

            // Collect any URLs in root file.
            var pattern = new XRegExp(/>(http.*?)</gi);
            var matches = [];
            var match;

            while (true) {
                match = pattern.exec(body);
                if (!match) {
                    break;
                }
                matches.push(match);
            }

            // Figure out what each URL begins with -- this is removed to create the relative path.
            var prefix = '';

            function iterator(element, index, list) {
                return element[1].indexOf(prefix) === 0;
            }

            while (true) {
                prefix += matches[0][1][prefix.length];
                if (!_.every(matches, iterator)) {
                    prefix = prefix.substr(0, prefix.length - 1);
                    break;
                }
            }

            // Build file objects.
            _.each(matches, function(element, index, list) {
                var url = element[1];
                var relativePath = url.substr(prefix.length);
                var file = new exports.ContentFile({
                    url: url,
                    filePath: this.get('local') + relativePath,
                    tempPath: this.get('temp') + relativePath
                });

                file.on('loaded', this._onFileLoaded, this);
                this.get('files').add(file);
            }, this);

            _.each(this.get('files').models, _.bind(this._processFile, this));
        }, this));
    },

    // Determine if the file needs to be loaded, or if it's already up to date.
    _processFile: function(contentFile) {
        fs.exists(contentFile.get('tempPath'), _.bind(function(exists) {
            if (!exists) {
                // The file doesn't exist locally, so download it.
                this._downloadFile(contentFile);
                return;
            }

            // The file does exist locally, check if the remote one is newer.
            fs.stat(contentFile.get('tempPath'), _.bind(function(error, stats) {
                var localFileModified = moment(stats.mtime);
                request({
                    url: contentFile.get('url'),
                    method: 'HEAD'
                }, _.bind(function(error, response, body) {
                    var remoteFileModified = moment(response.headers['last-modified']);
                    if (remoteFileModified.isAfter(localFileModified)) {
                        // The remote file is newer, go get it.
                        this._downloadFile(contentFile);
                        return;
                    }

                    // The local file is newer, flag it as loaded.
                    contentFile.set('progress', 1);
                }, this));
            }, this));
        }, this));
    },

    // Load the file into memory and create its temp output directory.
    _downloadFile: function(contentFile) {
        logger.info('Downloading ' + contentFile.get('url'));

        // Request the file from the network.
        progress(request({
            url: contentFile.get('url'),
            encoding: null // Required for binary files.
        }, _.bind(function(error, response, body) {
            if (!error && response.statusCode != 200) {
                error = response.statusCode;
            }

            this.set('needsUpdate', !error);
            this._handleError('Error loading ' + contentFile.get('url'), error);

            // Create the file's output directory if needed.
            var dir = path.dirname(contentFile.get('tempPath'));
            fs.exists(dir, _.bind(function(exists) {
                if (!exists) {
                    fs.mkdir(dir, 0777, true, _.bind(function(error) {
                        this._handleError('Error creating temp directory for file.', error);
                        this._writeFile(contentFile, body);
                    }, this));
                } else {
                    this._writeFile(contentFile, body);
                }
            }, this));

        }, this))).on('progress', function(state) {

            // Set progress properties on the file.
            contentFile.set('receivedBytes', state.received);
            contentFile.set('totalBytes', state.total);
            contentFile.set('progress', state.percent / 100);
        });
    },

    // When the file is done loading, write it to disk.
    _writeFile: function(contentFile, body) {
        fs.writeFile(contentFile.get('tempPath'), body, null, _.bind(function(error) {
            this._handleError('Error writing file.', error);
            contentFile.set('progress', 1);
        }, this));
    },

    // Called when the progress of a file is set to 1.
    _onFileLoaded: function(contentFile) {
        contentFile.off('loaded', this._onFileLoaded, this);

        // Determine if all files are loaded.
        var filesToGo = _.filter(this.get('files').models, function(file) {
            return file.get('progress') < 1;
        }).length;
        var style = contentFile.get('totalBytes') ? 'loaded' : 'cached';
        logger.info(contentFile.get('url') + ' ' + style + ', ' + filesToGo + ' to go');

        if (!filesToGo) {
            this.set('downloaded', moment());
            this._callback();
        }
    },

    // Copy files to their final destination when all files are loaded.
    update: function(callback) {
        this._callback = callback;
        // Recursive copy from temp to target.
        ncp(this.get('temp'), this.get('local'), _.bind(function(error) {
            this._handleError('Error copying from temp folder.', error);
            this._completed();
        }, this));
    },

    // Notify on completion.
    _completed: function() {
        this.set('updated', moment());
        this.trigger('complete');
        if (this._callback) {
            this._callback();
        }
    },

    // Generic error handling function.
    _handleError: function(message, error) {
        if (!error) {
            return;
        }

        logger.error(message, error);
        if (this._callback) {
            this._callback(error);
        }
    },

    // Robocopy files instead of downloading them.
    _robocopy: function(sourceDir, targetDir, file, callback) {
        // http://technet.microsoft.com/en-us/library/cc733145.aspx
        var args = [
            sourceDir,
            targetDir,
            file,
        '/e', // Copies subdirectories. Note that this option includes empty directories.
            '/v', // Produces verbose output, and shows all skipped files.
            '/np', // Specifies that the progress of the copying operation (the number of files or directories copied so far) will not be displayed.
            '/njs', // Specifies that there is no job summary.
            '/njh', // Specifies that there is no job header.
            '/bytes', // Prints sizes, as bytes.
            '/fft', // Assumes FAT file times (two-second precision).
            '/ndl' // Specifies that directory names are not to be logged.
        ];

        if (!file) {
            // We're copying full directories, remove the file argument.
            args.splice(2, 1);
        } else {
            // We're copying one file, remove the copy-subdirectories argument.
            args.splice(3, 1);
        }

        var copy = child_process.spawn('robocopy', args);
        logger.info('robocopy: robocopy ' + args.join(' '));

        copy.stdout.on('data', _.bind(function(data) {
            var lines = data.toString().split('\r\n');
            _.each(lines, function(line) {
                line = line.trim();
                if (line) {
                    logger.info('robocopy: ' + line);
                }
            });
        }, this));

        copy.stderr.on('data', _.bind(function(data) {
            logger.error('robocopy: ' + data.toString());
        }, this));

        copy.on('close', _.bind(function(code) {
            // The return codes are weird. http://support.microsoft.com/kb/954404
            this.set('downloaded', moment());
            if (callback) {
                callback(code);
            }
        }, this));
    }
});

exports.ContentFile = Backbone.Model.extend({
    defaults: {
        url: null,
        filePath: null,
        tempPath: null,
        progress: 0,
        receivedBytes: 0,
        totalBytes: 0
    },

    initialize: function() {
        this.on('change:progress', function(model, value, options) {
            if (value >= 1) {
                this.trigger('loaded', this);
            }
        }, this);
    }
});

exports.ContentFiles = Backbone.Collection.extend({
    model: exports.ContentFile
});<commit_msg>back up content before updating, #7624<commit_after>var child_process = require('child_process'); // http://nodejs.org/api/child_process.html
var path = require('path'); // File path processing. http://nodejs.org/api/path.html
var _ = require('lodash'); // Utilities. http://underscorejs.org/
var Backbone = require('backbone'); // Data model utilities. http://backbonejs.org/
var moment = require('moment'); // Date processing. http://momentjs.com/
var request = require('request'); // Simpler HTTP. https://github.com/mikeal/request
var progress = require('request-progress'); // Progress events on downloads. https://github.com/IndigoUnited/node-request-progress
var XRegExp = require('xregexp').XRegExp; // Fancy regular expressions. http://xregexp.com/
var rimraf = require('rimraf'); // Recursive directory delete. https://github.com/isaacs/rimraf
var fs = require('node-fs'); // Recursive directory creation. https://github.com/bpedro/node-fs
var ncp = require('ncp').ncp; // Recursive directory copy. https://npmjs.org/package/ncp
var winston = require('winston'); // Logging. https://github.com/flatiron/winston
var BaseModel = require('./baseModel.js').BaseModel;

// Update content for the application, as well as the application itself.
exports.ContentUpdater = BaseModel.extend({

    defaults: {
        // When the content was last updated.
        downloaded: null,
        updated: null,

        // The remote URL to the root XML.
        remote: null,

        // The final local path for content.
        local: 'content/',

        // The temp path for content.
        temp: null,

        // The path for content backups.
        backup: null,

        // A collection of file objects that are being processed.
        files: null,
        needsUpdate: false,
    },

    // A callback to call when updating is complete.
    _callback: null,

    initialize: function() {
        BaseModel.prototype.initialize.apply(this);

        // Normalize slashes at the end of paths.
        this.set('local', path.join(this.get('local'), '/'));

        // Set up temp and backup directories.
        this.set('temp', path.join(path.dirname(this.get('local')), path.basename(this.get('local')) + '.temp', '/'));
        this.set('backup', path.join(path.dirname(this.get('local')), path.basename(this.get('local')) + '.backup', '/'));
    },

    // Download new content to the temp folder.
    download: function(callback) {
        if (!this.get('remote')) {
            return;
        }

        this.set('needsUpdate', false);
        this._callback = callback;
        this._initDirectories(_.bind(function() {

            if (this.get('remote').indexOf('http') === 0) {
                // We're going to pull down an XML file from the web and parse it for other files.
                request(this.get('remote'), _.bind(this._processContentRoot, this));
            } else {
                // We're going to just robocopy from another folder instead.
                this._robocopy(this.get('remote'), path.resolve(this.get('temp')), null, _.bind(function(code) {
                    this.set('needsUpdate', code > 0 && code <= 8);
                    this._callback(code > 8 ? code : 0);
                    if (code > 8) {
                        // Something bad happened.
                        logger.error('Robocopy failed with code ' + code);
                    }
                }, this));
            }
        }, this));
    },

    // Set up the temp and output directories.
    _initDirectories: function(callback) {
        // Make the temp directory.
        fs.mkdir(this.get('temp'), 0777, true, _.bind(function(error) {
            this._handleError('Error creating temp directory.', error);

            fs.exists(this.get('local'), _.bind(function(exists) {
                if (exists) {
                    callback();
                    return;
                }

                // Make the ouput directory.
                fs.mkdir(this.get('local'), 0777, true, _.bind(function(error) {
                    this._handleError('Error creating ouput directory.', error);
                    callback();
                }, this));
            }, this));
        }, this));
    },

    // Process the XML file to extract files to load.
    _processContentRoot: function(error, response, body) {
        if (response.statusCode != 200) {
            this._handleError('Error loading root XML -- bad password?');
            return;
        }

        this._handleError('Error loading root XML.', error);

        // Write the root XML file.
        fs.writeFile(this.get('temp') + 'content.xml', body, _.bind(function(error) {
            this._handleError('Error writing root XML.', error);
            this.set('files', new exports.ContentFiles());

            // Collect any URLs in root file.
            var pattern = new XRegExp(/>(http.*?)</gi);
            var matches = [];
            var match;

            while (true) {
                match = pattern.exec(body);
                if (!match) {
                    break;
                }
                matches.push(match);
            }

            // Figure out what each URL begins with -- this is removed to create the relative path.
            var prefix = '';

            function iterator(element, index, list) {
                return element[1].indexOf(prefix) === 0;
            }

            while (true) {
                prefix += matches[0][1][prefix.length];
                if (!_.every(matches, iterator)) {
                    prefix = prefix.substr(0, prefix.length - 1);
                    break;
                }
            }

            // Build file objects.
            _.each(matches, function(element, index, list) {
                var url = element[1];
                var relativePath = url.substr(prefix.length);
                var file = new exports.ContentFile({
                    url: url,
                    filePath: this.get('local') + relativePath,
                    tempPath: this.get('temp') + relativePath
                });

                file.on('loaded', this._onFileLoaded, this);
                this.get('files').add(file);
            }, this);

            _.each(this.get('files').models, _.bind(this._processFile, this));
        }, this));
    },

    // Determine if the file needs to be loaded, or if it's already up to date.
    _processFile: function(contentFile) {
        fs.exists(contentFile.get('tempPath'), _.bind(function(exists) {
            if (!exists) {
                // The file doesn't exist locally, so download it.
                this._downloadFile(contentFile);
                return;
            }

            // The file does exist locally, check if the remote one is newer.
            fs.stat(contentFile.get('tempPath'), _.bind(function(error, stats) {
                var localFileModified = moment(stats.mtime);
                request({
                    url: contentFile.get('url'),
                    method: 'HEAD'
                }, _.bind(function(error, response, body) {
                    var remoteFileModified = moment(response.headers['last-modified']);
                    if (remoteFileModified.isAfter(localFileModified)) {
                        // The remote file is newer, go get it.
                        this._downloadFile(contentFile);
                        return;
                    }

                    // The local file is newer, flag it as loaded.
                    contentFile.set('progress', 1);
                }, this));
            }, this));
        }, this));
    },

    // Load the file into memory and create its temp output directory.
    _downloadFile: function(contentFile) {
        logger.info('Downloading ' + contentFile.get('url'));

        // Request the file from the network.
        progress(request({
            url: contentFile.get('url'),
            encoding: null // Required for binary files.
        }, _.bind(function(error, response, body) {
            if (!error && response.statusCode != 200) {
                error = response.statusCode;
            }

            this.set('needsUpdate', !error);
            this._handleError('Error loading ' + contentFile.get('url'), error);

            // Create the file's output directory if needed.
            var dir = path.dirname(contentFile.get('tempPath'));
            fs.exists(dir, _.bind(function(exists) {
                if (!exists) {
                    fs.mkdir(dir, 0777, true, _.bind(function(error) {
                        this._handleError('Error creating temp directory for file.', error);
                        this._writeFile(contentFile, body);
                    }, this));
                } else {
                    this._writeFile(contentFile, body);
                }
            }, this));

        }, this))).on('progress', function(state) {

            // Set progress properties on the file.
            contentFile.set('receivedBytes', state.received);
            contentFile.set('totalBytes', state.total);
            contentFile.set('progress', state.percent / 100);
        });
    },

    // When the file is done loading, write it to disk.
    _writeFile: function(contentFile, body) {
        fs.writeFile(contentFile.get('tempPath'), body, null, _.bind(function(error) {
            this._handleError('Error writing file.', error);
            contentFile.set('progress', 1);
        }, this));
    },

    // Called when the progress of a file is set to 1.
    _onFileLoaded: function(contentFile) {
        contentFile.off('loaded', this._onFileLoaded, this);

        // Determine if all files are loaded.
        var filesToGo = _.filter(this.get('files').models, function(file) {
            return file.get('progress') < 1;
        }).length;
        var style = contentFile.get('totalBytes') ? 'loaded' : 'cached';
        logger.info(contentFile.get('url') + ' ' + style + ', ' + filesToGo + ' to go');

        if (!filesToGo) {
            this.set('downloaded', moment());
            this._callback();
        }
    },

    // Copy files to their final destination when all files are loaded.
    update: function(callback) {
        this._callback = callback;
        // Recursive copy from temp to target.
        ncp(this.get('local'), this.get('backup'), _.bind(function(error) {
            this._handleError('Error copying to backup folder.', error);
            if (error) {
                this._completed();
                return;
            }

            ncp(this.get('temp'), this.get('local'), _.bind(function(error) {
                this._handleError('Error copying from temp folder.', error);
                this._completed();
            }, this));
        }, this));
    },

    // Notify on completion.
    _completed: function() {
        this.set('updated', moment());
        this.trigger('complete');
        if (this._callback) {
            this._callback();
        }
    },

    // Generic error handling function.
    _handleError: function(message, error) {
        if (!error) {
            return;
        }

        logger.error(message, error);
        if (this._callback) {
            this._callback(error);
        }
    },

    // Robocopy files instead of downloading them.
    _robocopy: function(sourceDir, targetDir, file, callback) {
        // http://technet.microsoft.com/en-us/library/cc733145.aspx
        var args = [
            sourceDir,
            targetDir,
            file,
            '/e', // Copies subdirectories. Note that this option includes empty directories.
            '/v', // Produces verbose output, and shows all skipped files.
            '/np', // Specifies that the progress of the copying operation (the number of files or directories copied so far) will not be displayed.
            '/njs', // Specifies that there is no job summary.
            '/njh', // Specifies that there is no job header.
            '/bytes', // Prints sizes, as bytes.
            '/fft', // Assumes FAT file times (two-second precision).
            '/ndl' // Specifies that directory names are not to be logged.
        ];

        if (!file) {
            // We're copying full directories, remove the file argument.
            args.splice(2, 1);
        } else {
            // We're copying one file, remove the copy-subdirectories argument.
            args.splice(3, 1);
        }

        var copy = child_process.spawn('robocopy', args);
        logger.info('robocopy: robocopy ' + args.join(' '));

        copy.stdout.on('data', _.bind(function(data) {
            var lines = data.toString().split('\r\n');
            _.each(lines, function(line) {
                line = line.trim();
                if (line) {
                    logger.info('robocopy: ' + line);
                }
            });
        }, this));

        copy.stderr.on('data', _.bind(function(data) {
            logger.error('robocopy: ' + data.toString());
        }, this));

        copy.on('close', _.bind(function(code) {
            // The return codes are weird. http://support.microsoft.com/kb/954404
            this.set('downloaded', moment());
            if (callback) {
                callback(code);
            }
        }, this));
    }
});

exports.ContentFile = Backbone.Model.extend({
    defaults: {
        url: null,
        filePath: null,
        tempPath: null,
        progress: 0,
        receivedBytes: 0,
        totalBytes: 0
    },

    initialize: function() {
        this.on('change:progress', function(model, value, options) {
            if (value >= 1) {
                this.trigger('loaded', this);
            }
        }, this);
    }
});

exports.ContentFiles = Backbone.Collection.extend({
    model: exports.ContentFile
});<|endoftext|>


<commit_before>var langCode = 'sv';

$( document ).ready( function() {
	//set language by parameter
	var uselang = getURLParameter('uselang');
	if ( uselang ) {
		switchLanguage( uselang );
	}

	//set language through uls
	$( '.uls-trigger' ).uls( {
		onSelect : function( language ) {
			switchLanguage( language );
		},
		quickList: ['en', 'de', 'es', 'fr', 'ru', 'ja', 'ko', 'sv']
	} );

	//load langQ json
	var langQ = {
	    'sv' : 'Q1123',
	    'fr' : 'Q150'
	};

	//trigger baloon on clicking any qlabel
	//would be much nicer to use jquery.mobile popup but it's unclear
	//how to have popup trigger lookup with right Q-val
	$.balloon.defaults.classname = "my-balloon";
	$('.qlabel').on("click", function() {
	    if($(this).hasClass('selected')) {
		$(this).hideBalloon();
		$(this).removeClass('selected');
	    } else {
		$(this).addClass('selected');
		entity = $(this).attr('its-ta-ident-ref').split('/entity/')[1];
		$(this).showBalloon({
		    position: null,
		    contents: 'This has qNo ' +
			      entity +
			      '

' +
			      '

'
		});
		getClaims( entity, 'P18', null, null );
		getClaims( entity, 'P443', 'P407', langQ[langCode] );
	    }
	    // close also if you click the balloon (closes them all though)
	    $('.my-balloon').on("click", function() {
		$('.selected').hideBalloon();
		$('.selected').removeClass('selected');
	    });
	});
} );

/**
 * request the claims for a specific entity for a specific property
 * (value must be of type string)
 * if a qualifier is not null then only claims where the qualifier is
 * present and has the value qualifierValue (must be of type wikibase-entityid)
 * are used
 */
function getClaims( entity, property, qualifier, qualifierValue ) {
    return $.getJSON('https://www.wikidata.org/w/api.php?callback=?', {
	action: 'wbgetclaims',
	entity: entity,
	property: property,
	format: 'json'
    }, function(data) {
	// TODO handle errors and exceptions
	$.each(data.claims[property], function(index, claim) {
	    if ( qualifier === null ){
		setClaim(property, claim.mainsnak.datavalue.value);
		return false;
	    }
	    else if ( qualifierValue === null ){  //no language match at all
		return false;
	    }
	    else if ( claim.qualifiers && qualifier in claim.qualifiers ) {
		console.log(qualifierValue.slice(1) + ' == ' +
		    claim.qualifiers[qualifier][0].datavalue.value['numeric-id']+
		    ' ?');
		if ( qualifierValue.slice(1) == claim.qualifiers[qualifier][0].datavalue.value['numeric-id'] ) {
		    setClaim(property, claim.mainsnak.datavalue.value);
		    return false;
		}
	    }
	});
    });
}

//sets the value of a given property in a popup
function setClaim(property, value) {
    console.log(property + ', ' + value);
    $('.property-'+property).text(value);
}

//all of the needed language changes
function switchLanguage( language ) {
    langCode = language;
    $.qLabel.switchLanguage( language );
    console.log( 'Selected: ' + language + ' (' +
				    $.uls.data.getDir(language) +
			     ')' );
    $( '.content' ).css( 'direction', $.uls.data.getDir( language ) );
    if ( $.uls.data.getDir( language ) == 'rtl' ) {
	    $( '.content' ).css( 'text-align', 'right' );
    }
    else {
	    $( '.content' ).css( 'text-align', 'left' );
    }
}

//returns the named url parameter
function getURLParameter(param) {
    var pageURL = decodeURIComponent(window.location.search.substring(1));
    var urlVariables = pageURL.split('&');
    for (var i = 0; i < urlVariables.length; i++) {
        var parameterName = urlVariables[i].split('=');
        if (parameterName[0] == param) {
            return parameterName[1];
        }
    }
}
<commit_msg>Swittching to bootstrap popovers to make them work<commit_after>var langCode = 'sv';
var dataContent = '';
var popped = false;
$( document ).ready( function() {
	//set language by parameter
	var uselang = getURLParameter('uselang');
	if ( uselang ) {
		switchLanguage( uselang );
	}

	//set language through uls
	$( '.uls-trigger' ).uls( {
		onSelect : function( language ) {
			switchLanguage( language );
		},
		quickList: ['en', 'de', 'es', 'fr', 'ru', 'ja', 'ko', 'sv']
	} );

	//load langQ json
	var langQ = {
	    'sv' : 'Q1123',
	    'fr' : 'Q150'
	};

	//trigger lookup on popover event
	$("[data-toggle='popover']").on('click', function(){
	    if ( popped ) {
		$(this).popover('toggle');
		popped = false;
		return false;  // should prevent kill trigger?
	    }
	    else {
		console.log(dataContent);
		var $triggerElement = $(this);
		var entity = $(this).children().attr('its-ta-ident-ref').split('/entity/')[1];
		dataContent = 'This has qNo ' + entity + 'Image: <property-P18>, Sound: <property-P443>';
		var jqxhrP18 = getClaims( entity, 'P18', null, null );
		var jqxhrP443 = getClaims( entity, 'P443', 'P407', langQ[langCode] );

		$.when(jqxhrP18, jqxhrP443).done(function() {
		    console.log(dataContent);
		    console.log($triggerElement.attr('data-content'));
		    $triggerElement.attr('data-content', dataContent);
		    popped = true;
		    $triggerElement.popover('toggle');
		});
	    }
	});
	// Kill also if you clicked anywhere else
	$(document).on('click', function(){
	    if ( popped ) {
		$("[data-toggle='popover']").popover('destroy');
		popped = false;
	    }
	});
} );

/**
 * request the claims for a specific entity for a specific property
 * (value must be of type string)
 * if a qualifier is not null then only claims where the qualifier is
 * present and has the value qualifierValue (must be of type wikibase-entityid)
 * are used
 */
function getClaims( entity, property, qualifier, qualifierValue ) {
    return $.getJSON('https://www.wikidata.org/w/api.php?callback=?', {
	action: 'wbgetclaims',
	entity: entity,
	property: property,
	format: 'json'
    }, function(data) {
	if (typeof data.claims[property] === "undefined") {
	    // No hits for that property
	    return false;
	}
	$.each(data.claims[property], function(index, claim) {
	    if ( qualifier === null ){
		setClaim(property, claim.mainsnak.datavalue.value);
		return false;
	    }
	    else if ( qualifierValue === null || typeof qualifierValue === "undefined" ){
		// no language match at all
		return false;
	    }
	    else if ( claim.qualifiers && qualifier in claim.qualifiers ) {
		console.log(qualifierValue.slice(1) + ' == ' +
		    claim.qualifiers[qualifier][0].datavalue.value['numeric-id']+
		    ' ?');
		if ( qualifierValue.slice(1) == claim.qualifiers[qualifier][0].datavalue.value['numeric-id'] ) {
		    setClaim(property, claim.mainsnak.datavalue.value);
		    return false;
		}
	    }
	});
    });
}

//sets the value of a given property in a popup
function setClaim(property, value) {
    console.log(property + ', ' + value);
    dataContent = dataContent.replace('<property-'+property+'>',value);
}

//all of the needed language changes
function switchLanguage( language ) {
    langCode = language;
    $.qLabel.switchLanguage( language );
    console.log( 'Selected: ' + language + ' (' +
				    $.uls.data.getDir(language) +
			     ')' );
    $( '.row' ).css( 'direction', $.uls.data.getDir( language ) );
    if ( $.uls.data.getDir( language ) == 'rtl' ) {
	    $( '.row' ).css( 'text-align', 'right' );
    }
    else {
	    $( '.row' ).css( 'text-align', 'left' );
    }
}

//returns the named url parameter
function getURLParameter(param) {
    var pageURL = decodeURIComponent(window.location.search.substring(1));
    var urlVariables = pageURL.split('&');
    for (var i = 0; i < urlVariables.length; i++) {
        var parameterName = urlVariables[i].split('=');
        if (parameterName[0] == param) {
            return parameterName[1];
        }
    }
}
<|endoftext|>


<commit_before>import {Tooltip} from "../ui/tooltip"
import {elt, insertCSS} from "../dom"
import {defineParamHandler, Command} from "../edit"
import sortedInsert from "../util/sortedinsert"

import {getIcon} from "./icons"

// ;; #path=CommandSpec #kind=interface #noAnchor #toc=false
// The `menu` module gives meaning to two additional properties of
// [command specs](#CommandSpec).

// :: string #path=CommandSpec.menuGroup
//
// Adds the command to the menugroup with the given name. The value
// may either be just a name (for example `"inline"` or `"block"`), or
// a name followed by a parenthesized rank (`"inline(40)"`) to control
// the order in which the commands appear in the group (from low to
// high, with 50 as default rank).

// :: Object #path=CommandSpec.display
//
// Determines how a command is shown in the menu. The object should
// have a `type` property, which picks a style of display. These types
// are supported:
//
// **`"icon"`**
//   : Show the command as an icon. The object may have `{path, width,
//     height}` properties, where `path` is an [SVG path
//     spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d),
//     and `width` and `height` provide the viewbox in which that path
//     exists. Alternatively, it may have a `text` property specifying
//     a string of text that makes up the icon, with an optional
//     `style` property giving additional CSS styling for the text.
//
// **`"param"`**
//   : Render command based on its first and only
//     [parameter](#CommandSpec.params), and immediately execute the
//     command when the parameter is changed. Currently only works for
//     `"select"` parameters.

export class Menu {
  constructor(pm, display) {
    this.display = display
    this.stack = []
    this.pm = pm
  }

  show(content, displayInfo) {
    this.stack.length = 0
    this.enter(content, displayInfo)
  }

  reset() {
    this.stack.length = 0
    this.display.reset()
  }

  enter(content, displayInfo) {
    let pieces = [], close = false, explore = value => {
      if (Array.isArray(value)) {
        for (let i = 0; i < value.length; i++) explore(value[i])
        close = true
      } else if (!value.select || value.select(this.pm)) {
        if (close) {
          pieces.push(separator)
          close = false
        }
        pieces.push(value)
      }
    }
    explore(content)

    if (!pieces.length) return this.display.clear()

    this.stack.push(pieces)
    this.draw(displayInfo)
  }

  get active() {
    return this.stack.length > 1
  }

  draw(displayInfo) {
    let cur = this.stack[this.stack.length - 1]
    let rendered = elt("div", {class: "ProseMirror-menu"}, cur.map(item => renderItem(item, this)))
    if (this.stack.length > 1)
      this.display.enter(rendered, () => this.leave(), displayInfo)
    else
      this.display.show(rendered, displayInfo)
  }

  leave() {
    this.stack.pop()
    if (this.stack.length)
      this.draw()
    else
      this.display.reset()
  }
}

export class TooltipDisplay {
  constructor(tooltip, resetFunc) {
    this.tooltip = tooltip
    this.resetFunc = resetFunc
  }

  clear() {
    this.tooltip.close()
  }

  reset() {
    if (this.resetFunc) this.resetFunc()
    else this.clear()
  }

  show(dom, info) {
    this.tooltip.open(dom, info)
  }

  enter(dom, back, info) {
    let button = elt("div", {class: "ProseMirror-tooltip-back", title: "Back"})
    button.addEventListener("mousedown", e => {
      e.preventDefault(); e.stopPropagation()
      back()
    })
    this.show(elt("div", {class: "ProseMirror-tooltip-back-wrapper"}, dom, button), info)
  }
}

function title(pm, command) {
  let key = pm.keyForCommand(command.name)
  return key ? command.label + " (" + key + ")" : command.label
}

function renderIcon(command, menu) {
  let icon = getIcon(command.name, command.spec.display)
  if (command.active(menu.pm)) icon.className += " ProseMirror-icon-active"
  let dom = elt("span", {class: "ProseMirror-menuicon", title: title(menu.pm, command)}, icon)
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    if (!command.params.length) {
      command.exec(menu.pm)
      menu.reset()
    } else if (command.params.length == 1 && command.params[0].type == "select") {
      showSelectMenu(menu.pm, command, dom)
    } else {
      menu.enter(readParams(command))
    }
  })
  return dom
}

function renderSelect(item, menu) {
  let param = item.params[0]
  let deflt = paramDefault(param, menu.pm, item)
  if (deflt != null) {
    let options = param.options.call ? param.options(menu.pm) : param.options
    for (let i = 0; i < options.length; i++) if (options[i].value === deflt) {
      deflt = options[i]
      break
    }
  }

  let dom = elt("div", {class: "ProseMirror-select ProseMirror-select-command-" + item.name, title: item.label},
                !deflt ? (param.defaultLabel || "Select...") : deflt.display ? deflt.display(deflt) : deflt.label)
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    showSelectMenu(menu.pm, item, dom)
  })
  return dom
}

export function showSelectMenu(pm, item, dom) {
  let param = item.params[0]
  let options = param.options.call ? param.options(pm) : param.options
  let menu = elt("div", {class: "ProseMirror-select-menu"}, options.map(o => {
    let dom = elt("div", null, o.display ? o.display(o) : o.label)
    dom.addEventListener("mousedown", e => {
      e.preventDefault()
      item.exec(pm, [o.value])
      finish()
    })
    return dom
  }))
  let pos = dom.getBoundingClientRect(), box = pm.wrapper.getBoundingClientRect()
  menu.style.left = (pos.left - box.left - 2) + "px"
  menu.style.top = (pos.top - box.top - 2) + "px"

  let done = false
  function finish() {
    if (done) return
    done = true
    document.body.removeEventListener("mousedown", finish)
    document.body.removeEventListener("keydown", finish)
    pm.wrapper.removeChild(menu)
  }
  document.body.addEventListener("mousedown", finish)
  document.body.addEventListener("keydown", finish)
  pm.wrapper.appendChild(menu)
}

function renderItem(item, menu) {
  if (item instanceof Command) {
    var display = item.spec.display
    if (display.type == "icon") return renderIcon(item, menu)
    else if (display.type == "param") return renderSelect(item, menu)
    else throw new Error("Command " + item.name + " can not be shown in a menu")
  } else {
    return item.display(menu)
  }
}

function paramDefault(param, pm, command) {
  return !param.default ? ""
    : param.default.call ? param.default.call(command.self, pm)
    : param.default
}

function buildParamForm(pm, command) {
  let prefill = command.spec.prefillParams && command.spec.prefillParams.call(command.self, pm)
  let fields = command.params.map((param, i) => {
    let field, name = "field_" + i
    let val = prefill ? prefill[i] : paramDefault(param, pm, command)
    if (param.type == "text")
      field = elt("input", {name, type: "text",
                            placeholder: param.label,
                            value: val,
                            autocomplete: "off"})
    else if (param.type == "select")
      field = elt("select", {name}, (param.options.call ? param.options(pm) : param.options)
                  .map(o => elt("option", {value: o.value, selected: o == val}, o.label)))
    else // FIXME more types
      throw new Error("Unsupported parameter type: " + param.type)
    return elt("div", null, field)
  })
  return elt("form", null, fields)
}

function gatherParams(pm, command, form) {
  let bad = false
  let params = command.params.map((param, i) => {
    let val = form.elements["field_" + i].value
    if (val) return val
    if (param.default == null) bad = true
    else return paramDefault(param, pm, command)
  })
  return bad ? null : params
}

function paramForm(pm, command, callback) {
  let form = buildParamForm(pm, command), done = false

  let finish = result => {
    if (!done) {
      done = true
      callback(result)
    }
  }

  let submit = () => {
    // FIXME error messages
    finish(gatherParams(pm, command, form))
  }
  form.addEventListener("submit", e => {
    e.preventDefault()
    submit()
  })
  form.addEventListener("keydown", e => {
    if (e.keyCode == 27) {
      finish(null)
    } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
      e.preventDefault()
      submit()
    }
  })
  // FIXME too hacky?
  setTimeout(() => {
    let input = form.querySelector("input, textarea")
    if (input) input.focus()
  }, 20)

  return form
}

export function readParams(command) {
  return {display(menu) {
    return paramForm(menu.pm, command, params => {
      menu.pm.focus()
      if (params) {
        command.exec(menu.pm, params)
        menu.reset()
      } else {
        menu.leave()
      }
    })
  }}
}

const separator = {
  display() { return elt("div", {class: "ProseMirror-menuseparator"}) }
}

function menuRank(cmd) {
  let match = /^[^(]+\((\d+)\)$/.exec(cmd.spec.menuGroup)
  return match ? +match[1] : 50
}

function computeMenuGroups(pm) {
  let groups = Object.create(null)
  for (let name in pm.commands) {
    let cmd = pm.commands[name], spec = cmd.spec.menuGroup
    if (!spec) continue
    let [group] = /^[^(]+/.exec(spec)
    sortedInsert(groups[group] || (groups[group] = []), cmd, (a, b) => menuRank(a) - menuRank(b))
  }
  pm.mod.menuGroups = groups
  let clear = () => {
    pm.mod.menuGroups = null
    pm.off("commandsChanging", clear)
  }
  pm.on("commandsChanging", clear)
  return groups
}

export function menuGroups(pm, names) {
  let groups = pm.mod.menuGroups || computeMenuGroups(pm)
  return names.map(group => groups[group])
}

function tooltipParamHandler(pm, command, callback) {
  let tooltip = new Tooltip(pm.wrapper, "center")
  tooltip.open(paramForm(pm, command, params => {
    pm.focus()
    tooltip.close()
    callback(params)
  }))
}

defineParamHandler("default", tooltipParamHandler)
defineParamHandler("tooltip", tooltipParamHandler)

// FIXME check for obsolete styles
insertCSS(`

.ProseMirror-menu {
  margin: 0 -4px;
  line-height: 1;
  white-space: pre;
}
.ProseMirror-tooltip .ProseMirror-menu {
  width: -webkit-fit-content;
  width: fit-content;
}

.ProseMirror-tooltip-back-wrapper {
  padding-left: 12px;
}
.ProseMirror-tooltip-back {
  position: absolute;
  top: 5px; left: 5px;
  cursor: pointer;
}
.ProseMirror-tooltip-back:after {
  content: "«";
}

.ProseMirror-menuicon {
  margin: 0 7px;
}

.ProseMirror-menuseparator {
  display: inline-block;
}
.ProseMirror-menuseparator:after {
  content: "︙";
  opacity: 0.5;
  padding: 0 4px;
}

.ProseMirror-select, .ProseMirror-select-menu {
  border: 1px solid #777;
  border-radius: 3px;
  font-size: 90%;
}

.ProseMirror-select {
  padding: 1px 12px 1px 4px;
  display: inline-block;
  vertical-align: 1px;
  position: relative;
  cursor: pointer;
  margin: 0 8px;
}

.ProseMirror-select-command-textblockType {
  min-width: 3.2em;
}

.ProseMirror-select:after {
  content: "▿";
  color: #777;
  position: absolute;
  right: 4px;
}

.ProseMirror-select-menu {
  position: absolute;
  background: #444;
  color: white;
  padding: 2px 2px;
  z-index: 15;
}
.ProseMirror-select-menu div {
  cursor: pointer;
  padding: 0 1em 0 2px;
}
.ProseMirror-select-menu div:hover {
  background: #777;
}

`)
<commit_msg>Remove #toc tags from doc comments<commit_after>import {Tooltip} from "../ui/tooltip"
import {elt, insertCSS} from "../dom"
import {defineParamHandler, Command} from "../edit"
import sortedInsert from "../util/sortedinsert"

import {getIcon} from "./icons"

// ;; #path=CommandSpec #kind=interface #noAnchor
// The `menu` module gives meaning to two additional properties of
// [command specs](#CommandSpec).

// :: string #path=CommandSpec.menuGroup
//
// Adds the command to the menugroup with the given name. The value
// may either be just a name (for example `"inline"` or `"block"`), or
// a name followed by a parenthesized rank (`"inline(40)"`) to control
// the order in which the commands appear in the group (from low to
// high, with 50 as default rank).

// :: Object #path=CommandSpec.display
//
// Determines how a command is shown in the menu. The object should
// have a `type` property, which picks a style of display. These types
// are supported:
//
// **`"icon"`**
//   : Show the command as an icon. The object may have `{path, width,
//     height}` properties, where `path` is an [SVG path
//     spec](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d),
//     and `width` and `height` provide the viewbox in which that path
//     exists. Alternatively, it may have a `text` property specifying
//     a string of text that makes up the icon, with an optional
//     `style` property giving additional CSS styling for the text.
//
// **`"param"`**
//   : Render command based on its first and only
//     [parameter](#CommandSpec.params), and immediately execute the
//     command when the parameter is changed. Currently only works for
//     `"select"` parameters.

export class Menu {
  constructor(pm, display) {
    this.display = display
    this.stack = []
    this.pm = pm
  }

  show(content, displayInfo) {
    this.stack.length = 0
    this.enter(content, displayInfo)
  }

  reset() {
    this.stack.length = 0
    this.display.reset()
  }

  enter(content, displayInfo) {
    let pieces = [], close = false, explore = value => {
      if (Array.isArray(value)) {
        for (let i = 0; i < value.length; i++) explore(value[i])
        close = true
      } else if (!value.select || value.select(this.pm)) {
        if (close) {
          pieces.push(separator)
          close = false
        }
        pieces.push(value)
      }
    }
    explore(content)

    if (!pieces.length) return this.display.clear()

    this.stack.push(pieces)
    this.draw(displayInfo)
  }

  get active() {
    return this.stack.length > 1
  }

  draw(displayInfo) {
    let cur = this.stack[this.stack.length - 1]
    let rendered = elt("div", {class: "ProseMirror-menu"}, cur.map(item => renderItem(item, this)))
    if (this.stack.length > 1)
      this.display.enter(rendered, () => this.leave(), displayInfo)
    else
      this.display.show(rendered, displayInfo)
  }

  leave() {
    this.stack.pop()
    if (this.stack.length)
      this.draw()
    else
      this.display.reset()
  }
}

export class TooltipDisplay {
  constructor(tooltip, resetFunc) {
    this.tooltip = tooltip
    this.resetFunc = resetFunc
  }

  clear() {
    this.tooltip.close()
  }

  reset() {
    if (this.resetFunc) this.resetFunc()
    else this.clear()
  }

  show(dom, info) {
    this.tooltip.open(dom, info)
  }

  enter(dom, back, info) {
    let button = elt("div", {class: "ProseMirror-tooltip-back", title: "Back"})
    button.addEventListener("mousedown", e => {
      e.preventDefault(); e.stopPropagation()
      back()
    })
    this.show(elt("div", {class: "ProseMirror-tooltip-back-wrapper"}, dom, button), info)
  }
}

function title(pm, command) {
  let key = pm.keyForCommand(command.name)
  return key ? command.label + " (" + key + ")" : command.label
}

function renderIcon(command, menu) {
  let icon = getIcon(command.name, command.spec.display)
  if (command.active(menu.pm)) icon.className += " ProseMirror-icon-active"
  let dom = elt("span", {class: "ProseMirror-menuicon", title: title(menu.pm, command)}, icon)
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    if (!command.params.length) {
      command.exec(menu.pm)
      menu.reset()
    } else if (command.params.length == 1 && command.params[0].type == "select") {
      showSelectMenu(menu.pm, command, dom)
    } else {
      menu.enter(readParams(command))
    }
  })
  return dom
}

function renderSelect(item, menu) {
  let param = item.params[0]
  let deflt = paramDefault(param, menu.pm, item)
  if (deflt != null) {
    let options = param.options.call ? param.options(menu.pm) : param.options
    for (let i = 0; i < options.length; i++) if (options[i].value === deflt) {
      deflt = options[i]
      break
    }
  }

  let dom = elt("div", {class: "ProseMirror-select ProseMirror-select-command-" + item.name, title: item.label},
                !deflt ? (param.defaultLabel || "Select...") : deflt.display ? deflt.display(deflt) : deflt.label)
  dom.addEventListener("mousedown", e => {
    e.preventDefault(); e.stopPropagation()
    showSelectMenu(menu.pm, item, dom)
  })
  return dom
}

export function showSelectMenu(pm, item, dom) {
  let param = item.params[0]
  let options = param.options.call ? param.options(pm) : param.options
  let menu = elt("div", {class: "ProseMirror-select-menu"}, options.map(o => {
    let dom = elt("div", null, o.display ? o.display(o) : o.label)
    dom.addEventListener("mousedown", e => {
      e.preventDefault()
      item.exec(pm, [o.value])
      finish()
    })
    return dom
  }))
  let pos = dom.getBoundingClientRect(), box = pm.wrapper.getBoundingClientRect()
  menu.style.left = (pos.left - box.left - 2) + "px"
  menu.style.top = (pos.top - box.top - 2) + "px"

  let done = false
  function finish() {
    if (done) return
    done = true
    document.body.removeEventListener("mousedown", finish)
    document.body.removeEventListener("keydown", finish)
    pm.wrapper.removeChild(menu)
  }
  document.body.addEventListener("mousedown", finish)
  document.body.addEventListener("keydown", finish)
  pm.wrapper.appendChild(menu)
}

function renderItem(item, menu) {
  if (item instanceof Command) {
    var display = item.spec.display
    if (display.type == "icon") return renderIcon(item, menu)
    else if (display.type == "param") return renderSelect(item, menu)
    else throw new Error("Command " + item.name + " can not be shown in a menu")
  } else {
    return item.display(menu)
  }
}

function paramDefault(param, pm, command) {
  return !param.default ? ""
    : param.default.call ? param.default.call(command.self, pm)
    : param.default
}

function buildParamForm(pm, command) {
  let prefill = command.spec.prefillParams && command.spec.prefillParams.call(command.self, pm)
  let fields = command.params.map((param, i) => {
    let field, name = "field_" + i
    let val = prefill ? prefill[i] : paramDefault(param, pm, command)
    if (param.type == "text")
      field = elt("input", {name, type: "text",
                            placeholder: param.label,
                            value: val,
                            autocomplete: "off"})
    else if (param.type == "select")
      field = elt("select", {name}, (param.options.call ? param.options(pm) : param.options)
                  .map(o => elt("option", {value: o.value, selected: o == val}, o.label)))
    else // FIXME more types
      throw new Error("Unsupported parameter type: " + param.type)
    return elt("div", null, field)
  })
  return elt("form", null, fields)
}

function gatherParams(pm, command, form) {
  let bad = false
  let params = command.params.map((param, i) => {
    let val = form.elements["field_" + i].value
    if (val) return val
    if (param.default == null) bad = true
    else return paramDefault(param, pm, command)
  })
  return bad ? null : params
}

function paramForm(pm, command, callback) {
  let form = buildParamForm(pm, command), done = false

  let finish = result => {
    if (!done) {
      done = true
      callback(result)
    }
  }

  let submit = () => {
    // FIXME error messages
    finish(gatherParams(pm, command, form))
  }
  form.addEventListener("submit", e => {
    e.preventDefault()
    submit()
  })
  form.addEventListener("keydown", e => {
    if (e.keyCode == 27) {
      finish(null)
    } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
      e.preventDefault()
      submit()
    }
  })
  // FIXME too hacky?
  setTimeout(() => {
    let input = form.querySelector("input, textarea")
    if (input) input.focus()
  }, 20)

  return form
}

export function readParams(command) {
  return {display(menu) {
    return paramForm(menu.pm, command, params => {
      menu.pm.focus()
      if (params) {
        command.exec(menu.pm, params)
        menu.reset()
      } else {
        menu.leave()
      }
    })
  }}
}

const separator = {
  display() { return elt("div", {class: "ProseMirror-menuseparator"}) }
}

function menuRank(cmd) {
  let match = /^[^(]+\((\d+)\)$/.exec(cmd.spec.menuGroup)
  return match ? +match[1] : 50
}

function computeMenuGroups(pm) {
  let groups = Object.create(null)
  for (let name in pm.commands) {
    let cmd = pm.commands[name], spec = cmd.spec.menuGroup
    if (!spec) continue
    let [group] = /^[^(]+/.exec(spec)
    sortedInsert(groups[group] || (groups[group] = []), cmd, (a, b) => menuRank(a) - menuRank(b))
  }
  pm.mod.menuGroups = groups
  let clear = () => {
    pm.mod.menuGroups = null
    pm.off("commandsChanging", clear)
  }
  pm.on("commandsChanging", clear)
  return groups
}

export function menuGroups(pm, names) {
  let groups = pm.mod.menuGroups || computeMenuGroups(pm)
  return names.map(group => groups[group])
}

function tooltipParamHandler(pm, command, callback) {
  let tooltip = new Tooltip(pm.wrapper, "center")
  tooltip.open(paramForm(pm, command, params => {
    pm.focus()
    tooltip.close()
    callback(params)
  }))
}

defineParamHandler("default", tooltipParamHandler)
defineParamHandler("tooltip", tooltipParamHandler)

// FIXME check for obsolete styles
insertCSS(`

.ProseMirror-menu {
  margin: 0 -4px;
  line-height: 1;
  white-space: pre;
}
.ProseMirror-tooltip .ProseMirror-menu {
  width: -webkit-fit-content;
  width: fit-content;
}

.ProseMirror-tooltip-back-wrapper {
  padding-left: 12px;
}
.ProseMirror-tooltip-back {
  position: absolute;
  top: 5px; left: 5px;
  cursor: pointer;
}
.ProseMirror-tooltip-back:after {
  content: "«";
}

.ProseMirror-menuicon {
  margin: 0 7px;
}

.ProseMirror-menuseparator {
  display: inline-block;
}
.ProseMirror-menuseparator:after {
  content: "︙";
  opacity: 0.5;
  padding: 0 4px;
}

.ProseMirror-select, .ProseMirror-select-menu {
  border: 1px solid #777;
  border-radius: 3px;
  font-size: 90%;
}

.ProseMirror-select {
  padding: 1px 12px 1px 4px;
  display: inline-block;
  vertical-align: 1px;
  position: relative;
  cursor: pointer;
  margin: 0 8px;
}

.ProseMirror-select-command-textblockType {
  min-width: 3.2em;
}

.ProseMirror-select:after {
  content: "▿";
  color: #777;
  position: absolute;
  right: 4px;
}

.ProseMirror-select-menu {
  position: absolute;
  background: #444;
  color: white;
  padding: 2px 2px;
  z-index: 15;
}
.ProseMirror-select-menu div {
  cursor: pointer;
  padding: 0 1em 0 2px;
}
.ProseMirror-select-menu div:hover {
  background: #777;
}

`)
<|endoftext|>


<commit_before>/**
 * © Alexander Buzin, 2014-2015
 * Site: http://alexbuzin.me/
 * Email: alexbuzin88@gmail.com
*/

/**
 * MAKEFIRSTPERSON.
 *
 * @param {Object} object *WHS* figure/object. (REQUIRED)
 */

const PI_2 = Math.PI/2;

WHS.init.prototype.MakeFirstPerson = function(object, params) {

    'use strict';

    var target = WHS.API.extend({
        block: $('#blocker'),
        speed: 1,
        ypos: 1
    }, params);

    this.controls = new (function ( camera, mesh, params) {

        /* Velocity properties */
        var velocityFactor = 1, //Same as 20 * 0.05
            runVelocity = 0.25;

        mesh.setAngularFactor({x: 0, y: 0, z: 0});

        /* Init */
        var scope = this,
            pitchObject = new THREE.Object3D();

        pitchObject.add( camera );

        var yawObject = new THREE.Object3D();

        yawObject.position.y = params.ypos; // eyes are 2 meters above the ground
        yawObject.add( pitchObject );

        var quat = new THREE.Quaternion(),

            moveForward = false,
            moveBackward = false,
            moveLeft = false,
            moveRight = false,

            canJump = false;

        mesh.addEventListener("collision", function(other_object, v, r, contactNormal){

            // If contactNormal.dot(upAxis) is between 0 and 1, we know that the contact normal is somewhat in the up direction.
            // Update, we do not need upAxis, (a, b, c).dot((0, 1, 0)) = b right ?
            if(contactNormal.y < 0.5) // Use a "good" threshold value between 0 and 1 here!
                    canJump = true;
        });

        function onMouseMove ( event ) {
            if ( scope.enabled === false ) return;

            var movementX = event.movementX || event.mozMovementX || event.getMovementX() || 0,
                movementY = event.movementY || event.mozMovementY || event.getMovementY() || 0;

            yawObject.rotation.y -= movementX * 0.002,
            pitchObject.rotation.x -= movementY * 0.002;

            pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );
        };

        function onKeyDown ( event ) {

            switch ( event.keyCode ) {

                case 38: // up
                case 87: // w
                    moveForward = true;
                    break;

                case 37: // left
                case 65: // a
                    moveLeft = true;
                    break;

                case 40: // down
                case 83: // s
                    moveBackward = true;
                    break;

                case 39: // right
                case 68: // d
                    moveRight = true;
                    break;

                case 32: // space
                    if ( canJump == true ){

                            mesh.applyCentralImpulse({x: 0, y: 300, z: 0});
                            // Read the PhysiJS Code, they do not verify if
                            // the argument is a THREE.Vector3 as long is it has x, y, z

                    }

                    canJump = false;

                    break;

                case 15: // shift

                        runVelocity = 0.5;
                        break;

            }

        };

        function onKeyUp ( event ) {
            switch( event.keyCode ) {

                case 38: // up
                case 87: // w
                    moveForward = false;
                    break;

                case 37: // left
                case 65: // a
                    moveLeft = false;
                    break;

                case 40: // down
                case 83: // a
                    moveBackward = false;
                    break;

                case 39: // right
                case 68: // d
                    moveRight = false;
                    break;

                case 15: // shift
                    runVelocity = 0.25;
                    break;

            }

        };

        document.body.addEventListener( 'mousemove', onMouseMove, false );
        document.body.addEventListener( 'keydown', onKeyDown, false );
        document.body.addEventListener( 'keyup', onKeyUp, false );

        this.enabled = false;

        this.getObject = function () {
            return yawObject;
        };

        this.getDirection = function(targetVec){
            targetVec.set(0,0,-1);
            quat.multiplyVector3(targetVec);
        }

        // Moves the camera to the Cannon.js object position
        // and adds velocity to the object if the run key is down.
        var inputVelocity = new THREE.Vector3(),
            euler = new THREE.Euler();

        this.update = function ( delta ) {

            var moveVec = new THREE.Vector3();

            if ( scope.enabled === false ) return;

            delta = 0.5;
            delta = Math.min(delta, 0.5);

            inputVelocity.set(0,0,0);

            var speed = velocityFactor * delta * params.speed * runVelocity;

            if ( moveForward ){
                inputVelocity.z = -speed;
            }

            if ( moveBackward ){
                inputVelocity.z = speed;
            }

            if ( moveLeft ){
                inputVelocity.x = -speed;
            }

            if ( moveRight ){
                inputVelocity.x = speed;
            }

            // Convert velocity to world coordinates
            euler.x = pitchObject.rotation.x,
            euler.y = yawObject.rotation.y,
            euler.order = "XYZ";

            quat.setFromEuler(euler);

            inputVelocity.applyQuaternion(quat);
            //quat.multiplyVector3(inputVelocity);

            mesh.applyCentralImpulse({x: inputVelocity.x * 10, y: 0, z: inputVelocity.z * 10});
            mesh.setAngularVelocity({x: inputVelocity.z * 10, y: 0, z: -inputVelocity.x * 10});

            yawObject.position.copy(mesh.position);
        };

    })(this._camera, object.visible, target);

    var controls = this.controls;

    WHS.API.merge(this.scene, this.controls.getObject());

    if ('pointerLockElement' in document ||
        'mozPointerLockElement' in document ||
        'webkitPointerLockElement' in document) {

        var element = document.body;

        this.pointerlockchange = function() {
            if (document.pointerLockElement === element ||
                document.mozPointerLockElement === element ||
                document.webkitPointerLockElement === element) {

                controls.enabled = true;

                $(target.block).fadeOut();

            } else {

                controls.enabled = false;

                $(target.block).fadeIn();

            }

        }

    }

    document.addEventListener('pointerlockchange', this.pointerlockchange, false);
    document.addEventListener('mozpointerlockchange', this.pointerlockchange, false);
    document.addEventListener('webkitpointerlockchange', this.pointerlockchange, false);

    this.pointerlockerror = function() {
        console.warn("Pointer lock error.");
    }

    document.addEventListener('pointerlockerror', this.pointerlockerror, false);
    document.addEventListener('mozpointerlockerror', this.pointerlockerror, false);
    document.addEventListener('webkitpointerlockerror', this.pointerlockerror, false);

    target.block.on('click', function() {

        element.requestPointerLock = element.requestPointerLock ||
                                     element.mozRequestPointerLock ||
                                     element.webkitRequestPointerLock;

        element.requestFullscreen = element.requestFullscreen ||
                                    element.mozRequestFullscreen ||
                                    element.mozRequestFullScreen ||
                                    element.webkitRequestFullscreen;

        if (/Firefox/i.test(navigator.userAgent)) {

            var fullscreenchange = function() {
                if (document.fullscreenElement === element ||
                    document.mozFullscreenElement === element ||
                    document.mozFullScreenElement === element) {

                        document.removeEventListener('fullscreenchange', fullscreenchange);
                        document.removeEventListener('mozfullscreenchange', fullscreenchange);

                        element.requestPointerLock();

                }
            }

            document.addEventListener('fullscreenchange', fullscreenchange, false);
            document.addEventListener('mozfullscreenchange', fullscreenchange, false);

            element.requestFullscreen();

        } else
            element.requestPointerLock();

    } );

}
<commit_msg>WIP. removing jQuery #25 <commit_after>/**
 * © Alexander Buzin, 2014-2015
 * Site: http://alexbuzin.me/
 * Email: alexbuzin88@gmail.com
*/

/**
 * MAKEFIRSTPERSON.
 *
 * @param {Object} object *WHS* figure/object. (REQUIRED)
 */

const PI_2 = Math.PI/2;

WHS.init.prototype.MakeFirstPerson = function(object, params) {

    'use strict';

    var target = WHS.API.extend({
        block: document.getElementById('blocker'),
        speed: 1,
        ypos: 1
    }, params);

    this.controls = new (function ( camera, mesh, params) {

        /* Velocity properties */
        var velocityFactor = 1,
            runVelocity = 0.25;

        mesh.setAngularFactor({x: 0, y: 0, z: 0});

        /* Init */
        var scope = this,
            pitchObject = new THREE.Object3D();

        pitchObject.add( camera );

        var yawObject = new THREE.Object3D();

        yawObject.position.y = params.ypos; // eyes are 2 meters above the ground
        yawObject.add( pitchObject );

        var quat = new THREE.Quaternion(),

            moveForward = false,
            moveBackward = false,
            moveLeft = false,
            moveRight = false,

            canJump = false;

        mesh.addEventListener("collision", function(other_object, v, r, contactNormal){

            if(contactNormal.y < 0.5) // Use a "good" threshold value between 0 and 1 here!
                    canJump = true;
        });

        function onMouseMove ( event ) {
            if ( scope.enabled === false ) return;

            var movementX = event.movementX || event.mozMovementX || event.getMovementX() || 0,
                movementY = event.movementY || event.mozMovementY || event.getMovementY() || 0;

            yawObject.rotation.y -= movementX * 0.002,
            pitchObject.rotation.x -= movementY * 0.002;

            pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );
        };

        function onKeyDown ( event ) {

            switch ( event.keyCode ) {

                case 38: // up
                case 87: // w
                    moveForward = true;
                    break;

                case 37: // left
                case 65: // a
                    moveLeft = true;
                    break;

                case 40: // down
                case 83: // s
                    moveBackward = true;
                    break;

                case 39: // right
                case 68: // d
                    moveRight = true;
                    break;

                case 32: // space
                    if ( canJump == true ){

                            mesh.applyCentralImpulse({x: 0, y: 300, z: 0});

                    }

                    canJump = false;

                    break;

                case 15: // shift

                        runVelocity = 0.5;
                        break;

            }

        };

        function onKeyUp ( event ) {
            switch( event.keyCode ) {

                case 38: // up
                case 87: // w
                    moveForward = false;
                    break;

                case 37: // left
                case 65: // a
                    moveLeft = false;
                    break;

                case 40: // down
                case 83: // a
                    moveBackward = false;
                    break;

                case 39: // right
                case 68: // d
                    moveRight = false;
                    break;

                case 15: // shift
                    runVelocity = 0.25;
                    break;

            }

        };

        document.body.addEventListener( 'mousemove', onMouseMove, false );
        document.body.addEventListener( 'keydown', onKeyDown, false );
        document.body.addEventListener( 'keyup', onKeyUp, false );

        this.enabled = false;

        this.getObject = function () {
            return yawObject;
        };

        this.getDirection = function(targetVec){
            targetVec.set(0,0,-1);
            quat.multiplyVector3(targetVec);
        }

        // Moves the camera to the Cannon.js object position
        // and adds velocity to the object if the run key is down.
        var inputVelocity = new THREE.Vector3(),
            euler = new THREE.Euler();

        this.update = function ( delta ) {

            var moveVec = new THREE.Vector3();

            if ( scope.enabled === false ) return;

            delta = delta || 0.5;
            delta = Math.min(delta, 0.5);

            inputVelocity.set(0,0,0);

            var speed = velocityFactor * delta * params.speed * runVelocity;

            if ( moveForward ){
                inputVelocity.z = -speed;
            }

            if ( moveBackward ){
                inputVelocity.z = speed;
            }

            if ( moveLeft ){
                inputVelocity.x = -speed;
            }

            if ( moveRight ){
                inputVelocity.x = speed;
            }

            // Convert velocity to world coordinates
            euler.x = pitchObject.rotation.x,
            euler.y = yawObject.rotation.y,
            euler.order = "XYZ";

            quat.setFromEuler(euler);

            inputVelocity.applyQuaternion(quat);

            mesh.applyCentralImpulse({x: inputVelocity.x * 10, y: 0, z: inputVelocity.z * 10});
            mesh.setAngularVelocity({x: inputVelocity.z * 10, y: 0, z: -inputVelocity.x * 10});

            yawObject.position.copy(mesh.position);
        };

    })(this._camera, object.visible, target);

    var controls = this.controls;

    WHS.API.merge(this.scene, this.controls.getObject());

    if ('pointerLockElement' in document ||
        'mozPointerLockElement' in document ||
        'webkitPointerLockElement' in document) {

        var element = document.body;

        this.pointerlockchange = function() {
            if (document.pointerLockElement === element ||
                document.mozPointerLockElement === element ||
                document.webkitPointerLockElement === element) {

                controls.enabled = true;

                target.block.fadeOut();

            } else {

                controls.enabled = false;

                target.block.fadeIn();

            }

        }

    }

    document.addEventListener('pointerlockchange', this.pointerlockchange, false);
    document.addEventListener('mozpointerlockchange', this.pointerlockchange, false);
    document.addEventListener('webkitpointerlockchange', this.pointerlockchange, false);

    this.pointerlockerror = function() {
        console.warn("Pointer lock error.");
    }

    document.addEventListener('pointerlockerror', this.pointerlockerror, false);
    document.addEventListener('mozpointerlockerror', this.pointerlockerror, false);
    document.addEventListener('webkitpointerlockerror', this.pointerlockerror, false);

    target.block.addEventListener('click', function() {

        element.requestPointerLock = element.requestPointerLock ||
                                     element.mozRequestPointerLock ||
                                     element.webkitRequestPointerLock;

        element.requestFullscreen = element.requestFullscreen ||
                                    element.mozRequestFullscreen ||
                                    element.mozRequestFullScreen ||
                                    element.webkitRequestFullscreen;

        if (/Firefox/i.test(navigator.userAgent)) {

            var fullscreenchange = function() {
                if (document.fullscreenElement === element ||
                    document.mozFullscreenElement === element ||
                    document.mozFullScreenElement === element) {

                        document.removeEventListener('fullscreenchange', fullscreenchange);
                        document.removeEventListener('mozfullscreenchange', fullscreenchange);

                        element.requestPointerLock();

                }
            }

            document.addEventListener('fullscreenchange', fullscreenchange, false);
            document.addEventListener('mozfullscreenchange', fullscreenchange, false);

            element.requestFullscreen();

        } else
            element.requestPointerLock();

    } );

}
<|endoftext|>


<commit_before>const os = require('os');
const path = require('path');
const { app, shell, dialog } = require('electron');
const appName = app.getName();

// based on and inspired by
// https://github.com/sindresorhus/anatine/blob/master/menu.js

module.exports = function createMenu ({ createWindow, updatePlugins }) {
  return [
    {
      label: 'Application',
      submenu: [
        {
          role: 'about'
        },
        {
          type: 'separator'
        },
        {
          label: 'Preferences...',
          accelerator: 'Cmd+,',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('preferences');
            } else {
              createWindow(win => win.rpc.emit('preferences'));
            }
          }
        },
        {
          type: 'separator'
        },
        {
          role: 'services',
          submenu: []
        },
        {
          type: 'separator'
        },
        {
          role: 'hide'
        },
        {
          role: 'hideothers'
        },
        {
          role: 'unhide'
        },
        {
          type: 'separator'
        },
        {
          role: 'quit'
        }
      ]
    },
    {
      label: 'Shell',
      submenu: [
        {
          label: 'New Window',
          accelerator: 'CmdOrCtrl+N',
          click (item, focusedWindow) {
            createWindow();
          }
        },
        {
          label: 'New Tab',
          accelerator: 'CmdOrCtrl+T',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('session add req');
            } else {
              createWindow();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Close',
          accelerator: 'CmdOrCtrl+W',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('session close req');
            }
          }
        }
      ]
    },
    {
      label: 'Edit',
      submenu: [
        {
          role: 'undo'
        },
        {
          role: 'redo'
        },
        {
          type: 'separator'
        },
        {
          role: 'cut'
        },
        {
          role: 'copy'
        },
        {
          role: 'paste'
        },
        {
          role: 'selectall'
        },
        {
          type: 'separator'
        },
        {
          label: 'Clear',
          accelerator: 'CmdOrCtrl+K',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('session clear req');
            }
          }
        }
      ]
    },
    {
      label: 'View',
      submenu: [
        {
          label: 'Reload',
          accelerator: 'CmdOrCtrl+R',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('reload');
            }
          }
        },
        {
          label: 'Full Reload',
          accelerator: 'CmdOrCtrl+Shift+R',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.reload();
            }
          }
        },
        {
          label: 'Toggle Developer Tools',
          accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.toggleDevTools();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Actual Size',
          accelerator: 'CmdOrCtrl+0',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('reset fontSize req');
            }
          }
        },
        {
          label: 'Zoom In',
          accelerator: 'CmdOrCtrl+plus',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('increase fontSize req');
            }
          }
        },
        {
          label: 'Zoom Out',
          accelerator: 'CmdOrCtrl+-',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('decrease fontSize req');
            }
          }
        }
      ]
    },
    {
      label: 'Plugins',
      submenu: [
        {
          label: 'Update All Now',
          accelerator: 'CmdOrCtrl+Shift+U',
          click () {
            updatePlugins();
          }
        }
      ]
    },
    {
      label: 'Window',
      submenu: [
        {
          role: 'minimize'
        },
        {
          role: 'close'
        },
        {
          type: 'separator'
        },
        {
          label: 'Show Previous Tab',
          accelerator: 'CmdOrCtrl+Option+Left',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('move left req');
            }
          }
        },
        {
          label: 'Show Next Tab',
          accelerator: 'CmdOrCtrl+Option+Right',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('move right req');
            }
          }
        },
        {
          type: 'separator'
        },
        {
          role: 'front'
        },
        {
          role: 'togglefullscreen'
        }
      ]
    },
    {
      role: 'help',
      submenu: [
        {
          label: `${appName} Website`,
          click () {
            shell.openExternal('https://hyperterm.now.sh');
          }
        },
        {
          label: 'Report an Issue...',
          click () {
            const body = `
<!-- Please succinctly describe your issue and steps to reproduce it. -->

-

${app.getName()} ${app.getVersion()}
Electron ${process.versions.electron}
${process.platform} ${process.arch} ${os.release()}`;

            shell.openExternal(`https://github.com/zeit/hyperterm/issues/new?body=${encodeURIComponent(body)}`);
          }
        },
        ...(
          'darwin' !== process.platform
            ? [
                { type: 'separator' },
              {
                role: 'about',
                click () {
                  dialog.showMessageBox({
                    title: `About ${appName}`,
                    message: `${appName} ${app.getVersion()}`,
                    detail: 'Created by Guillermo Rauch',
                    icon: path.join(__dirname, 'static/icon.png'),
                    buttons: []
                  });
                }
              }
            ]
            : []
        )
      ]
    }
  ];
};
<commit_msg>Add window zoom menu item (#325)<commit_after>const os = require('os');
const path = require('path');
const { app, shell, dialog } = require('electron');
const appName = app.getName();

// based on and inspired by
// https://github.com/sindresorhus/anatine/blob/master/menu.js

module.exports = function createMenu ({ createWindow, updatePlugins }) {
  return [
    {
      label: 'Application',
      submenu: [
        {
          role: 'about'
        },
        {
          type: 'separator'
        },
        {
          label: 'Preferences...',
          accelerator: 'Cmd+,',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('preferences');
            } else {
              createWindow(win => win.rpc.emit('preferences'));
            }
          }
        },
        {
          type: 'separator'
        },
        {
          role: 'services',
          submenu: []
        },
        {
          type: 'separator'
        },
        {
          role: 'hide'
        },
        {
          role: 'hideothers'
        },
        {
          role: 'unhide'
        },
        {
          type: 'separator'
        },
        {
          role: 'quit'
        }
      ]
    },
    {
      label: 'Shell',
      submenu: [
        {
          label: 'New Window',
          accelerator: 'CmdOrCtrl+N',
          click (item, focusedWindow) {
            createWindow();
          }
        },
        {
          label: 'New Tab',
          accelerator: 'CmdOrCtrl+T',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('session add req');
            } else {
              createWindow();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Close',
          accelerator: 'CmdOrCtrl+W',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('session close req');
            }
          }
        },
        {
          label: 'Close Terminal Window',
          role: 'close',
          accelerator: 'CmdOrCtrl+Shift+W',
        }
      ]
    },
    {
      label: 'Edit',
      submenu: [
        {
          role: 'undo'
        },
        {
          role: 'redo'
        },
        {
          type: 'separator'
        },
        {
          role: 'cut'
        },
        {
          role: 'copy'
        },
        {
          role: 'paste'
        },
        {
          role: 'selectall'
        },
        {
          type: 'separator'
        },
        {
          label: 'Clear',
          accelerator: 'CmdOrCtrl+K',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('session clear req');
            }
          }
        }
      ]
    },
    {
      label: 'View',
      submenu: [
        {
          label: 'Reload',
          accelerator: 'CmdOrCtrl+R',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('reload');
            }
          }
        },
        {
          label: 'Full Reload',
          accelerator: 'CmdOrCtrl+Shift+R',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.reload();
            }
          }
        },
        {
          label: 'Toggle Developer Tools',
          accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.toggleDevTools();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Actual Size',
          accelerator: 'CmdOrCtrl+0',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('reset fontSize req');
            }
          }
        },
        {
          label: 'Zoom In',
          accelerator: 'CmdOrCtrl+plus',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('increase fontSize req');
            }
          }
        },
        {
          label: 'Zoom Out',
          accelerator: 'CmdOrCtrl+-',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('decrease fontSize req');
            }
          }
        }
      ]
    },
    {
      label: 'Plugins',
      submenu: [
        {
          label: 'Update All Now',
          accelerator: 'CmdOrCtrl+Shift+U',
          click () {
            updatePlugins();
          }
        }
      ]
    },
    {
      label: 'Window',
      submenu: [
        {
          role: 'minimize'
        },
        {
          role: 'zoom'
        },
        {
          type: 'separator'
        },
        {
          label: 'Show Previous Tab',
          accelerator: 'CmdOrCtrl+Option+Left',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('move left req');
            }
          }
        },
        {
          label: 'Show Next Tab',
          accelerator: 'CmdOrCtrl+Option+Right',
          click (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.rpc.emit('move right req');
            }
          }
        },
        {
          type: 'separator'
        },
        {
          role: 'front'
        },
        {
          role: 'togglefullscreen'
        }
      ]
    },
    {
      role: 'help',
      submenu: [
        {
          label: `${appName} Website`,
          click () {
            shell.openExternal('https://hyperterm.now.sh');
          }
        },
        {
          label: 'Report an Issue...',
          click () {
            const body = `
<!-- Please succinctly describe your issue and steps to reproduce it. -->

-

${app.getName()} ${app.getVersion()}
Electron ${process.versions.electron}
${process.platform} ${process.arch} ${os.release()}`;

            shell.openExternal(`https://github.com/zeit/hyperterm/issues/new?body=${encodeURIComponent(body)}`);
          }
        },
        ...(
          'darwin' !== process.platform
            ? [
                { type: 'separator' },
              {
                role: 'about',
                click () {
                  dialog.showMessageBox({
                    title: `About ${appName}`,
                    message: `${appName} ${app.getVersion()}`,
                    detail: 'Created by Guillermo Rauch',
                    icon: path.join(__dirname, 'static/icon.png'),
                    buttons: []
                  });
                }
              }
            ]
            : []
        )
      ]
    }
  ];
};
<|endoftext|>